View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.InputStream;
29  import java.math.BigDecimal;
30  import java.math.BigInteger;
31  import java.math.MathContext;
32  import java.nio.charset.StandardCharsets;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.Date;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Objects;
40  import java.util.concurrent.CountDownLatch;
41  import java.util.concurrent.atomic.AtomicBoolean;
42  import java.util.concurrent.atomic.AtomicReference;
43  
44  import javax.xml.parsers.DocumentBuilder;
45  import javax.xml.parsers.DocumentBuilderFactory;
46  
47  import org.apache.commons.jexl3.junit.Asserter;
48  import org.apache.commons.lang3.SystemProperties;
49  import org.junit.jupiter.api.BeforeEach;
50  import org.junit.jupiter.api.Test;
51  import org.w3c.dom.Attr;
52  import org.w3c.dom.Document;
53  import org.w3c.dom.NamedNodeMap;
54  import org.w3c.dom.Node;
55  
56  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
57  class ArithmeticTest extends JexlTestCase {
58      public static class Arithmetic132 extends JexlArithmetic {
59          public Arithmetic132() {
60              super(false);
61          }
62  
63          @Override
64          public Object divide(final Object left, final Object right) {
65              if (left == null && right == null) {
66                  return controlNullNullOperands(JexlOperator.DIVIDE);
67              }
68              // if either are bigdecimal use that type
69              if (left instanceof BigDecimal || right instanceof BigDecimal) {
70                  final BigDecimal l = toBigDecimal(left);
71                  final BigDecimal r = toBigDecimal(right);
72                  if (BigDecimal.ZERO.equals(r)) {
73                      return divideZero(l);
74                  }
75                  final BigDecimal result = l.divide(r, getMathContext());
76                  return narrowBigDecimal(left, right, result);
77              }
78              // if either are floating point (double or float) use double
79              if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
80                  final double l = toDouble(left);
81                  final double r = toDouble(right);
82                  return Double.valueOf(l / r);
83              }
84              // otherwise treat as integers
85              final BigInteger l = toBigInteger(left);
86              final BigInteger r = toBigInteger(right);
87              if (BigInteger.ZERO.equals(r)) {
88                  return divideZero(l);
89              }
90              final BigInteger result = l.divide(r);
91              return narrowBigInteger(left, right, result);
92          }
93  
94          protected double divideZero(final BigDecimal x) {
95              final int ls = x.signum();
96              if (ls < 0) {
97                  return Double.NEGATIVE_INFINITY;
98              }
99              if (ls > 0) {
100                 return Double.POSITIVE_INFINITY;
101             }
102             return Double.NaN;
103         }
104 
105         protected double divideZero(final BigInteger x) {
106             final int ls = x.signum();
107             if (ls < 0) {
108                 return Double.NEGATIVE_INFINITY;
109             }
110             if (ls > 0) {
111                 return Double.POSITIVE_INFINITY;
112             }
113             return Double.NaN;
114         }
115 
116         @Override
117         public Object mod(final Object left, final Object right) {
118             if (left == null && right == null) {
119                 return controlNullNullOperands(JexlOperator.MOD);
120             }
121             // if either are bigdecimal use that type
122             if (left instanceof BigDecimal || right instanceof BigDecimal) {
123                 final BigDecimal l = toBigDecimal(left);
124                 final BigDecimal r = toBigDecimal(right);
125                 if (BigDecimal.ZERO.equals(r)) {
126                     return divideZero(l);
127                 }
128                 final BigDecimal remainder = l.remainder(r, getMathContext());
129                 return narrowBigDecimal(left, right, remainder);
130             }
131             // if either are floating point (double or float) use double
132             if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
133                 final double l = toDouble(left);
134                 final double r = toDouble(right);
135                 return Double.valueOf(l % r);
136             }
137             // otherwise treat as integers
138             final BigInteger l = toBigInteger(left);
139             final BigInteger r = toBigInteger(right);
140             final BigInteger result = l.mod(r);
141             if (BigInteger.ZERO.equals(r)) {
142                 return divideZero(l);
143             }
144             return narrowBigInteger(left, right, result);
145         }
146     }
147     // an arithmetic that fail systematically with vars
148     public static class ArithmeticFail extends JexlArithmetic {
149         public ArithmeticFail(final boolean strict) {
150             super(strict);
151         }
152 
153         @Override
154         public Object add(final Object lhs, final Object rhs) {
155             throw new ArithmeticException(lhs + " o " + rhs);
156         }
157 
158         @Override
159         public Object and(final Object lhs, final Object rhs) {
160             throw new ArithmeticException(lhs + " o " + rhs);
161         }
162 
163         @Override
164         public Object complement(final Object arg) {
165             throw new ArithmeticException(Objects.toString(arg));
166         }
167 
168         @Override
169         public Boolean contains(final Object lhs, final Object rhs) {
170             throw new ArithmeticException(lhs + " o " + rhs);
171         }
172 
173         @Override
174         public Object decrement(final Object arg) {
175             throw new ArithmeticException(Objects.toString(arg));
176         }
177 
178         @Override
179         public Object divide(final Object lhs, final Object rhs) {
180             throw new ArithmeticException(lhs + " o " + rhs);
181         }
182 
183         @Override
184         public Boolean endsWith(final Object lhs, final Object rhs) {
185             throw new ArithmeticException(lhs + " o " + rhs);
186         }
187 
188         @Override
189         public boolean equals(final Object lhs, final Object rhs) {
190             throw new ArithmeticException(lhs + " o " + rhs);
191         }
192 
193         @Override
194         public boolean greaterThan(final Object lhs, final Object rhs) {
195             throw new ArithmeticException(lhs + " o " + rhs);
196         }
197 
198         @Override
199         public boolean greaterThanOrEqual(final Object lhs, final Object rhs) {
200             throw new ArithmeticException(lhs + " o " + rhs);
201         }
202 
203         @Override
204         public Object increment(final Object arg) {
205             throw new ArithmeticException(Objects.toString(arg));
206         }
207 
208         @Override
209         public boolean lessThan(final Object lhs, final Object rhs) {
210             throw new ArithmeticException(lhs + " o " + rhs);
211         }
212 
213         @Override
214         public boolean lessThanOrEqual(final Object lhs, final Object rhs) {
215             throw new ArithmeticException(lhs + " o " + rhs);
216         }
217 
218         @Override
219         public Object mod(final Object lhs, final Object rhs) {
220             throw new ArithmeticException(lhs + " o " + rhs);
221         }
222 
223         @Override
224         public Object multiply(final Object lhs, final Object rhs) {
225             throw new ArithmeticException(lhs + " o " + rhs);
226         }
227 
228         @Override
229         public Object negate(final Object arg) {
230             throw new ArithmeticException(Objects.toString(arg));
231         }
232 
233         @Override
234         public Object not(final Object arg) {
235             throw new ArithmeticException(Objects.toString(arg));
236         }
237 
238         @Override
239         public Object or(final Object lhs, final Object rhs) {
240             throw new ArithmeticException(lhs + " o " + rhs);
241         }
242 
243         @Override
244         public Object shiftLeft(final Object lhs, final Object rhs) {
245             throw new ArithmeticException(lhs + " o " + rhs);
246         }
247 
248         @Override
249         public Object shiftRight(final Object lhs, final Object rhs) {
250             throw new ArithmeticException(lhs + " o " + rhs);
251         }
252 
253         @Override
254         public Object shiftRightUnsigned(final Object lhs, final Object rhs) {
255             throw new ArithmeticException(lhs + " o " + rhs);
256         }
257 
258         @Override
259         public Boolean startsWith(final Object lhs, final Object rhs) {
260             throw new ArithmeticException(lhs + " o " + rhs);
261         }
262 
263         @Override
264         public boolean strictEquals(final Object lhs, final Object rhs) {
265             throw new ArithmeticException(lhs + " o " + rhs);
266         }
267 
268         @Override
269         public Object subtract(final Object lhs, final Object rhs) {
270             throw new ArithmeticException(lhs + " o " + rhs);
271         }
272 
273         @Override
274         public boolean toBoolean(final Object x) {
275             throw new ArithmeticException(Objects.toString(x));
276         }
277 
278         @Override
279         public Object xor(final Object lhs, final Object rhs) {
280             throw new ArithmeticException(lhs + " o " + rhs);
281         }
282     }
283 
284     // an arithmetic that knows how to deal with vars
285     public static class ArithmeticPlus extends JexlArithmetic {
286         public ArithmeticPlus(final boolean strict) {
287             super(strict);
288         }
289 
290         public Var add(final Var lhs, final Var rhs) {
291             return new Var(lhs.value + rhs.value);
292         }
293 
294         public Var and(final Var lhs, final Var rhs) {
295             return new Var(lhs.value & rhs.value);
296         }
297 
298         public Var complement(final Var arg) {
299             return new Var(~arg.value);
300         }
301 
302         public Boolean contains(final Var lhs, final Var rhs) {
303             return lhs.toString().contains(rhs.toString());
304         }
305 
306         public Var divide(final Var lhs, final Var rhs) {
307             return new Var(lhs.value / rhs.value);
308         }
309 
310         public Boolean endsWith(final Var lhs, final Var rhs) {
311             return lhs.toString().endsWith(rhs.toString());
312         }
313 
314         public boolean equals(final Var lhs, final Var rhs) {
315             return lhs.value == rhs.value;
316         }
317 
318         public boolean greaterThan(final Var lhs, final Var rhs) {
319             return lhs.value > rhs.value;
320         }
321 
322         public boolean greaterThanOrEqual(final Var lhs, final Var rhs) {
323             return lhs.value >= rhs.value;
324         }
325 
326         public boolean lessThan(final Var lhs, final Var rhs) {
327             return lhs.value < rhs.value;
328         }
329 
330         public boolean lessThanOrEqual(final Var lhs, final Var rhs) {
331             return lhs.value <= rhs.value;
332         }
333 
334         public Var mod(final Var lhs, final Var rhs) {
335             return new Var(lhs.value / rhs.value);
336         }
337 
338         public Var multiply(final Var lhs, final Var rhs) {
339             return new Var(lhs.value * rhs.value);
340         }
341 
342         public Object negate(final String str) {
343             final int length = str.length();
344             final StringBuilder strb = new StringBuilder(str.length());
345             for (int c = length - 1; c >= 0; --c) {
346                 strb.append(str.charAt(c));
347             }
348             return strb.toString();
349         }
350 
351         public Var negate(final Var arg) {
352             return new Var(-arg.value);
353         }
354 
355         public Object not(final Var x) {
356             throw new NullPointerException("make it fail");
357         }
358 
359         public Var or(final Var lhs, final Var rhs) {
360             return new Var(lhs.value | rhs.value);
361         }
362 
363         public Var shiftLeft(final Var lhs, final Var rhs) {
364             return new Var(lhs.value << rhs.value);
365         }
366 
367         public Var shiftRight(final Var lhs, final Var rhs) {
368             return new Var(lhs.value >> rhs.value);
369         }
370 
371         public Var shiftRightUnsigned(final Var lhs, final Var rhs) {
372             return new Var(lhs.value >>> rhs.value);
373         }
374 
375         public Boolean startsWith(final Var lhs, final Var rhs) {
376             return lhs.toString().startsWith(rhs.toString());
377         }
378 
379         public Object subtract(final String x, final String y) {
380             final int ix = x.indexOf(y);
381             if (ix < 0) {
382                 return x;
383             }
384             final StringBuilder strb = new StringBuilder(x.substring(0, ix));
385             strb.append(x.substring(ix + y.length()));
386             return strb.toString();
387         }
388 
389         public Var subtract(final Var lhs, final Var rhs) {
390             return new Var(lhs.value - rhs.value);
391         }
392 
393         public Var xor(final Var lhs, final Var rhs) {
394             return new Var(lhs.value ^ rhs.value);
395         }
396     }
397 
398     public static class Callable173 {
399         public Object call(final Integer... arg) {
400             return arg[0] * arg[1];
401         }
402         public Object call(final String... arg) {
403             return 42;
404         }
405     }
406 
407     public static class EmptyTestContext extends MapContext implements JexlContext.NamespaceResolver {
408         public static int log(final Object fmt, final int... arr) {
409             //System.out.println(String.format(fmt.toString(), arr));
410             return arr == null ? 0 : arr.length;
411         }
412 
413         public static int log(final Object fmt, final Object... arr) {
414             //System.out.println(String.format(fmt.toString(), arr));
415             return arr == null ? 0 : arr.length;
416         }
417 
418         @Override
419         public Object resolveNamespace(final String name) {
420             return this;
421         }
422     }
423     public static class InstanceofContext extends MapContext implements JexlContext.ClassNameResolver {
424         @Override
425         public String resolveClassName(final String name) {
426             if ("Double".equals(name)) {
427                 return Double.class.getName();
428             }
429             return null;
430         }
431     }
432     public static class Var {
433         int value;
434 
435         Var(final int v) {
436             value = v;
437         }
438 
439         @Override
440         public String toString() {
441             return Integer.toString(value);
442         }
443     }
444 
445     /**
446      * An arithmetic that has some DOM node knowledge.
447      */
448     public static class XmlArithmetic extends JexlArithmetic {
449         public XmlArithmetic(final boolean astrict) {
450             super(astrict);
451         }
452 
453         public XmlArithmetic(final boolean astrict, final MathContext bigdContext, final int bigdScale) {
454             super(astrict, bigdContext, bigdScale);
455         }
456 
457         public boolean empty(final org.w3c.dom.Element elt) {
458             return !elt.hasAttributes() && !elt.hasChildNodes();
459         }
460 
461         public int size(final org.w3c.dom.Element elt) {
462             return elt.getChildNodes().getLength();
463         }
464 
465         public Object propertyGet(final NamedNodeMap attrs, final String name) {
466             final Node node  = attrs.getNamedItem(name);
467             return node == null ? JexlEngine.TRY_FAILED : node.getNodeValue();
468         }
469 
470         public Object propertySet(final NamedNodeMap attrs, final String name, final Object value) {
471             final Node node = attrs.getNamedItem(name);
472             if (node == null) {
473                 return JexlEngine.TRY_FAILED;
474             }
475             node.setNodeValue(value.toString());
476             return value;
477         }
478 
479         public Object propertyGet(final Node elt, final String name) {
480             if ("attributes".equals(name)) {
481                 return elt.getAttributes();
482             }
483             return JexlEngine.TRY_FAILED;
484         }
485     }
486 
487     /** A small delta to compare doubles. */
488     private static final double EPSILON = 1.e-6;
489 
490     private static void assertArithmeticException(final java.util.function.Supplier<Object> fun) {
491         assertThrows(ArithmeticException.class, fun::get);
492     }
493 
494     private static void assertNullOperand(final java.util.function.Supplier<Object> fun) {
495         try {
496             assertNull(fun.get());
497         } catch (final JexlArithmetic.NullOperand xany) {
498             assertNotNull(xany);
499         }
500     }
501 
502     private static Document getDocument(final String xml) throws Exception {
503         final DocumentBuilder xmlBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
504         final InputStream stringInputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
505         return xmlBuilder.parse(stringInputStream);
506     }
507 
508     /**
509      * Returns the Java version as an int value.
510      * @return The Java version as an int value (8, 9, etc.)
511      */
512     private static int getJavaVersion() {
513         String version = SystemProperties.getJavaVersion();
514         if (version.startsWith("1.")) {
515             version = version.substring(2);
516         }
517         int sep = version.indexOf(".");
518         if (sep < 0) {
519             sep = version.indexOf("-");
520         }
521         if (sep > 0) {
522             version = version.substring(0, sep);
523         }
524         return Integer.parseInt(version);
525     }
526 
527     private final Asserter asserter;
528 
529     private final JexlArithmetic jexla;
530 
531     private final JexlArithmetic jexlb;
532 
533     private final Date date = new Date();
534 
535     public ArithmeticTest() {
536         super("ArithmeticTest");
537         asserter = new Asserter(JEXL);
538         jexla = JEXL.getArithmetic();
539         final JexlOptions options = new JexlOptions();
540         options.setStrictArithmetic(false);
541         jexlb = jexla.options(options);
542     }
543 
544     void checkEmpty(final Object x, final boolean expect) {
545         final JexlScript s0 = JEXL.createScript("empty(x)", "x");
546         boolean empty = (Boolean) s0.execute(null, x);
547         assertEquals(expect, empty);
548         final JexlScript s1 = JEXL.createScript("empty x", "x");
549         empty = (Boolean) s1.execute(null, x);
550         assertEquals(expect, empty);
551         final JexlScript s2 = JEXL.createScript("x.empty()", "x");
552         empty = (Boolean) s2.execute(null, x);
553         assertEquals(expect, empty);
554     }
555 
556     private void runInstanceof(final JexlEngine jexl, final JexlContext ctxt) {
557         Object r = jexl.createExpression("3.0 instanceof 'Double'").evaluate(ctxt);
558         assertTrue((Boolean) r);
559         r = jexl.createExpression("'3.0' !instanceof 'Double'").evaluate(ctxt);
560         assertTrue((Boolean) r);
561         JexlScript script = jexl.createScript("x instanceof y", "x", "y");
562         r = script.execute(ctxt, "foo", String.class);
563         assertTrue((Boolean) r);
564         r = script.execute(ctxt, 42.0, Double.class);
565         assertTrue((Boolean) r);
566         script = jexl.createScript("x !instanceof y", "x", "y");
567         r = script.execute(ctxt, "foo", Double.class);
568         assertTrue((Boolean) r);
569         r = script.execute(ctxt, 42.0, String.class);
570         assertTrue((Boolean) r);
571     }
572 
573     protected void runOverload(final JexlEngine jexl, final JexlContext jc) {
574         JexlScript script;
575         Object result;
576 
577         script = jexl.createScript("(x, y)->{ x < y }");
578         result = script.execute(jc, 42, 43);
579         assertEquals(true, result);
580         result = script.execute(jc, new Var(42), new Var(43));
581         assertEquals(true, result);
582         result = script.execute(jc, new Var(42), new Var(43));
583         assertEquals(true, result);
584         result = script.execute(jc, 43, 42);
585         assertEquals(false, result);
586         result = script.execute(jc, new Var(43), new Var(42));
587         assertEquals(false, result);
588 
589         script = jexl.createScript("(x, y)->{ x <= y }");
590         result = script.execute(jc, 42, 43);
591         assertEquals(true, result);
592         result = script.execute(jc, new Var(42), new Var(43));
593         assertEquals(true, result);
594         result = script.execute(jc, new Var(41), new Var(44));
595         assertEquals(true, result);
596         result = script.execute(jc, 43, 42);
597         assertEquals(false, result);
598         result = script.execute(jc, new Var(45), new Var(40));
599         assertEquals(false, result);
600 
601         script = jexl.createScript("(x, y)->{ x > y }");
602         result = script.execute(jc, 42, 43);
603         assertEquals(false, result);
604         result = script.execute(jc, new Var(42), new Var(43));
605         assertEquals(false, result);
606         result = script.execute(jc, new Var(42), new Var(43));
607         assertEquals(false, result);
608         result = script.execute(jc, 43, 42);
609         assertEquals(true, result);
610         result = script.execute(jc, new Var(43), new Var(42));
611         assertEquals(true, result);
612 
613         script = jexl.createScript("(x, y)->{ x >= y }");
614         result = script.execute(jc, 42, 43);
615         assertEquals(false, result);
616         result = script.execute(jc, new Var(42), new Var(43));
617         assertEquals(false, result);
618         result = script.execute(jc, new Var(41), new Var(44));
619         assertEquals(false, result);
620         result = script.execute(jc, 43, 42);
621         assertEquals(true, result);
622         result = script.execute(jc, new Var(45), new Var(40));
623         assertEquals(true, result);
624 
625         script = jexl.createScript("(x, y)->{ x == y }");
626         result = script.execute(jc, 42, 43);
627         assertEquals(false, result);
628         result = script.execute(jc, new Var(42), new Var(43));
629         assertEquals(false, result);
630         result = script.execute(jc, new Var(41), new Var(44));
631         assertEquals(false, result);
632         result = script.execute(jc, 43, 42);
633         assertEquals(false, result);
634         result = script.execute(jc, new Var(45), new Var(40));
635         assertEquals(false, result);
636 
637         script = jexl.createScript("(x, y)->{ x != y }");
638         result = script.execute(jc, 42, 43);
639         assertEquals(true, result);
640         result = script.execute(jc, new Var(42), new Var(43));
641         assertEquals(true, result);
642         result = script.execute(jc, new Var(44), new Var(44));
643         assertEquals(false, result);
644         result = script.execute(jc, 44, 44);
645         assertEquals(false, result);
646         result = script.execute(jc, new Var(45), new Var(40));
647         assertEquals(true, result);
648 
649         script = jexl.createScript("(x, y)->{ x % y }");
650         result = script.execute(jc, 4242, 100);
651         assertEquals(42, result);
652         result = script.execute(jc, new Var(4242), new Var(100));
653         assertEquals(42, ((Var) result).value);
654 
655         script = jexl.createScript("(x, y)->{ x * y }");
656         result = script.execute(jc, 6, 7);
657         assertEquals(42, result);
658         result = script.execute(jc, new Var(6), new Var(7));
659         assertEquals(42, ((Var) result).value);
660 
661         script = jexl.createScript("(x, y)->{ x + y }");
662         result = script.execute(jc, 35, 7);
663         assertEquals(42, result);
664         result = script.execute(jc, new Var(35), new Var(7));
665         assertEquals(42, ((Var) result).value);
666 
667         script = jexl.createScript("(x, y)->{ x - y }");
668         result = script.execute(jc, 49, 7);
669         assertEquals(42, result);
670         result = script.execute(jc, "foobarquux", "bar");
671         assertEquals("fooquux", result);
672         result = script.execute(jc, 50, 8);
673         assertEquals(42, result);
674         result = script.execute(jc, new Var(50), new Var(8));
675         assertEquals(42, ((Var) result).value);
676 
677         script = jexl.createScript("(x)->{ -x }");
678         result = script.execute(jc, -42);
679         assertEquals(42, result);
680         result = script.execute(jc, new Var(-42));
681         assertEquals(42, ((Var) result).value);
682         result = script.execute(jc, "pizza");
683         assertEquals("azzip", result);
684         result = script.execute(jc, -142);
685         assertEquals(142, result);
686 
687         script = jexl.createScript("(x)->{ ~x }");
688         result = script.execute(jc, -1);
689         assertEquals(0L, result);
690         result = script.execute(jc, new Var(-1));
691         assertEquals(0L, ((Var) result).value);
692         result = script.execute(jc, new Var(-42));
693         assertEquals(41, ((Var) result).value);
694 
695         script = jexl.createScript("(x, y)->{ x ^ y }");
696         result = script.execute(jc, 35, 7);
697         assertEquals(36L, result);
698         result = script.execute(jc, new Var(35), new Var(7));
699         assertEquals(36L, ((Var) result).value);
700         // legacy
701         script = jexl.createScript("(x, y)->bitwiseXor(x,y)");
702         result = script.execute(jc, 35, 7);
703         assertEquals(36L, result);
704 
705         script = jexl.createScript("(x, y)->{ x | y }");
706         result = script.execute(jc, 35, 7);
707         assertEquals(39L, result);
708         result = script.execute(jc, new Var(35), new Var(7));
709         assertEquals(39L, ((Var) result).value);
710         // legacy
711         script = jexl.createScript("(x, y)->bitwiseOr(x,y)");
712         result = script.execute(jc, 35, 7);
713         assertEquals(39L, result);
714 
715         script = jexl.createScript("(x, y)->{ x << y }");
716         result = script.execute(jc, 35, 1);
717         assertEquals(70L, result);
718         result = script.execute(jc, new Var(35), new Var(1));
719         assertEquals(70L, ((Var) result).value);
720 
721         script = jexl.createScript("(x, y)->{ x >> y }");
722         result = script.execute(jc, 42, 1);
723         assertEquals(21L, result);
724         result = script.execute(jc, new Var(42), new Var(1));
725         assertEquals(21, ((Var) result).value);
726 
727         script = jexl.createScript("(x, y)->{ x >>> y }");
728         result = script.execute(jc, 84, 2);
729         assertEquals(21L, result);
730         result = script.execute(jc, new Var(84), new Var(2));
731         assertEquals(21, ((Var) result).value);
732 
733         script = jexl.createScript("(x, y)->{ x & y }");
734         result = script.execute(jc, 35, 7);
735         assertEquals(3L, result);
736         result = script.execute(jc, new Var(35), new Var(7));
737         assertEquals(3L, ((Var) result).value);
738         // legacy
739         script = jexl.createScript("(x, y)->bitwiseAnd(x,y)");
740         result = script.execute(jc, 35, 7);
741         assertEquals(3L, result);
742 
743         script = jexl.createScript("(x, y)->{ x =^ y }");
744         result = script.execute(jc, 3115, 31);
745         assertFalse((Boolean) result);
746         result = script.execute(jc, new Var(3115), new Var(31));
747         assertTrue((Boolean) result);
748 
749         script = jexl.createScript("(x, y)->{ x !^ y }");
750         result = script.execute(jc, 3115, 31);
751         assertTrue((Boolean) result);
752         result = script.execute(jc, new Var(3115), new Var(31));
753         assertFalse((Boolean) result);
754 
755         script = jexl.createScript("(x, y)->{ x =$ y }");
756         result = script.execute(jc, 3115, 15);
757         assertFalse((Boolean) result);
758         result = script.execute(jc, new Var(3115), new Var(15));
759         assertTrue((Boolean) result);
760 
761         script = jexl.createScript("(x, y)->{ x !$ y }");
762         result = script.execute(jc, 3115, 15);
763         assertTrue((Boolean) result);
764         result = script.execute(jc, new Var(3115), new Var(15));
765         assertFalse((Boolean) result);
766 
767         script = jexl.createScript("(x, y)->{ x =~ y }");
768         result = script.execute(jc, 3155, 15);
769         assertFalse((Boolean) result);
770         result = script.execute(jc, new Var(3155), new Var(15));
771         assertFalse((Boolean) result);
772         result = script.execute(jc, new Var(15), new Var(3155));
773         assertTrue((Boolean) result);
774 
775         script = jexl.createScript("(x, y)->{ x !~ y }");
776         result = script.execute(jc, 3115, 15);
777         assertTrue((Boolean) result);
778         result = script.execute(jc, new Var(3155), new Var(15));
779         assertTrue((Boolean) result);
780         result = script.execute(jc, new Var(15), new Var(3155));
781         assertFalse((Boolean) result);
782 
783         final JexlScript script1 = jexl.createScript("(x)->{ !x }");
784         assertThrows(JexlException.Operator.class, () -> script1.execute(jc, new Var(-42)));
785     }
786 
787     @BeforeEach
788     @Override
789     public void setUp() {
790     }
791 
792     // JEXL-24: doubles with exponent
793     @Test
794     void test2DoubleLiterals() {
795         final JexlEvalContext ctxt = new JexlEvalContext();
796         final JexlOptions options = ctxt.getEngineOptions();
797         options.setStrictArithmetic(true);
798         final String stmt = "{a = 42.0e1D; b = 42.0E+2D; c = 42.0e-1d; d = 42.0E-2d; e=10e10; f= +1.e1; g=1e1; }";
799         final JexlScript expr = JEXL.createScript(stmt);
800         /* Object value = */ expr.execute(ctxt);
801         assertEquals(Double.valueOf("42.0e+1"), ctxt.get("a"));
802         assertEquals(Double.valueOf("42.0e+2"), ctxt.get("b"));
803         assertEquals(Double.valueOf("42.0e-1"), ctxt.get("c"));
804         assertEquals(Double.valueOf("42.0e-2"), ctxt.get("d"));
805         assertEquals(Double.valueOf("10e10"), ctxt.get("e"));
806         assertEquals(Double.valueOf("10"), ctxt.get("f"));
807         assertEquals(Double.valueOf("10"), ctxt.get("g"));
808     }
809 
810     @Test
811     void testAddWithStringsLenient() {
812         final JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(false)).create();
813         JexlScript script;
814         Object result;
815         script = jexl.createScript("'a' + 0");
816         result = script.execute(null);
817         assertEquals("a0", result);
818 
819         script = jexl.createScript("0 + 'a' ");
820         result = script.execute(null);
821         assertEquals("0a", result);
822 
823         script = jexl.createScript("0 + '1.2' ");
824         result = script.execute(null);
825         assertEquals(1.2d, (Double) result, EPSILON);
826 
827         script = jexl.createScript("'1.2' + 1.2 ");
828         result = script.execute(null);
829         assertEquals(2.4d, (Double) result, EPSILON);
830 
831         script = jexl.createScript("1.2 + 1.2 ");
832         result = script.execute(null);
833         assertEquals(2.4d, (Double) result, EPSILON);
834 
835         script = jexl.createScript("1.2 + '1.2' ");
836         result = script.execute(null);
837         assertEquals(2.4d, (Double) result, EPSILON);
838 
839         script = jexl.createScript("'1.2' + 0 ");
840         result = script.execute(null);
841         assertEquals(1.2d, (Double) result, EPSILON);
842 
843         script = jexl.createScript("'1.2' + '1.2' ");
844         result = script.execute(null);
845         assertEquals("1.21.2", result);
846     }
847 
848     @Test
849     void testAddWithStringsStrict() {
850         final JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(true)).create();
851         JexlScript script;
852         Object result;
853         script = jexl.createScript("'a' + 0");
854         result = script.execute(null);
855         assertEquals("a0", result);
856 
857         script = jexl.createScript("0 + 'a' ");
858         result = script.execute(null);
859         assertEquals("0a", result);
860 
861         script = jexl.createScript("0 + '1.2' ");
862         result = script.execute(null);
863         assertEquals("01.2", result);
864 
865         script = jexl.createScript("'1.2' + 1.2 ");
866         result = script.execute(null);
867         assertEquals("1.21.2", result);
868 
869         script = jexl.createScript("1.2 + 1.2 ");
870         result = script.execute(null);
871         assertEquals(2.4d, (Double) result, EPSILON);
872 
873         script = jexl.createScript("1.2 + '1.2' ");
874         result = script.execute(null);
875         assertEquals("1.21.2", result);
876 
877         script = jexl.createScript("'1.2' + 0 ");
878         result = script.execute(null);
879         assertEquals("1.20", result);
880 
881         script = jexl.createScript("'1.2' + '1.2' ");
882         result = script.execute(null);
883         assertEquals("1.21.2", result);
884 
885     }
886 
887     @Test
888     void testArithmeticPlus() {
889         final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new ArithmeticPlus(false)).create();
890         final JexlContext jc = new EmptyTestContext();
891         runOverload(jexl, jc);
892         runOverload(jexl, jc);
893     }
894 
895     @Test
896     void testArithmeticPlusNoCache() {
897         final JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new ArithmeticPlus(false)).create();
898         final JexlContext jc = new EmptyTestContext();
899         runOverload(jexl, jc);
900     }
901 
902     @Test
903     void testAtomicBoolean() {
904         // in a condition
905         JexlScript e = JEXL.createScript("if (x) 1 else 2;", "x");
906         final JexlArithmetic jexla = JEXL.getArithmetic();
907         final JexlContext jc = new MapContext();
908         final AtomicBoolean ab = new AtomicBoolean();
909         Object o;
910         o = e.execute(jc, ab);
911         assertEquals(Integer.valueOf(2), o);
912         ab.set(true);
913         o = e.execute(jc, ab);
914         assertEquals(Integer.valueOf(1), o);
915         // in a binary logical op
916         e = JEXL.createScript("x && y", "x", "y");
917         ab.set(true);
918         o = e.execute(jc, ab, Boolean.FALSE);
919         assertFalse(jexla.toBoolean(o));
920         ab.set(true);
921         o = e.execute(jc, ab, Boolean.TRUE);
922         assertTrue(jexla.toBoolean(o));
923         ab.set(false);
924         o = e.execute(jc, ab, Boolean.FALSE);
925         assertFalse(jexla.toBoolean(o));
926         ab.set(false);
927         o = e.execute(jc, ab, Boolean.FALSE);
928         assertFalse(jexla.toBoolean(o));
929         // in arithmetic op
930         e = JEXL.createScript("x + y", "x", "y");
931         ab.set(true);
932         o = e.execute(jc, ab, 10);
933         assertEquals(11, o);
934         o = e.execute(jc, 10, ab);
935         assertEquals(11, o);
936         o = e.execute(jc, ab, 10.d);
937         assertEquals(11.d, (Double) o, EPSILON);
938         o = e.execute(jc, 10.d, ab);
939         assertEquals(11.d, (Double) o, EPSILON);
940 
941         final BigInteger bi10 = BigInteger.TEN;
942         ab.set(false);
943         o = e.execute(jc, ab, bi10);
944         assertEquals(bi10, o);
945         o = e.execute(jc, bi10, ab);
946         assertEquals(bi10, o);
947 
948         final BigDecimal bd10 = BigDecimal.TEN;
949         ab.set(false);
950         o = e.execute(jc, ab, bd10);
951         assertEquals(bd10, o);
952         o = e.execute(jc, bd10, ab);
953         assertEquals(bd10, o);
954 
955         // in a (the) monadic op
956         e = JEXL.createScript("!x", "x");
957         ab.set(true);
958         o = e.execute(jc, ab);
959         assertFalse((Boolean) o);
960         ab.set(false);
961         o = e.execute(jc, ab);
962         assertTrue((Boolean) o);
963 
964         // in a (the) monadic op
965         e = JEXL.createScript("-x", "x");
966         ab.set(true);
967         o = e.execute(jc, ab);
968         assertFalse((Boolean) o);
969         ab.set(false);
970         o = e.execute(jc, ab);
971         assertTrue((Boolean) o);
972     }
973 
974     @Test
975     void testBigDecimal() throws Exception {
976         asserter.setVariable("left", new BigDecimal(2));
977         asserter.setVariable("right", new BigDecimal(6));
978         asserter.assertExpression("left + right", new BigDecimal(8));
979         asserter.assertExpression("right - left", new BigDecimal(4));
980         asserter.assertExpression("right * left", new BigDecimal(12));
981         asserter.assertExpression("right / left", new BigDecimal(3));
982         asserter.assertExpression("right % left", new BigDecimal(0));
983     }
984 
985     @Test
986     void testBigdOp() {
987         final BigDecimal sevendot475 = new BigDecimal("7.475");
988         final BigDecimal SO = new BigDecimal("325");
989         final JexlContext jc = new MapContext();
990         jc.set("SO", SO);
991 
992         final String expr = "2.3*SO/100";
993 
994         final Object evaluate = JEXL.createExpression(expr).evaluate(jc);
995         assertEquals(sevendot475, evaluate);
996     }
997 
998     // JEXL-24: big decimals with exponent
999     @Test
1000     void testBigExponentLiterals() {
1001         final JexlEvalContext ctxt = new JexlEvalContext();
1002         final JexlOptions options = ctxt.getEngineOptions();
1003         options.setStrictArithmetic(true);
1004         final String stmt = "{a = 42.0e1B; b = 42.0E+2B; c = 42.0e-1B; d = 42.0E-2b; e=4242.4242e1b}";
1005         final JexlScript expr = JEXL.createScript(stmt);
1006         /* Object value = */ expr.execute(ctxt);
1007         assertEquals(new BigDecimal("42.0e+1"), ctxt.get("a"));
1008         assertEquals(new BigDecimal("42.0e+2"), ctxt.get("b"));
1009         assertEquals(new BigDecimal("42.0e-1"), ctxt.get("c"));
1010         assertEquals(new BigDecimal("42.0e-2"), ctxt.get("d"));
1011         assertEquals(new BigDecimal("4242.4242e1"), ctxt.get("e"));
1012     }
1013 
1014     @Test
1015     void testBigInteger() throws Exception {
1016         asserter.setVariable("left", new BigInteger("2"));
1017         asserter.setVariable("right", new BigInteger("6"));
1018         asserter.assertExpression("left + right", new BigInteger("8"));
1019         asserter.assertExpression("right - left", new BigInteger("4"));
1020         asserter.assertExpression("right * left", new BigInteger("12"));
1021         asserter.assertExpression("right / left", new BigInteger("3"));
1022         asserter.assertExpression("right % left", new BigInteger("0"));
1023     }
1024 
1025     // JEXL-24: big integers and big decimals
1026     @Test
1027     void testBigLiterals() {
1028         final JexlEvalContext ctxt = new JexlEvalContext();
1029         final JexlOptions options = ctxt.getEngineOptions();
1030         options.setStrictArithmetic(true);
1031         final String stmt = "{a = 10H; b = 10h; c = 42.0B; d = 42.0b;}";
1032         final JexlScript expr = JEXL.createScript(stmt);
1033         /* Object value = */ expr.execute(ctxt);
1034         assertEquals(new BigInteger("10"), ctxt.get("a"));
1035         assertEquals(new BigInteger("10"), ctxt.get("b"));
1036         assertEquals(new BigDecimal("42.0"), ctxt.get("c"));
1037         assertEquals(new BigDecimal("42.0"), ctxt.get("d"));
1038     }
1039 
1040     @Test
1041     void testBigLiteralValue() {
1042         final JexlEvalContext ctxt = new JexlEvalContext();
1043         final JexlOptions options = ctxt.getEngineOptions();
1044         options.setStrictArithmetic(true);
1045         final JexlExpression e = JEXL.createExpression("9223372036854775806.5B");
1046         final String res = String.valueOf(e.evaluate(ctxt));
1047         assertEquals("9223372036854775806.5", res);
1048     }
1049 
1050     /**
1051    * test some simple mathematical calculations
1052    */
1053     @Test
1054     void testCalculations() throws Exception {
1055         asserter.setStrict(true, false);
1056         /*
1057          * test new null coersion
1058          */
1059         asserter.setVariable("imanull", null);
1060         asserter.assertExpression("imanull + 2", Integer.valueOf(2));
1061         asserter.assertExpression("imanull + imanull", Integer.valueOf(0));
1062         asserter.setVariable("foo", Integer.valueOf(2));
1063 
1064         asserter.assertExpression("foo + 2", Integer.valueOf(4));
1065         asserter.assertExpression("3 + 3", Integer.valueOf(6));
1066         asserter.assertExpression("3 + 3 + foo", Integer.valueOf(8));
1067         asserter.assertExpression("3 * 3", Integer.valueOf(9));
1068         asserter.assertExpression("3 * 3 + foo", Integer.valueOf(11));
1069         asserter.assertExpression("3 * 3 - foo", Integer.valueOf(7));
1070 
1071         /*
1072          * test parenthesized exprs
1073          */
1074         asserter.assertExpression("(4 + 3) * 6", Integer.valueOf(42));
1075         asserter.assertExpression("(8 - 2) * 7", Integer.valueOf(42));
1076 
1077         /*
1078          * test some floaty stuff
1079          */
1080         asserter.assertExpression("3 * \"3.0\"", Double.valueOf(9));
1081         asserter.assertExpression("3 * 3.0", Double.valueOf(9));
1082 
1083         /*
1084          * test / and %
1085          */
1086         asserter.setStrict(false, false);
1087         asserter.assertExpression("6 / 3", Integer.valueOf(6 / 3));
1088         asserter.assertExpression("6.4 / 3", Double.valueOf(6.4 / 3));
1089         asserter.assertExpression("0 / 3", Integer.valueOf(0 / 3));
1090         asserter.assertExpression("3 / 0", Double.valueOf(0));
1091         asserter.assertExpression("4 % 3", Integer.valueOf(1));
1092         asserter.assertExpression("4.8 % 3", Double.valueOf(4.8 % 3));
1093 
1094     }
1095 
1096     @Test
1097     void testCoerceBigDecimal() {
1098         final JexlArithmetic ja = JEXL.getArithmetic();
1099         final JexlEvalContext ctxt = new JexlEvalContext();
1100         final JexlOptions options = ctxt.getEngineOptions();
1101         options.setStrictArithmetic(true);
1102         final String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
1103         final JexlScript expr = JEXL.createScript(stmt);
1104         /* Object value = */ expr.execute(ctxt);
1105         assertEquals(BigDecimal.valueOf(34), ja.toBigDecimal(ctxt.get("a")));
1106         assertEquals(BigDecimal.valueOf(45.), ja.toBigDecimal(ctxt.get("b")));
1107         assertEquals(BigDecimal.valueOf(56.), ja.toBigDecimal(ctxt.get("c")));
1108         assertEquals(BigDecimal.valueOf(67), ja.toBigDecimal(ctxt.get("d")));
1109         assertEquals(BigDecimal.valueOf(78), ja.toBigDecimal(ctxt.get("e")));
1110         assertEquals(BigDecimal.valueOf(10), ja.toBigDecimal("10"));
1111         assertEquals(BigDecimal.valueOf(1.), ja.toBigDecimal(true));
1112         assertEquals(BigDecimal.valueOf(0.), ja.toBigDecimal(false));
1113         // BigDecimal precision is kept when used as argument
1114         final BigDecimal a42 = BigDecimal.valueOf(42);
1115         final BigDecimal a49 = BigDecimal.valueOf(49);
1116         JexlScript bde = JEXL.createScript("a * 6 / 7", "a");
1117         assertEquals(a42, bde.execute(null, a49));
1118         bde = JEXL.createScript("(a - 12) / 12", "a");
1119         final MathContext mc = ja.getMathContext();
1120         final BigDecimal b56 = BigDecimal.valueOf(56);
1121         final BigDecimal b12 = BigDecimal.valueOf(12);
1122         final BigDecimal b3dot666 = b56.subtract(b12, mc).divide(b12, mc);
1123         assertEquals(b3dot666, bde.execute(null, b56));
1124     }
1125 
1126     @Test
1127     void testCoerceBigInteger() {
1128         final JexlArithmetic ja = JEXL.getArithmetic();
1129         final JexlEvalContext ctxt = new JexlEvalContext();
1130         final JexlOptions options = ctxt.getEngineOptions();
1131         options.setStrictArithmetic(true);
1132         final String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
1133         final JexlScript expr = JEXL.createScript(stmt);
1134         /* Object value = */ expr.execute(ctxt);
1135         assertEquals(BigInteger.valueOf(34), ja.toBigInteger(ctxt.get("a")));
1136         assertEquals(BigInteger.valueOf(45), ja.toBigInteger(ctxt.get("b")));
1137         assertEquals(BigInteger.valueOf(56), ja.toBigInteger(ctxt.get("c")));
1138         assertEquals(BigInteger.valueOf(67), ja.toBigInteger(ctxt.get("d")));
1139         assertEquals(BigInteger.valueOf(78), ja.toBigInteger(ctxt.get("e")));
1140         assertEquals(BigInteger.valueOf(10), ja.toBigInteger("10"));
1141         assertEquals(BigInteger.valueOf(1), ja.toBigInteger(true));
1142         assertEquals(BigInteger.valueOf(0), ja.toBigInteger(false));
1143     }
1144 
1145     @Test
1146     void testCoerceDouble() {
1147         final JexlArithmetic ja = JEXL.getArithmetic();
1148         final JexlEvalContext ctxt = new JexlEvalContext();
1149         final JexlOptions options = ctxt.getEngineOptions();
1150         options.setStrictArithmetic(true);
1151         final String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
1152         final JexlScript expr = JEXL.createScript(stmt);
1153         /* Object value = */ expr.execute(ctxt);
1154         assertEquals(34, ja.toDouble(ctxt.get("a")), EPSILON);
1155         assertEquals(45, ja.toDouble(ctxt.get("b")), EPSILON);
1156         assertEquals(56, ja.toDouble(ctxt.get("c")), EPSILON);
1157         assertEquals(67, ja.toDouble(ctxt.get("d")), EPSILON);
1158         assertEquals(78, ja.toDouble(ctxt.get("e")), EPSILON);
1159         assertEquals(10d, ja.toDouble("10"), EPSILON);
1160         assertEquals(1.D, ja.toDouble(true), EPSILON);
1161         assertEquals(0.D, ja.toDouble(false), EPSILON);
1162     }
1163 
1164     @Test
1165     void testCoerceInteger() {
1166         final JexlArithmetic ja = JEXL.getArithmetic();
1167         final JexlEvalContext ctxt = new JexlEvalContext();
1168         final JexlOptions options = ctxt.getEngineOptions();
1169         options.setStrictArithmetic(true);
1170         final String stmt = "a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H;";
1171         final JexlScript expr = JEXL.createScript(stmt);
1172         /* Object value = */ expr.execute(ctxt);
1173         assertEquals(34, ja.toInteger(ctxt.get("a")));
1174         assertEquals(45, ja.toInteger(ctxt.get("b")));
1175         assertEquals(56, ja.toInteger(ctxt.get("c")));
1176         assertEquals(67, ja.toInteger(ctxt.get("d")));
1177         assertEquals(78, ja.toInteger(ctxt.get("e")));
1178         assertEquals(10, ja.toInteger("10"));
1179         assertEquals(1, ja.toInteger(true));
1180         assertEquals(0, ja.toInteger(false));
1181     }
1182 
1183     @Test
1184     void testCoerceLong() {
1185         final JexlArithmetic ja = JEXL.getArithmetic();
1186         final JexlEvalContext ctxt = new JexlEvalContext();
1187         final JexlOptions options = ctxt.getEngineOptions();
1188         options.setStrictArithmetic(true);
1189         final String stmt = "a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H;";
1190         final JexlScript expr = JEXL.createScript(stmt);
1191         /* Object value = */ expr.execute(ctxt);
1192         assertEquals(34L, ja.toLong(ctxt.get("a")));
1193         assertEquals(45L, ja.toLong(ctxt.get("b")));
1194         assertEquals(56L, ja.toLong(ctxt.get("c")));
1195         assertEquals(67L, ja.toLong(ctxt.get("d")));
1196         assertEquals(78L, ja.toLong(ctxt.get("e")));
1197         assertEquals(10L, ja.toLong("10"));
1198         assertEquals(1L, ja.toLong(true));
1199         assertEquals(0L, ja.toLong(false));
1200     }
1201 
1202     @Test
1203     void testCoercions() throws Exception {
1204         asserter.assertExpression("1", Integer.valueOf(1)); // numerics default to Integer
1205         asserter.assertExpression("5L", Long.valueOf(5));
1206 
1207         asserter.setVariable("I2", Integer.valueOf(2));
1208         asserter.setVariable("L2", Long.valueOf(2));
1209         asserter.setVariable("L3", Long.valueOf(3));
1210         asserter.setVariable("B10", BigInteger.TEN);
1211 
1212         // Integer & Integer => Integer
1213         asserter.assertExpression("I2 + 2", Integer.valueOf(4));
1214         asserter.assertExpression("I2 * 2", Integer.valueOf(4));
1215         asserter.assertExpression("I2 - 2", Integer.valueOf(0));
1216         asserter.assertExpression("I2 / 2", Integer.valueOf(1));
1217 
1218         // Integer & Long => Long
1219         asserter.assertExpression("I2 * L2", Long.valueOf(4));
1220         asserter.assertExpression("I2 / L2", Long.valueOf(1));
1221 
1222         // Long & Long => Long
1223         asserter.assertExpression("L2 + 3", Long.valueOf(5));
1224         asserter.assertExpression("L2 + L3", Long.valueOf(5));
1225         asserter.assertExpression("L2 / L2", Long.valueOf(1));
1226         asserter.assertExpression("L2 / 2", Long.valueOf(1));
1227 
1228         // BigInteger
1229         asserter.assertExpression("B10 / 10", BigInteger.ONE);
1230         asserter.assertExpression("B10 / I2", new BigInteger("5"));
1231         asserter.assertExpression("B10 / L2", new BigInteger("5"));
1232     }
1233 
1234     @Test
1235     void testCompare() {
1236         // JEXL doesn't support more than one operator in the same expression, for example: 1 == 1 == 1
1237         final Object[] EXPRESSIONS = {
1238                 // Basic compare
1239                 "1 == 1", true,
1240                 "1 === 1", true,
1241                 "1.d === 1", false,
1242                 "1 != 1", false,
1243                 "1 != 2", true,
1244                 "1 > 2", false,
1245                 "1 >= 2", false,
1246                 "1 < 2", true,
1247                 "1 <= 2", true,
1248                 // Int <-> Float Coercion
1249                 "1.0 == 1", true,
1250                 "1 == 1.0", true,
1251                 "1.1 != 1", true,
1252                 "1.1 < 2", true,
1253                 // Big Decimal <-> Big Integer Coercion
1254                 "1.0b == 1h", true,
1255                 "1.0b !== 1h", true,
1256                 "1h == 1.0b", true,
1257                 "1.1b != 1h", true,
1258                 "1.1b < 2h", true,
1259                 // Mix all type of numbers
1260                 "1l == 1.0", true, // long and int
1261                 "1l !== 1.0", true, // long and int
1262                 "1.0d == 1.0f", true, // double and float
1263                 "1l == 1.0b", true,
1264                 "1l == 1h", true,
1265                 "1.0d == 1.0b", true,
1266                 "1.0f == 1.0b", true,
1267                 "1.0d == 1h", true,
1268                 "1.0f == 1h", true,
1269                 // numbers and strings
1270                 "'1' == 1", true,
1271                 "'1' == 1l", true,
1272                 "'1' == 1h", true,
1273                 "'' == 0", true, // empty string is coerced to zero (ECMA compliance)
1274                 "'1.0' == 1", true,
1275                 "'1.0' == 1.0f", true,
1276                 "'1.0' == 1.0d", true,
1277                 "'1.0' == 1.0b", true,
1278                 "'1.01' == 1.01", true,
1279                 "'1.01' == 1", false,
1280                 "'1.01' == 1b", false,
1281                 "'1.01' == 1h", false,
1282                 "'1.00001' == 1b", false,
1283                 "'1.00001' == 1h", false,
1284                 "'1.00000001' == 1", false,
1285                 "'1.00000001' == 1b", false,
1286                 "'1.00000001' == 1h", false,
1287                 "1.0 >= '1'", true,
1288                 "1.0 > '1'", false,
1289                 "1.0 == 'a'", false,
1290                 "10 == 'a'", false,
1291                 "10 > 'a'", ArithmeticException.class,
1292                 "10.0 > 'a'", ArithmeticException.class,
1293                 "'a' <= 10b", ArithmeticException.class,
1294                 "'a' >= 1h", ArithmeticException.class
1295         };
1296         final JexlEngine jexl = new JexlBuilder().create();
1297         final JexlContext jc = new EmptyTestContext();
1298 
1299         for (int e = 0; e < EXPRESSIONS.length; e += 2) {
1300             final String stext = (String) EXPRESSIONS[e];
1301             final Object expected = EXPRESSIONS[e + 1];
1302             final JexlExpression expression = jexl.createExpression(stext);
1303             try {
1304                 final Object result = expression.evaluate(jc);
1305                 assertEquals(expected, result, () -> "failed on " + stext);
1306             } catch (final JexlException jexlException) {
1307                 final Throwable cause = jexlException.getCause();
1308                 if (cause == null || !cause.getClass().equals(expected)) {
1309                     fail(stext);
1310                 }
1311             }
1312         }
1313     }
1314 
1315     @Test
1316     void testDivClass() {
1317         final JexlEngine jexl = new JexlBuilder().create();
1318         final JexlContext jc = new MapContext();
1319         final Object ra = jexl.createExpression("463.0d / 0.1").evaluate(jc);
1320         assertEquals(Double.class, ra.getClass());
1321         final Object r0 = jexl.createExpression("463.0B / 0.1").evaluate(jc);
1322         assertEquals(java.math.BigDecimal.class, r0.getClass());
1323         final Object r1 = jexl.createExpression("463.0B / 0.1B").evaluate(jc);
1324         assertEquals(java.math.BigDecimal.class, r1.getClass());
1325     }
1326 
1327     /**
1328      *
1329      * if silent, all arith exception return 0.0
1330      * if not silent, all arith exception throw
1331      */
1332     @Test
1333     void testDivideByZero() throws Exception {
1334         final Map<String, Object> vars = new HashMap<>();
1335         final JexlEvalContext context = new JexlEvalContext(vars);
1336         final JexlOptions options = context.getEngineOptions();
1337         options.setStrictArithmetic(true);
1338         vars.put("aByte", Byte.valueOf((byte) 1));
1339         vars.put("aShort", Short.valueOf((short) 2));
1340         vars.put("aInteger", Integer.valueOf(3));
1341         vars.put("aLong", Long.valueOf(4));
1342         vars.put("aFloat", Float.valueOf((float) 5.5));
1343         vars.put("aDouble", Double.valueOf(6.6));
1344         vars.put("aBigInteger", new BigInteger("7"));
1345         vars.put("aBigDecimal", new BigDecimal("8.8"));
1346 
1347         vars.put("zByte", Byte.valueOf((byte) 0));
1348         vars.put("zShort", Short.valueOf((short) 0));
1349         vars.put("zInteger", Integer.valueOf(0));
1350         vars.put("zLong", Long.valueOf(0));
1351         vars.put("zFloat", Float.valueOf(0));
1352         vars.put("zDouble", Double.valueOf(0));
1353         vars.put("zBigInteger", new BigInteger("0"));
1354         vars.put("zBigDecimal", new BigDecimal("0"));
1355 
1356         final String[] tnames = {
1357             "Byte", "Short", "Integer", "Long",
1358             "Float", "Double",
1359             "BigInteger", "BigDecimal"
1360         };
1361         // number of permutations this will generate
1362         final int PERMS = tnames.length * tnames.length;
1363 
1364         final JexlEngine jexl = JEXL;
1365         // for non-silent, silent...
1366         for (int s = 0; s < 2; ++s) {
1367             final boolean strict = s != 0;
1368             options.setStrict(true);
1369             options.setStrictArithmetic(strict);
1370             int zthrow = 0;
1371             int zeval = 0;
1372             // for vars of all types...
1373             for (final String vname : tnames) {
1374                 // for zeros of all types...
1375                 for (final String zname : tnames) {
1376                     // divide var by zero
1377                     final String expr = "a" + vname + " / " + "z" + zname;
1378                     try {
1379                         final JexlExpression zexpr = jexl.createExpression(expr);
1380                         final Object nan = zexpr.evaluate(context);
1381                         // check we have a zero & increment zero count
1382                         if (nan instanceof Number) {
1383                             final double zero = ((Number) nan).doubleValue();
1384                             if (zero == 0.0) {
1385                                 zeval += 1;
1386                             }
1387                         }
1388                     } catch (final Exception any) {
1389                         // increment the exception count
1390                         zthrow += 1;
1391                     }
1392                 }
1393             }
1394             if (strict) {
1395                 assertEquals(zthrow, PERMS, "All expressions should have thrown " + zthrow + "/" + PERMS);
1396             } else {
1397                 assertEquals(zeval, PERMS, "All expressions should have zeroed " + zeval + "/" + PERMS);
1398             }
1399         }
1400         debuggerCheck(jexl);
1401     }
1402 
1403     @Test
1404     void testDivideEdges() {
1405         assertNullOperand(() -> jexla.divide(null, null));
1406         assertEquals(0, jexlb.divide(null, null));
1407         assertNullOperand(() -> jexla.divide(null, null));
1408         assertEquals(0, jexlb.mod(null, null));
1409         assertArithmeticException(() -> jexla.divide(1, 0));
1410         assertArithmeticException(() -> jexla.divide(1L, 0L));
1411         assertArithmeticException(() -> jexla.divide(1f, 0f));
1412         assertArithmeticException(() -> jexla.divide(1d, 0d));
1413         assertArithmeticException(() -> jexla.divide(BigInteger.ONE, BigInteger.ZERO));
1414         assertArithmeticException(() -> jexla.divide(BigInteger.ONE, BigDecimal.ZERO));
1415     }
1416 
1417     @Test
1418     void testEmpty() throws Exception {
1419         final Object[] SCRIPTS = {
1420             "var x = null; log('x = %s', x);", 0,
1421             "var x = 'abc'; log('x = %s', x);", 1,
1422             "var x = 333; log('x = %s', x);", 1,
1423             "var x = [1, 2]; log('x = %s', x);", 2,
1424             "var x = ['a', 'b']; log('x = %s', x);", 2,
1425             "var x = {1:'A', 2:'B'}; log('x = %s', x);", 1,
1426             "var x = null; return empty(x);", true,
1427             "var x = ''; return empty(x);", true,
1428             "var x = 'abc'; return empty(x);", false,
1429             "var x = 0; return empty(x);", true,
1430             "var x = 333; return empty(x);", false,
1431             "var x = []; return empty(x);", true,
1432             "var x = [1, 2]; return empty(x);", false,
1433             "var x = ['a', 'b']; return empty(x);", false,
1434             "var x = [...]; return empty(x);", true,
1435             "var x = [1, 2,...]; return empty(x);", false,
1436             "var x = {:}; return empty(x);", true,
1437             "var x = {1:'A', 2:'B'}; return empty(x);", false,
1438             "var x = {}; return empty(x);", true,
1439             "var x = {'A','B'}; return empty(x);", false
1440         };
1441         final JexlEngine jexl = new JexlBuilder().create();
1442         final JexlContext jc = new EmptyTestContext();
1443         JexlScript script;
1444 
1445         for (int e = 0; e < SCRIPTS.length; e += 2) {
1446             final String stext = (String) SCRIPTS[e];
1447             final Object expected = SCRIPTS[e + 1];
1448             script = jexl.createScript(stext);
1449             final Object result = script.execute(jc);
1450             assertEquals(expected, result, () -> "failed on " + stext);
1451         }
1452     }
1453 
1454     /**
1455      * A strict engine must surface argument-evaluation errors from {@code empty()}/{@code size()}
1456      * instead of masking them as {@code true}/{@code 0} (JEXL-security f028); a lenient engine keeps
1457      * the historical empty/0 fallback.
1458      */
1459     @Test
1460     void testEmptySizeStrictPropagatesError() {
1461         final JexlEngine strict = new JexlBuilder().strict(true).safe(false).create();
1462         assertThrows(JexlException.class, () -> strict.createScript("empty(x.y)", "x").execute(null, (Object) null));
1463         assertThrows(JexlException.class, () -> strict.createScript("size(x.y)", "x").execute(null, (Object) null));
1464         final JexlEngine lenient = new JexlBuilder().strict(false).safe(true).create();
1465         assertEquals(Boolean.TRUE, lenient.createScript("empty(x.y)", "x").execute(null, (Object) null));
1466         assertEquals(0, lenient.createScript("size(x.y)", "x").execute(null, (Object) null));
1467     }
1468 
1469     /**
1470      * Cancellation during {@code empty()}/{@code size()} argument evaluation must never be masked
1471      * as empty/0, even in a lenient engine (JEXL-security f028).
1472      */
1473     @Test
1474     void testEmptySizeDoNotSwallowCancel() {
1475         final JexlEngine jexl = new JexlBuilder().strict(false).cancellable(true).create();
1476         final JexlScript empty = jexl.createScript("empty(x)", "x");
1477         final JexlScript size = jexl.createScript("size(x)", "x");
1478         try {
1479             Thread.currentThread().interrupt();
1480             assertThrows(JexlException.Cancel.class, () -> empty.execute(null, "abc"));
1481         } finally {
1482             Thread.interrupted(); // clear the interrupted status so it does not leak into other tests
1483         }
1484         try {
1485             Thread.currentThread().interrupt();
1486             assertThrows(JexlException.Cancel.class, () -> size.execute(null, "abc"));
1487         } finally {
1488             Thread.interrupted();
1489         }
1490     }
1491 
1492     @Test
1493     void testEmptyDouble()  {
1494         Object x;
1495         x = JEXL.createScript("4294967296.d").execute(null);
1496         assertEquals(4294967296.0d, (Double) x, EPSILON);
1497         checkEmpty(x, false);
1498         x = JEXL.createScript("4294967296.0d").execute(null);
1499         assertEquals(4294967296.0d, (Double) x, EPSILON);
1500         checkEmpty(x, false);
1501         x = JEXL.createScript("0.0d").execute(null);
1502         assertEquals(0.0d, (Double) x, EPSILON);
1503         checkEmpty(x, true);
1504         x = Double.NaN;
1505         checkEmpty(x, true);
1506 
1507     }
1508 
1509     @Test
1510     void testEmptyFloat()  {
1511         Object x;
1512         x = JEXL.createScript("4294967296.f").execute(null);
1513         assertEquals(4294967296.0f, (Float) x, EPSILON);
1514         checkEmpty(x, false);
1515         x = JEXL.createScript("4294967296.0f").execute(null);
1516         assertEquals(4294967296.0f, (Float) x, EPSILON);
1517         checkEmpty(x, false);
1518         x = JEXL.createScript("0.0f").execute(null);
1519         assertEquals(0.0f, (Float) x, EPSILON);
1520         checkEmpty(x, true);
1521         x = Float.NaN;
1522         checkEmpty(x, true);
1523     }
1524 
1525     @Test
1526     void testEmptyLong()  {
1527         Object x;
1528         x = JEXL.createScript("new('java.lang.Long', 4294967296)").execute(null);
1529         assertEquals(4294967296L, ((Long) x).longValue());
1530         x = JEXL.createScript("new Long(4294967296)").execute(null);
1531         assertEquals(4294967296L, ((Long) x).longValue());
1532         x = JEXL.createScript("new('java.lang.Long', '4294967296')").execute(null);
1533         assertEquals(4294967296L, ((Long) x).longValue());
1534         x = JEXL.createScript("4294967296l").execute(null);
1535         assertEquals(4294967296L, ((Long) x).longValue());
1536         x = JEXL.createScript("4294967296L").execute(null);
1537         assertEquals(4294967296L, ((Long) x).longValue());
1538         checkEmpty(x, false);
1539         x = JEXL.createScript("0L").execute(null);
1540         assertEquals(0, ((Long) x).longValue());
1541         checkEmpty(x, true);
1542     }
1543 
1544     @Test
1545     void testFailAllOperators() {
1546         // @formatter:off
1547         final String[] scripts = {
1548             "(x, y)->{ x < y }",
1549             "(x, y)->{ x <= y }",
1550             "(x, y)->{ x > y }",
1551             "(x, y)->{ x >= y }",
1552             "(x, y)->{ x == y }",
1553             "(x, y)->{ x != y }",
1554             "(x, y)->{ x === y }",
1555             "(x, y)->{ x !== y }",
1556             "(x, y)->{ x % y }",
1557             "(x, y)->{ x * y }",
1558             "(x, y)->{ x + y }",
1559             "(x, y)->{ x - y }",
1560             "(x, y)->{ x ^ y }",
1561             "(x, y)->bitwiseXor(x,y)",
1562             "(x, y)->{ x || y }",
1563             "(x, y)->{ x | y }",
1564             "(x, y)->bitwiseOr(x,y)",
1565             "(x, y)->{ x << y }",
1566             "(x, y)->{ x >> y }",
1567             "(x, y)->{ x >>> y }",
1568             "(x, y)->{ x & y }",
1569             "(x, y)->{ x && y }",
1570             "(x, y)->bitwiseAnd(x,y)",
1571             "(x, y)->{ x =^ y }",
1572             "(x, y)->{ x !^ y }",
1573             "(x, y)->{ x =$ y }",
1574             "(x, y)->{ x !$ y }",
1575             "(x, y)->{ x =~ y }",
1576             "(x, y)->{ x !~ y }",
1577             "(x, ignore)->{ -x }",
1578             "(x, ignore)->{ +x }",
1579             "(x, ignore)->{ --x }",
1580             "(x, ignore)->{ ++x }",
1581             "(x, ignore)->{ x-- }",
1582             "(x, ignore)->{ x++ }"
1583         };
1584         // @formatter:on
1585         final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new ArithmeticFail(true)).create();
1586         final JexlContext jc = new EmptyTestContext();
1587         for (final String src : scripts) {
1588             final JexlScript script = jexl.createScript(src);
1589             assertThrows(JexlException.class, () -> script.execute(jc, new Var(42), new Var(43)));
1590         }
1591     }
1592 
1593     @Test
1594     void testInfiniteArithmetic() {
1595         final Map<String, Object> ns = new HashMap<>();
1596         ns.put("math", Math.class);
1597         final JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic132()).namespaces(ns).create();
1598 
1599         Object evaluate = jexl.createExpression("1/0").evaluate(null);
1600         assertTrue(Double.isInfinite((Double) evaluate));
1601 
1602         evaluate = jexl.createExpression("-1/0").evaluate(null);
1603         assertTrue(Double.isInfinite((Double) evaluate));
1604 
1605         evaluate = jexl.createExpression("1.0/0.0").evaluate(null);
1606         assertTrue(Double.isInfinite((Double) evaluate));
1607 
1608         evaluate = jexl.createExpression("-1.0/0.0").evaluate(null);
1609         assertTrue(Double.isInfinite((Double) evaluate));
1610 
1611         evaluate = jexl.createExpression("math:abs(-42)").evaluate(null);
1612         assertEquals(42, evaluate);
1613 
1614         evaluate =  jexl.createExpression("42B / 7").evaluate(null);
1615         assertEquals(6, evaluate);
1616 
1617         evaluate =  jexl.createExpression("42.7B / 7").evaluate(null);
1618         assertEquals(BigDecimal.valueOf(6.1d), evaluate);
1619     }
1620 
1621     @Test
1622     void testInstanceOf0() {
1623         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create();
1624         final JexlContext ctxt = new InstanceofContext();
1625         runInstanceof(jexl, ctxt);
1626     }
1627 
1628     @Test
1629     void testInstanceOf1() {
1630         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).imports("java.lang").create();
1631         runInstanceof(jexl, null);
1632     }
1633 
1634     @Test
1635     void testIntegerCoercionEdges() {
1636         assertNullOperand(() -> jexla.toBoolean(null));
1637         assertTrue(jexla.toBoolean(date));
1638         // int coercions
1639         assertNullOperand(() -> jexla.toInteger(null));
1640         assertEquals(0, jexlb.toInteger(null));
1641         assertArithmeticException(() -> jexla.toInteger(date));
1642         assertEquals(0, jexla.toInteger(Double.NaN));
1643         assertEquals(0, jexla.toInteger(""));
1644         assertEquals('b', jexla.toInteger('b'));
1645         assertEquals(1, jexla.toInteger(new AtomicBoolean(true)));
1646         assertEquals(0, jexla.toInteger(new AtomicBoolean()));
1647 
1648         // long coercions
1649         assertNullOperand(() -> jexla.toLong(null));
1650         assertEquals(0L, jexlb.toLong(null));
1651         assertArithmeticException(() -> jexla.toLong(date));
1652         assertEquals(0L, jexla.toLong(Double.NaN));
1653         assertEquals(0L, jexla.toLong(""));
1654         assertEquals('b', jexla.toLong('b'));
1655         assertEquals(1L, jexla.toLong(new AtomicBoolean(true)));
1656         assertEquals(0L, jexla.toLong(new AtomicBoolean()));
1657     }
1658 
1659     @Test
1660     void testIsFloatingPointPattern() {
1661         final JexlArithmetic ja = new JexlArithmetic(true);
1662 
1663         assertFalse(ja.isFloatingPointNumber("floating point"));
1664         assertFalse(ja.isFloatingPointNumber("a1."));
1665         assertFalse(ja.isFloatingPointNumber("b1.2"));
1666         assertFalse(ja.isFloatingPointNumber("-10.2a-34"));
1667         assertFalse(ja.isFloatingPointNumber("+10.2a+34"));
1668         assertFalse(ja.isFloatingPointNumber("0"));
1669         assertFalse(ja.isFloatingPointNumber("1"));
1670         assertFalse(ja.isFloatingPointNumber("12A"));
1671         assertFalse(ja.isFloatingPointNumber("2F3"));
1672         assertFalse(ja.isFloatingPointNumber("23"));
1673         assertFalse(ja.isFloatingPointNumber("+3"));
1674         assertFalse(ja.isFloatingPointNumber("+34"));
1675         assertFalse(ja.isFloatingPointNumber("+3-4"));
1676         assertFalse(ja.isFloatingPointNumber("+3.-4"));
1677         assertFalse(ja.isFloatingPointNumber("3ee4"));
1678 
1679         assertTrue(ja.isFloatingPointNumber("0."));
1680         assertTrue(ja.isFloatingPointNumber("1."));
1681         assertTrue(ja.isFloatingPointNumber("1.2"));
1682         assertTrue(ja.isFloatingPointNumber("1.2e3"));
1683         assertTrue(ja.isFloatingPointNumber("2e3"));
1684         assertTrue(ja.isFloatingPointNumber("+2e-3"));
1685         assertTrue(ja.isFloatingPointNumber("+23E-34"));
1686         assertTrue(ja.isFloatingPointNumber("+23.E-34"));
1687         assertTrue(ja.isFloatingPointNumber("-23.4E+45"));
1688         assertTrue(ja.isFloatingPointNumber("1.2e34"));
1689         assertTrue(ja.isFloatingPointNumber("10.2e34"));
1690         assertTrue(ja.isFloatingPointNumber("+10.2e34"));
1691         assertTrue(ja.isFloatingPointNumber("-10.2e34"));
1692         assertTrue(ja.isFloatingPointNumber("10.2e-34"));
1693         assertTrue(ja.isFloatingPointNumber("10.2e+34"));
1694         assertTrue(ja.isFloatingPointNumber("-10.2e-34"));
1695         assertTrue(ja.isFloatingPointNumber("+10.2e+34"));
1696         assertTrue(ja.isFloatingPointNumber("-10.2E-34"));
1697         assertTrue(ja.isFloatingPointNumber("+10.2E+34"));
1698     }
1699 
1700     @Test
1701     void testJexl173() {
1702         final JexlEngine jexl = new JexlBuilder().create();
1703         final JexlContext jc = new MapContext();
1704         final Callable173 c173 = new Callable173();
1705         JexlScript e = jexl.createScript("c173(9, 6)", "c173");
1706         Object result = e.execute(jc, c173);
1707         assertEquals(54, result);
1708         e = jexl.createScript("c173('fourty', 'two')", "c173");
1709         result = e.execute(jc, c173);
1710         assertEquals(42, result);
1711 
1712     }
1713 
1714     @Test
1715     void testLeftNullOperand() throws Exception {
1716         asserter.setVariable("left", null);
1717         asserter.setVariable("right", Integer.valueOf(8));
1718         asserter.setStrict(true);
1719         asserter.failExpression("left + right", ".*null.*");
1720         asserter.failExpression("left - right", ".*null.*");
1721         asserter.failExpression("left * right", ".*null.*");
1722         asserter.failExpression("left / right", ".*null.*");
1723         asserter.failExpression("left % right", ".*null.*");
1724         asserter.failExpression("left & right", ".*null.*");
1725         asserter.failExpression("left | right", ".*null.*");
1726         asserter.failExpression("left ^ right", ".*null.*");
1727         asserter.failExpression("left < right", ".*null.*");
1728         asserter.failExpression("left <= right", ".*null.*");
1729         asserter.failExpression("left > right", ".*null.*");
1730         asserter.failExpression("left >= right", ".*null.*");
1731     }
1732 
1733     @Test
1734     void testLeftNullOperand2() throws Exception {
1735         asserter.setVariable("x.left", null);
1736         asserter.setVariable("right", Integer.valueOf(8));
1737         asserter.setStrict(true);
1738         asserter.failExpression("x.left + right", ".*null.*");
1739         asserter.failExpression("x.left - right", ".*null.*");
1740         asserter.failExpression("x.left * right", ".*null.*");
1741         asserter.failExpression("x.left / right", ".*null.*");
1742         asserter.failExpression("x.left % right", ".*null.*");
1743         asserter.failExpression("x.left & right", ".*null.*");
1744         asserter.failExpression("x.left | right", ".*null.*");
1745         asserter.failExpression("x.left ^ right", ".*null.*");
1746         asserter.failExpression("x.left < right", ".*null.*");
1747         asserter.failExpression("x.left <= right", ".*null.*");
1748         asserter.failExpression("x.left > right", ".*null.*");
1749         asserter.failExpression("x.left >= right", ".*null.*");
1750     }
1751 
1752     // JEXL-24: long integers (and doubles)
1753     @Test
1754     void testLongLiterals() {
1755         final JexlEvalContext ctxt = new JexlEvalContext();
1756         final JexlOptions options = ctxt.getEngineOptions();
1757         options.setStrictArithmetic(true);
1758         final String stmt = "{a = 10L; b = 10l; c = 42.0D; d = 42.0d; e=56.3F; f=56.3f; g=63.5; h=0x10; i=010; j=0x10L; k=010l}";
1759         final JexlScript expr = JEXL.createScript(stmt);
1760         /* Object value = */ expr.execute(ctxt);
1761         assertEquals(10L, ctxt.get("a"));
1762         assertEquals(10L, ctxt.get("b"));
1763         assertEquals(42.0D, ctxt.get("c"));
1764         assertEquals(42.0d, ctxt.get("d"));
1765         assertEquals(56.3f, ctxt.get("e"));
1766         assertEquals(56.3f, ctxt.get("f"));
1767         assertEquals(63.5d, ctxt.get("g"));
1768         assertEquals(0x10, ctxt.get("h"));
1769         assertEquals(010, ctxt.get("i")); // octal 010
1770         assertEquals(0x10L, ctxt.get("j")); // hexa 010L
1771         assertEquals(010L, ctxt.get("k")); // octal 010L
1772     }
1773 
1774     @Test
1775     void testMinusClass() {
1776         final JexlEngine jexl = new JexlBuilder().create();
1777         final JexlContext jc = new MapContext();
1778         final Object ra = jexl.createExpression("463.0d - 0.1").evaluate(jc);
1779         assertEquals(Double.class, ra.getClass());
1780         final Object r0 = jexl.createExpression("463.0B - 0.1").evaluate(jc);
1781         assertEquals(java.math.BigDecimal.class, r0.getClass());
1782         final Object r1 = jexl.createExpression("463.0B - 0.1B").evaluate(jc);
1783         assertEquals(java.math.BigDecimal.class, r1.getClass());
1784     }
1785 
1786     @Test
1787     void testMinusMinusPostfix() throws Exception {
1788         asserter.setVariable("aByte", Byte.valueOf((byte) 2));
1789         asserter.setVariable("aShort", Short.valueOf((short) 3));
1790         asserter.setVariable("anInteger", Integer.valueOf(4));
1791         asserter.setVariable("aLong", Long.valueOf(5));
1792         asserter.setVariable("aFloat", Float.valueOf((float) 6.6));
1793         asserter.setVariable("aDouble", Double.valueOf(7.7));
1794         asserter.setVariable("aBigInteger", new BigInteger("8"));
1795         asserter.setVariable("aBigDecimal", new BigDecimal("9.9"));
1796         asserter.setVariable("aString", "forty-two");
1797 
1798         asserter.assertExpression("aByte--",Byte.valueOf((byte) 2));
1799         asserter.assertExpression("aShort--", Short.valueOf((short) 3));
1800         asserter.assertExpression("anInteger--", Integer.valueOf(4));
1801         asserter.assertExpression("aLong--", Long.valueOf(5));
1802         asserter.assertExpression("aFloat--", Float.valueOf((float) 6.6));
1803         asserter.assertExpression("aDouble--", Double.valueOf(7.7));
1804         asserter.assertExpression("aBigInteger--", new BigInteger("8"));
1805         asserter.assertExpression("aBigDecimal--", new BigDecimal("9.9"));
1806 
1807         asserter.assertExpression("aByte", Byte.valueOf((byte) 1));
1808         asserter.assertExpression("aShort", Short.valueOf((short) 2));
1809         asserter.assertExpression("anInteger", Integer.valueOf(3));
1810         asserter.assertExpression("aLong", Long.valueOf(4));
1811         asserter.assertExpression("aFloat", Float.valueOf((float) 5.6));
1812         asserter.assertExpression("aDouble", Double.valueOf(6.7));
1813         asserter.assertExpression("aBigInteger", new BigInteger("7"));
1814         asserter.assertExpression("aBigDecimal", new BigDecimal("8.9"));
1815 
1816         asserter.failExpression("aString--", "--", String::contains);
1817     }
1818 
1819     @Test
1820     void testMinusMinusPrefix() throws Exception {
1821         asserter.setVariable("aByte", Byte.valueOf((byte) 2));
1822         asserter.setVariable("aShort", Short.valueOf((short) 3));
1823         asserter.setVariable("anInteger", Integer.valueOf(4));
1824         asserter.setVariable("aLong", Long.valueOf(5));
1825         asserter.setVariable("aFloat", Float.valueOf((float) 6.6));
1826         asserter.setVariable("aDouble", Double.valueOf(7.7));
1827         asserter.setVariable("aBigInteger", new BigInteger("8"));
1828         asserter.setVariable("aBigDecimal", new BigDecimal("9.9"));
1829         asserter.setVariable("aString", "forty-two");
1830 
1831         asserter.assertExpression("--aByte", Byte.valueOf((byte) 1));
1832         asserter.assertExpression("--aShort", Short.valueOf((short) 2));
1833         asserter.assertExpression("--anInteger", Integer.valueOf(3));
1834         asserter.assertExpression("--aLong", Long.valueOf(4));
1835         asserter.assertExpression("--aFloat", Float.valueOf((float) 5.6));
1836         asserter.assertExpression("--aDouble", Double.valueOf(6.7));
1837         asserter.assertExpression("--aBigInteger", new BigInteger("7"));
1838         asserter.assertExpression("--aBigDecimal", new BigDecimal("8.9"));
1839 
1840         asserter.failExpression("aString--", "--", String::contains);
1841     }
1842 
1843     @Test
1844     void testModEdge() {
1845         assertNullOperand(() -> jexla.mod(null, null));
1846         assertEquals(0, jexlb.mod(null, null));
1847         assertArithmeticException(() -> jexla.mod(1, 0));
1848         assertArithmeticException(() -> jexla.mod(1L, 0L));
1849         assertArithmeticException(() -> jexla.mod(1f, 0f));
1850         assertArithmeticException(() -> jexla.mod(1d, 0d));
1851         assertArithmeticException(() -> jexla.mod(BigInteger.ONE, BigInteger.ZERO));
1852         assertArithmeticException(() -> jexla.mod(BigInteger.ONE, BigDecimal.ZERO));
1853         assertNullOperand(() -> jexla.divide(null, null));
1854     }
1855 
1856     /**
1857      * JEXL-156.
1858      */
1859     @Test
1860     void testMultClass(){
1861         final JexlEngine jexl = new JexlBuilder().create();
1862         final JexlContext jc = new MapContext();
1863         final Object ra = jexl.createExpression("463.0d * 0.1").evaluate(jc);
1864         assertEquals(Double.class, ra.getClass());
1865         final Object r0 = jexl.createExpression("463.0B * 0.1").evaluate(jc);
1866         assertEquals(java.math.BigDecimal.class, r0.getClass());
1867         final Object r1 = jexl.createExpression("463.0B * 0.1B").evaluate(jc);
1868         assertEquals(java.math.BigDecimal.class, r1.getClass());
1869     }
1870 
1871     @Test
1872     void testNaN() {
1873         final Map<String, Object> ns = new HashMap<>();
1874         ns.put("double", Double.class);
1875         final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
1876         JexlScript script;
1877         Object result;
1878         script = jexl.createScript("#NaN");
1879         result = script.execute(null);
1880         assertTrue(Double.isNaN((Double) result));
1881         script = jexl.createScript("NaN");
1882         result = script.execute(null);
1883         assertTrue(Double.isNaN((Double) result));
1884         script = jexl.createScript("double:isNaN(#NaN)");
1885         result = script.execute(null);
1886         assertTrue((Boolean) result);
1887         script = jexl.createScript("double:isNaN(NaN)");
1888         result = script.execute(null);
1889         assertTrue((Boolean) result);
1890     }
1891 
1892     @Test
1893     void testNarrowBigDecimal() throws Exception {
1894         final List<String> ls = Arrays.asList("zero", "one", "two");
1895         asserter.setVariable("list", ls);
1896         asserter.assertExpression("a -> list.get(a)", "zero", BigDecimal.ZERO);
1897         asserter.assertExpression("a -> list.get(a)", "one", BigDecimal.ONE);
1898         asserter.assertExpression("a -> list.get(2B)", "two");
1899         final BigDecimal bd42 = BigDecimal.valueOf(42);
1900         asserter.setVariable("bd10", BigDecimal.valueOf(10d));
1901         asserter.setVariable("bd420",BigDecimal.valueOf(420d));
1902         asserter.assertExpression("420 / bd10", bd42);
1903         asserter.assertExpression("420l / bd10", bd42);
1904         asserter.assertExpression("420H / bd10", bd42);
1905         asserter.assertExpression("bd420 / 10", bd42);
1906         asserter.assertExpression("bd420 / 10H", bd42);
1907         asserter.assertExpression("bd420 / 10B", bd42);
1908     }
1909 
1910     @Test
1911     void testNarrowBigInteger() throws Exception {
1912         final List<String> ls = Arrays.asList("zero", "one", "two");
1913         asserter.setVariable("list",ls);
1914         asserter.assertExpression("a -> list.get(a)", "zero", BigInteger.ZERO);
1915         asserter.assertExpression("a -> list.get(a)", "one", BigInteger.ONE);
1916         asserter.assertExpression("a -> list.get(2H)", "two");
1917         final BigInteger b42 = BigInteger.valueOf(42);
1918         asserter.setVariable("bi10", BigInteger.valueOf(10));
1919         asserter.setVariable("bi420", BigInteger.valueOf(420));
1920         asserter.assertExpression("420 / bi10", b42);
1921         asserter.assertExpression("420l / bi10", b42);
1922         asserter.assertExpression("bi420 / 420", BigInteger.ONE);
1923         asserter.assertExpression("bi420 / 420l", BigInteger.ONE);
1924         asserter.assertExpression("bi420 / 420H", BigInteger.ONE);
1925     }
1926 
1927     @Test
1928     void testNullArgs() {
1929         final JexlEngine jexl =  new JexlBuilder().arithmetic(new JexlArithmetic(true) {
1930             @Override public boolean isStrict(final JexlOperator op) {
1931                 return JexlOperator.ADD != op && super.isStrict(op);
1932             }
1933         }).create();
1934         final JexlScript script = jexl.createScript("'1.2' + x ", "x");
1935         final Object result = script.execute(null);
1936         assertEquals("1.2", result);
1937     }
1938 
1939     @Test
1940     void testNullOperand() throws Exception {
1941         asserter.setVariable("right", null);
1942         asserter.failExpression("~right", ".*null.*");
1943     }
1944 
1945     @Test
1946     void testNullOperands() throws Exception {
1947         asserter.setVariable("left", null);
1948         asserter.setVariable("right", null);
1949         asserter.failExpression("left + right", ".*null.*");
1950         asserter.failExpression("left - right", ".*null.*");
1951         asserter.failExpression("left * right", ".*null.*");
1952         asserter.failExpression("left / right", ".*null.*");
1953         asserter.failExpression("left % right", ".*null.*");
1954         asserter.failExpression("left & right", ".*null.*");
1955         asserter.failExpression("left | right", ".*null.*");
1956         asserter.failExpression("left ^ right", ".*null.*");
1957     }
1958 
1959     @Test
1960     void testOperatorsEdges() {
1961         assertNullOperand(() -> jexla.multiply(null, null));
1962         assertEquals(0, jexlb.multiply(null, null));
1963         assertNullOperand(() -> jexla.add(null, null));
1964         assertEquals(0, jexlb.add(null, null));
1965         assertNullOperand(() -> jexla.subtract(null, null));
1966         assertEquals(0, jexlb.subtract(null, null));
1967 
1968         assertTrue(jexla.contains(null, null));
1969         assertFalse(jexla.contains(true, null));
1970         assertFalse(jexla.contains(null, true));
1971         assertTrue(jexla.endsWith(null, null));
1972         assertFalse(jexla.endsWith(true, null));
1973         assertFalse(jexla.endsWith(null, true));
1974         assertTrue(jexla.startsWith(null, null));
1975         assertFalse(jexla.startsWith(true, null));
1976         assertFalse(jexla.startsWith(null, true));
1977         assertTrue(jexla.isEmpty(null));
1978     }
1979 
1980     @Test
1981     void testOption() {
1982         final Map<String, Object> vars = new HashMap<>();
1983         final JexlEvalContext context = new JexlEvalContext(vars);
1984         final JexlOptions options = context.getEngineOptions();
1985         options.setStrictArithmetic(true);
1986         final JexlScript script = JEXL.createScript("0 + '1.2' ");
1987         Object result;
1988 
1989         options.setStrictArithmetic(true);
1990         result = script.execute(context);
1991         assertEquals("01.2", result);
1992 
1993         options.setStrictArithmetic(false);
1994         result = script.execute(context);
1995         assertEquals(1.2d, (Double) result, EPSILON);
1996     }
1997 
1998     @Test
1999     void testOverflows() throws Exception {
2000         asserter.assertExpression("1 + 2147483647", Long.valueOf("2147483648"));
2001         asserter.assertExpression("3 + " + (Long.MAX_VALUE - 2),  BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE));
2002         asserter.assertExpression("-2147483648 - 1", Long.valueOf("-2147483649"));
2003         asserter.assertExpression("-3 + " + (Long.MIN_VALUE + 2),  BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE));
2004         asserter.assertExpression("1 + 9223372036854775807", new BigInteger("9223372036854775808"));
2005         asserter.assertExpression("-1 + (-9223372036854775808)", new BigInteger("-9223372036854775809"));
2006         asserter.assertExpression("-9223372036854775808 - 1", new BigInteger("-9223372036854775809"));
2007         final BigInteger maxl = BigInteger.valueOf(Long.MAX_VALUE);
2008         asserter.assertExpression(maxl + " * " + maxl , maxl.multiply(maxl));
2009     }
2010 
2011     @Test
2012     void testPlusClass() {
2013         final JexlEngine jexl = new JexlBuilder().create();
2014         final JexlContext jc = new MapContext();
2015         final Object ra = jexl.createExpression("463.0d + 0.1").evaluate(jc);
2016         assertEquals(Double.class, ra.getClass());
2017         final Object r0 = jexl.createExpression("463.0B + 0.1").evaluate(jc);
2018         assertEquals(java.math.BigDecimal.class, r0.getClass());
2019         final Object r1 = jexl.createExpression("463.0B + 0.1B").evaluate(jc);
2020         assertEquals(java.math.BigDecimal.class, r1.getClass());
2021     }
2022 
2023     @Test
2024     void testPlusPlusPostfix() throws Exception {
2025         asserter.setVariable("aByte", Byte.valueOf((byte) 0));
2026         asserter.setVariable("aShort", Short.valueOf((short) 1));
2027         asserter.setVariable("anInteger", Integer.valueOf(2));
2028         asserter.setVariable("aLong", Long.valueOf(3));
2029         asserter.setVariable("aFloat", Float.valueOf((float) 4.4));
2030         asserter.setVariable("aDouble", Double.valueOf(5.5));
2031         asserter.setVariable("aBigInteger", new BigInteger("6"));
2032         asserter.setVariable("aBigDecimal", new BigDecimal("7.7"));
2033         asserter.setVariable("aString", "forty-two");
2034 
2035         asserter.assertExpression("aByte++", Byte.valueOf((byte) 0));
2036         asserter.assertExpression("aShort++", Short.valueOf((short) 1));
2037         asserter.assertExpression("anInteger++", Integer.valueOf(2));
2038         asserter.assertExpression("aLong++", Long.valueOf(3));
2039         asserter.assertExpression("aFloat++", Float.valueOf((float) 4.4));
2040         asserter.assertExpression("aDouble++", Double.valueOf(5.5));
2041         asserter.assertExpression("aBigInteger++", new BigInteger("6"));
2042         asserter.assertExpression("aBigDecimal++", new BigDecimal("7.7"));
2043 
2044         asserter.assertExpression("aByte", Byte.valueOf((byte) 1));
2045         asserter.assertExpression("aShort", Short.valueOf((short) 2));
2046         asserter.assertExpression("anInteger", Integer.valueOf(3));
2047         asserter.assertExpression("aLong", Long.valueOf(4));
2048         asserter.assertExpression("aFloat", Float.valueOf((float) 5.4));
2049         asserter.assertExpression("aDouble", Double.valueOf(6.5));
2050         asserter.assertExpression("aBigInteger", new BigInteger("7"));
2051         asserter.assertExpression("aBigDecimal", new BigDecimal("8.7"));
2052 
2053         asserter.failExpression("aString++", "++", String::contains);
2054     }
2055 
2056     @Test
2057     void testPlusPlusPrefix() throws Exception {
2058         asserter.setVariable("aByte", Byte.valueOf((byte) 0));
2059         asserter.setVariable("aShort", Short.valueOf((short) 1));
2060         asserter.setVariable("anInteger", Integer.valueOf(2));
2061         asserter.setVariable("aLong", Long.valueOf(3));
2062         asserter.setVariable("aFloat", Float.valueOf((float) 4.4));
2063         asserter.setVariable("aDouble", Double.valueOf(5.5));
2064         asserter.setVariable("aBigInteger", new BigInteger("6"));
2065         asserter.setVariable("aBigDecimal", new BigDecimal("7.7"));
2066         asserter.setVariable("aString", "forty-two");
2067 
2068         asserter.assertExpression("++aByte", Byte.valueOf((byte) 1));
2069         asserter.assertExpression("++aShort", Short.valueOf((short) 2));
2070         asserter.assertExpression("++anInteger", Integer.valueOf(3));
2071         asserter.assertExpression("++aLong", Long.valueOf(4));
2072         asserter.assertExpression("++aFloat", Float.valueOf((float) 5.4));
2073         asserter.assertExpression("++aDouble", Double.valueOf(6.5));
2074         asserter.assertExpression("++aBigInteger", new BigInteger("7"));
2075         asserter.assertExpression("++aBigDecimal", new BigDecimal("8.7"));
2076 
2077         asserter.failExpression("++aString", "++", String::contains);
2078     }
2079 
2080     @Test
2081     void testRealCoercionEdges() throws Exception {
2082         assertNullOperand(() -> jexla.toDouble(null));
2083         assertEquals(0.0d, jexlb.toDouble(null), EPSILON);
2084         assertEquals(32.0d, jexlb.toDouble((char) 32), EPSILON);
2085         assertArithmeticException(() -> jexla.toDouble(date));
2086         assertTrue(Double.isNaN(jexla.toDouble("")));
2087         assertEquals("", jexla.toString(Double.NaN));
2088 
2089         assertNullOperand(() -> jexla.toBigInteger(null));
2090         assertArithmeticException(() -> jexla.toBigInteger(date));
2091         assertEquals(BigInteger.ZERO, jexla.toBigInteger(Double.NaN));
2092         assertEquals(BigInteger.ZERO, jexla.toBigInteger(""));
2093         assertEquals(BigInteger.ZERO, jexla.toBigInteger((char) 0));
2094 
2095         assertNullOperand(() -> jexla.toBigDecimal(null));
2096         assertArithmeticException(() -> jexla.toBigDecimal(date));
2097 
2098         assertEquals(BigDecimal.ZERO, jexla.toBigDecimal(Double.NaN));
2099         assertEquals(BigDecimal.ZERO, jexla.toBigDecimal(""));
2100         assertEquals(BigDecimal.ZERO, jexla.toBigDecimal((char) 0));
2101 
2102         final Double d64d3 = Double.valueOf(6.4 / 3);
2103         assertEquals(d64d3, ((Number) JEXL.createExpression("6.4 / 3").evaluate(null)).doubleValue(), EPSILON);
2104         asserter.assertExpression("6.4 / 3", d64d3);
2105         assertEquals(d64d3, ((Number) JEXL.createExpression("6.4 / 3d").evaluate(null)).doubleValue(), EPSILON);
2106         asserter.assertExpression("6.4 / 3d", d64d3);
2107         assertEquals(64d / 3, ((Number) JEXL.createExpression("64d / 3").evaluate(null)).doubleValue(), EPSILON);
2108         asserter.assertExpression("64 / 3d", 64 / 3d);
2109     }
2110 
2111     @Test
2112     void testRightNullOperand() throws Exception {
2113         asserter.setVariable("left", Integer.valueOf(9));
2114         asserter.setVariable("right", null);
2115         asserter.failExpression("left + right", ".*null.*");
2116         asserter.failExpression("left - right", ".*null.*");
2117         asserter.failExpression("left * right", ".*null.*");
2118         asserter.failExpression("left / right", ".*null.*");
2119         asserter.failExpression("left % right", ".*null.*");
2120         asserter.failExpression("left & right", ".*null.*");
2121         asserter.failExpression("left | right", ".*null.*");
2122         asserter.failExpression("left ^ right", ".*null.*");
2123         asserter.failExpression("left < right", ".*null.*");
2124         asserter.failExpression("left <= right", ".*null.*");
2125         asserter.failExpression("left > right", ".*null.*");
2126         asserter.failExpression("left >= right", ".*null.*");
2127     }
2128 
2129     @Test
2130     void testRightNullOperand2() throws Exception {
2131         asserter.setVariable("left", Integer.valueOf(9));
2132         asserter.setVariable("y.right", null);
2133         asserter.failExpression("left + y.right", ".*null.*");
2134         asserter.failExpression("left - y.right", ".*null.*");
2135         asserter.failExpression("left * y.right", ".*null.*");
2136         asserter.failExpression("left / y.right", ".*null.*");
2137         asserter.failExpression("left % y.right", ".*null.*");
2138         asserter.failExpression("left & y.right", ".*null.*");
2139         asserter.failExpression("left | y.right", ".*null.*");
2140         asserter.failExpression("left ^ y.right", ".*null.*");
2141         asserter.failExpression("left < y.right", ".*null.*");
2142         asserter.failExpression("left <= y.right", ".*null.*");
2143         asserter.failExpression("left > y.right", ".*null.*");
2144         asserter.failExpression("left >= y.right", ".*null.*");
2145     }
2146 
2147     /**
2148      * test some simple mathematical calculations
2149      */
2150     @Test
2151     void testUnaryMinus() throws Exception {
2152         asserter.setVariable("aByte", Byte.valueOf((byte) 1));
2153         asserter.setVariable("aShort", Short.valueOf((short) 2));
2154         asserter.setVariable("anInteger", Integer.valueOf(3));
2155         asserter.setVariable("aLong", Long.valueOf(4));
2156         asserter.setVariable("aFloat", Float.valueOf((float) 5.5));
2157         asserter.setVariable("aDouble", Double.valueOf(6.6));
2158         asserter.setVariable("aBigInteger", new BigInteger("7"));
2159         asserter.setVariable("aBigDecimal", new BigDecimal("8.8"));
2160 
2161         // loop to allow checking caching of constant numerals (debug)
2162         for(int i = 0 ; i < 2; ++i) {
2163             asserter.assertExpression("-3", Integer.valueOf("-3"));
2164             asserter.assertExpression("-3.0", Double.valueOf("-3.0"));
2165             asserter.assertExpression("-aByte", Byte.valueOf((byte) -1));
2166             asserter.assertExpression("-aShort", Short.valueOf((short) -2));
2167             asserter.assertExpression("-anInteger", Integer.valueOf(-3));
2168             asserter.assertExpression("-aLong", Long.valueOf(-4));
2169             asserter.assertExpression("-aFloat", Float.valueOf((float) -5.5));
2170             asserter.assertExpression("-aDouble", Double.valueOf(-6.6));
2171             asserter.assertExpression("-aBigInteger", new BigInteger("-7"));
2172             asserter.assertExpression("-aBigDecimal", new BigDecimal("-8.8"));
2173         }
2174     }
2175 
2176     @Test
2177     void testUnaryopsEdges() {
2178         assertArithmeticException(() -> jexla.positivize(date));
2179         assertNullOperand(() -> jexla.positivize(null));
2180         assertNull(jexlb.positivize(null));
2181         assertEquals(42, jexla.positivize((char) 42));
2182         assertEquals(Boolean.TRUE, jexla.positivize(Boolean.TRUE));
2183         assertEquals(Boolean.FALSE, jexla.positivize(Boolean.FALSE));
2184         assertEquals(Boolean.TRUE, jexla.positivize(new AtomicBoolean(true)));
2185         assertEquals(Boolean.FALSE, jexla.positivize(new AtomicBoolean()));
2186 
2187         assertNullOperand(() -> jexla.negate(null));
2188         assertNull(jexlb.negate(null));
2189         assertArithmeticException(() -> jexla.negate(date));
2190         assertEquals(Boolean.FALSE, jexla.negate(Boolean.TRUE));
2191         assertEquals(Boolean.TRUE, jexla.negate(Boolean.FALSE));
2192     }
2193 
2194     /**
2195      * test some simple mathematical calculations
2196      */
2197     @Test
2198     void testUnaryPlus() throws Exception {
2199         asserter.setVariable("aByte", Byte.valueOf((byte) 1));
2200         asserter.setVariable("aShort", Short.valueOf((short) 2));
2201         asserter.setVariable("anInteger", Integer.valueOf(3));
2202         asserter.setVariable("aLong", Long.valueOf(4));
2203         asserter.setVariable("aFloat", Float.valueOf((float) 5.5));
2204         asserter.setVariable("aDouble", Double.valueOf(6.6));
2205         asserter.setVariable("aBigInteger", new BigInteger("7"));
2206         asserter.setVariable("aBigDecimal", new BigDecimal("8.8"));
2207 
2208         // loop to allow checking caching of constant numerals (debug)
2209         for(int i = 0 ; i < 2; ++i) {
2210             asserter.assertExpression("+3", Integer.valueOf("3"));
2211             asserter.assertExpression("+3.0", Double.valueOf("3.0"));
2212             asserter.assertExpression("+aByte", Integer.valueOf(1));
2213             asserter.assertExpression("+aShort", Integer.valueOf(2));
2214             asserter.assertExpression("+anInteger", Integer.valueOf(3));
2215             asserter.assertExpression("+aLong", Long.valueOf(4));
2216             asserter.assertExpression("+aFloat", Float.valueOf((float) 5.5));
2217             asserter.assertExpression("+aDouble", Double.valueOf(6.6));
2218             asserter.assertExpression("+aBigInteger", new BigInteger("7"));
2219             asserter.assertExpression("+aBigDecimal", new BigDecimal("8.8"));
2220         }
2221     }
2222 
2223     @Test
2224     void testUndefinedVar() throws Exception {
2225         asserter.failExpression("objects[1].status", ".*variable 'objects' is undefined.*");
2226     }
2227 
2228     /**
2229      * Inspired by JEXL-16{1,2}.
2230      */
2231     @Test
2232     void testXmlArithmetic() throws Exception {
2233         Document xml;
2234         Node x;
2235         Boolean empty;
2236         int size;
2237         final JexlEvalContext ctxt = new JexlEvalContext();
2238         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).arithmetic(new XmlArithmetic(false)).create();
2239         final JexlScript e0 = jexl.createScript("x.empty()", "x");
2240         final JexlScript e1 = jexl.createScript("empty(x)", "x");
2241         final JexlScript s0 = jexl.createScript("x.size()", "x");
2242         final JexlScript s1 = jexl.createScript("size(x)", "x");
2243 
2244         empty = (Boolean) e1.execute(null, (Object) null);
2245         assertTrue(empty);
2246         size = (Integer) s1.execute(null, (Object) null);
2247         assertEquals(0, size);
2248 
2249         try {
2250             final Object xx = e0.execute(null, (Object) null);
2251             assertNull(xx);
2252         } catch (final JexlException.Variable xvar) {
2253             assertNotNull(xvar);
2254         }
2255         try {
2256             final Object xx = s0.execute(null, (Object) null);
2257             assertNull(xx);
2258         } catch (final JexlException.Variable xvar) {
2259             assertNotNull(xvar);
2260         }
2261         final JexlOptions options = ctxt.getEngineOptions();
2262         options.setSafe(true);
2263         final Object x0 = e0.execute(ctxt, (Object) null);
2264         assertNull(x0);
2265         final Object x1 = s0.execute(ctxt, (Object) null);
2266         assertNull(x1);
2267 
2268         xml = getDocument("<node info='123'/>");
2269         x = xml.getLastChild();
2270         empty = (Boolean) e0.execute(null, x);
2271         assertFalse(empty);
2272         empty = (Boolean) e1.execute(null, x);
2273         assertFalse(empty);
2274         size = (Integer) s0.execute(null, x);
2275         assertEquals(0, size);
2276         size = (Integer) s1.execute(null, x);
2277         assertEquals(0, size);
2278         xml = getDocument("<node><a/><b/></node>");
2279         x = xml.getLastChild();
2280         empty = (Boolean) e0.execute(null, x);
2281         assertFalse(empty);
2282         empty = (Boolean) e1.execute(null, x);
2283         assertFalse(empty);
2284         size = (Integer) s0.execute(null, x);
2285         assertEquals(2, size);
2286         size = (Integer) s1.execute(null, x);
2287         assertEquals(2, size);
2288         xml = getDocument("<node/>");
2289         x = xml.getLastChild();
2290         empty = (Boolean) e0.execute(null, x);
2291         assertTrue(empty);
2292         empty = (Boolean) e1.execute(null, x);
2293         assertTrue(empty);
2294         size = (Integer) s0.execute(null, x);
2295         assertEquals(0, size);
2296         size = (Integer) s1.execute(null, x);
2297         assertEquals(0, size);
2298         xml = getDocument("<node info='123'/>");
2299         NamedNodeMap nnm = xml.getLastChild().getAttributes();
2300         Attr info = (Attr) nnm.getNamedItem("info");
2301         assertEquals("123", info.getValue());
2302 
2303         // JEXL-161
2304         final JexlContext jc = new MapContext();
2305         final Object xmlx = xml.getLastChild();
2306         jc.set("x", xmlx);
2307         final String y = "456";
2308         jc.set("y", y);
2309         final JexlScript s = jexl.createScript("x.attributes.info = y");
2310         Object r;
2311         try {
2312             nnm = xml.getLastChild().getAttributes();
2313             info = (Attr) nnm.getNamedItem("info");
2314             r = s.execute(jc);
2315             assertEquals(y, r);
2316             assertEquals(y, info.getValue());
2317         } catch (final JexlException.Property xprop) {
2318             // test fails in java > 11 because modules, etc; need investigation
2319             assertTrue(xprop.getMessage().contains("attribute"));
2320             assertTrue(getJavaVersion() > 11, getJavaVersion() + " >? 11");
2321         }
2322     }
2323 
2324     @Test void testShortCircuitAnd() {
2325         final String src = "(x, y, z) -> x && y && z";
2326         final JexlBuilder builder = new JexlBuilder();
2327         final JexlEngine jexl = builder.create();
2328         JexlScript script;
2329         Object result;
2330         script = jexl.createScript(src);
2331         result = script.execute(null, true, "foo", 42);
2332         assertEquals(42, result);
2333         result = script.execute(null, true, "", 42);
2334         assertEquals("", result);
2335     }
2336 
2337     @Test void testShortCircuitOr() {
2338         final OptContext optc = new OptContext();
2339         final String src = "(x, y, z) -> x || y || z";
2340         final JexlBuilder builder = new JexlBuilder();
2341         final JexlEngine jexl = builder.create();
2342         final JexlOptions options = builder.options();
2343         optc.setOptions(options);
2344         JexlScript script;
2345         Object result;
2346         script = jexl.createScript(src);
2347         result = script.execute(optc, 0, "", 42);
2348         assertEquals(42, result);
2349         result = script.execute(optc, true, 42, null);
2350         assertEquals(true, result);
2351 
2352         options.setBooleanLogical(true);
2353         result = script.execute(optc, 0, "", Double.NaN);
2354         assertEquals(false, result);
2355         result = script.execute(optc, 0, "", Collections.emptySet());
2356         assertEquals(true, result);
2357 
2358     }
2359 
2360     @Test void testLogicalValue() {
2361         final String src = "function sanitize(const n) { n == 0 ? NaN : n }; sanitize(x) && 420 / x";
2362         final JexlEngine jexl = new JexlBuilder().create();
2363         JexlScript script;
2364         Object result;
2365         script = jexl.createScript(src, "x");
2366         result = script.execute(null, 10);
2367         assertEquals(42, result);
2368         result = script.execute(null, 0);
2369         assertTrue(Double.isNaN(((Number) result).doubleValue()));
2370     }
2371 
2372     public static class OptContext extends MapContext implements JexlContext.OptionsHandle {
2373         private JexlOptions options;
2374 
2375         @Override
2376         public JexlOptions getEngineOptions() {
2377             return options;
2378         }
2379 
2380         void setOptions(final JexlOptions options) {
2381             this.options = options;
2382         }
2383     }
2384 
2385     @Test void testLeadingDotFloatLiteral() {
2386         final JexlEngine jexl = new JexlBuilder().create();
2387         final JexlContext jc = new MapContext();
2388         // the exact case from JEXL-411
2389         assertEquals(2.2d, jexl.createExpression("(1+.1)*2").evaluate(jc));
2390         // the regression case that already worked
2391         assertEquals(2.2d, jexl.createExpression("(1+0.1)*2").evaluate(jc));
2392         // standalone and compound leading-dot literals
2393         assertEquals(0.1d, jexl.createExpression(".1").evaluate(jc));
2394         assertEquals(1.0d, jexl.createExpression(".5 + .5").evaluate(jc));
2395         assertEquals(0.55d, jexl.createExpression(".55").evaluate(jc));
2396         assertEquals(0.1d, jexl.createExpression("-.1 + .2").evaluate(jc));
2397         assertEquals(-0.1d, jexl.createExpression(".1 + -.2").evaluate(jc));
2398         assertEquals(0.1d, jexl.createExpression("-.1+.2").evaluate(jc));
2399         assertEquals(-0.1d, jexl.createExpression(".1+-.2").evaluate(jc));
2400         assertEquals(1.4d, jexl.createExpression(".5 - -.9").evaluate(jc));
2401         assertEquals(0.4d, jexl.createExpression("-.5 - -.9").evaluate(jc));
2402         assertEquals(-1.4d, jexl.createExpression("-.5+-.9").evaluate(jc));
2403         // unary handling
2404         assertEquals(-0.1d, jexl.createExpression("-.1").evaluate(jc));
2405         assertEquals(0.1d, jexl.createExpression("+.1").evaluate(jc));
2406         // property / index access via a dot must keep working (not parsed as a float)
2407         final List<Object> array = new java.util.ArrayList<>();
2408         array.add("zero");
2409         array.add("one");
2410         jc.set("array", array);
2411         assertEquals("zero", jexl.createExpression("array.0").evaluate(jc));
2412         assertEquals("one", jexl.createExpression("array.1").evaluate(jc));
2413     }
2414 
2415     // ----- security fixes f012 / f013 / f014 -----
2416 
2417     /**
2418      * f014: BigInteger literal with more digits than MAX_BIGINTEGER_DIGITS must be rejected at parse time.
2419      */
2420     @Test
2421     void testBigIntegerLiteralTooLong() {
2422         // normal H-literal still works
2423         assertNotNull(JEXL.createScript("42H"));
2424         // a literal just over the cap (256 + 1 digits + 'H') must fail to parse
2425         final char[] digits = new char[JexlArithmetic.MAX_BIGINTEGER_DIGITS + 1];
2426         java.util.Arrays.fill(digits, '1');
2427         final String huge = new String(digits) + "H";
2428         assertThrows(JexlException.Parsing.class, () -> JEXL.createScript(huge));
2429     }
2430 
2431     /**
2432      * f013: BigInteger arithmetic results that exceed the MathContext precision must throw.
2433      */
2434     @Test
2435     void testBigIntegerArithmeticPrecisionCap() {
2436         // precision=3 caps at ~11 bits (formula: 3 * 10 / 3 + 1 = 11), so results > 2047 are rejected
2437         final JexlArithmetic bounded = new JexlArithmetic(true, new MathContext(3), JexlArithmetic.BIGD_SCALE);
2438         final JexlEngine jexl = new JexlBuilder().arithmetic(bounded).create();
2439         // small values are fine
2440         assertEquals(new BigInteger("3"), jexl.createScript("a + b", "a", "b")
2441                 .execute(null, BigInteger.ONE, BigInteger.valueOf(2L)));
2442         // result > 2047 is rejected: 1500 + 1000 = 2500, bitLength=12 > 11
2443         assertThrows(JexlException.class, () ->
2444                 jexl.createScript("a + b", "a", "b")
2445                     .execute(null, BigInteger.valueOf(1500L), BigInteger.valueOf(1000L)));
2446         // multiply pre-check: 64 (7 bits) * 64 (7 bits), sum of operand bits = 14 > 11
2447         assertThrows(JexlException.class, () ->
2448                 jexl.createScript("a * b", "a", "b")
2449                     .execute(null, BigInteger.valueOf(64L), BigInteger.valueOf(64L)));
2450     }
2451 
2452     /**
2453      * f012: a regex pattern string longer than REGEX_PATTERN_MAX_LENGTH must throw.
2454      */
2455     @Test
2456     void testRegexPatternTooLong() {
2457         final JexlEngine jexl = new JexlBuilder().strict(true).create();
2458         final JexlScript script = jexl.createScript("x =~ y", "x", "y");
2459         final char[] chars = new char[JexlArithmetic.REGEX_PATTERN_MAX_LENGTH + 1];
2460         java.util.Arrays.fill(chars, 'a');
2461         final String longPattern = new String(chars);
2462         assertThrows(JexlException.class, () -> script.execute(null, "abc", longPattern));
2463     }
2464 
2465     /**
2466      * f012: regex matching must respond to thread interruption so a catastrophic-backtracking
2467      * pattern does not hang a cancellable engine indefinitely.
2468      */
2469     @Test
2470     void testRegexMatchingInterruptible() throws InterruptedException {
2471         final JexlEngine jexl = new JexlBuilder().cancellable(true).create();
2472         final JexlScript script = jexl.createScript("x =~ y", "x", "y");
2473         final String evilPattern = "(a+)+b";
2474 
2475         for (int size = 256; size <= 4096; size *= 2) {
2476             final char[] chars = new char[size];
2477             java.util.Arrays.fill(chars, 'a');
2478             final String evilValue = new String(chars) + "c";
2479 
2480             final AtomicReference<Throwable> caught = new AtomicReference<>();
2481             final CountDownLatch started = new CountDownLatch(1);
2482             final Thread t = new Thread(() -> {
2483                 try {
2484                     started.countDown();
2485                     script.execute(null, evilValue, evilPattern);
2486                 } catch (final Throwable e) {
2487                     caught.set(e);
2488                 }
2489             });
2490 
2491             t.start();
2492             started.await();
2493 
2494             final long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(5);
2495             while (t.isAlive() && System.nanoTime() < deadline) {
2496                 t.interrupt();
2497                 Thread.yield();
2498                 t.join(10L);
2499             }
2500 
2501             assertFalse(t.isAlive(), "Thread should have completed after interruption (regex should be interruptible)");
2502 
2503             final Throwable thrown = caught.get();
2504             if (thrown instanceof JexlException.Cancel) {
2505                 return;
2506             }
2507             if (thrown != null) {
2508                 fail("Expected JexlException.Cancel when interrupted during matching, got "
2509                     + thrown.getClass().getSimpleName(), thrown);
2510             }
2511         }
2512 
2513         fail("Did not observe JexlException.Cancel during interrupted regex matching");
2514     }
2515 }