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.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.math.BigDecimal;
26  import java.math.BigInteger;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.BitSet;
30  import java.util.Calendar;
31  import java.util.GregorianCalendar;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.commons.jexl3.parser.Parser;
40  import org.junit.jupiter.api.Test;
41  
42  /**
43   * Simple test cases.
44   */
45  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
46  public final class JexlTest extends JexlTestCase {
47      public static final class Duck {
48          int user = 10;
49  
50          @SuppressWarnings("boxing")
51          public Integer get(final String val) {
52              switch (val) {
53              case "zero":
54                  return 0;
55              case "one":
56                  return 1;
57              case "user":
58                  return user;
59              default:
60                  break;
61              }
62              return -1;
63          }
64  
65          @SuppressWarnings("boxing")
66          public void set(final String val, final Object value) {
67              if ("user".equals(val)) {
68                  if ("zero".equals(value)) {
69                      user = 0;
70                  } else if ("one".equals(value)) {
71                      user = 1;
72                  } else {
73                      user = value instanceof Integer ? (Integer) value : -1;
74                  }
75              }
76          }
77      }
78      static final String METHOD_STRING = "Method string";
79  
80      static final String GET_METHOD_STRING = "GetMethod string";
81  
82      public JexlTest() {
83          super("JexlTest",
84                  new JexlBuilder()
85                          .strict(false)
86                          .imports(Arrays.asList("java.lang","java.math"))
87                          .permissions(null)
88                          .cache(128).create());
89      }
90  
91      /**
92       * Asserts that the given expression returns the given value when applied to the
93       * given context
94       */
95      private void assertExpression(final JexlContext jc, final String expression, final Object expected) {
96          final JexlExpression e = JEXL.createExpression(expression);
97          final Object actual = e.evaluate(jc);
98          assertEquals(expected, actual, expression);
99      }
100 
101     @Test
102     void testAntPropertiesWithMethods() {
103         final JexlContext jc = new MapContext();
104         final String value = "Stinky Cheese";
105         jc.set("maven.bob.food", value);
106         assertExpression(jc, "maven.bob.food.length()", Integer.valueOf(value.length()));
107         assertExpression(jc, "empty(maven.bob.food)", Boolean.FALSE);
108         assertExpression(jc, "size(maven.bob.food)", Integer.valueOf(value.length()));
109         assertExpression(jc, "maven.bob.food + ' is good'", value + " is good");
110 
111         // DG: Note the following ant properties don't work
112 //        String version = "1.0.3";
113 //        jc.set("commons-logging", version);
114 //        assertExpression(jc, "commons-logging", version);
115     }
116 
117     @SuppressWarnings("boxing")
118     @Test
119     void testArray() {
120         final int[] array = {100, 101, 102};
121         final JexlEngine jexl = JEXL;
122         final JexlContext jc = new MapContext();
123         jc.set("array", array);
124         JexlExpression expr;
125         Object result;
126         expr = jexl.createExpression("array.1");
127         result = expr.evaluate(jc);
128         assertEquals(101, result);
129         expr = jexl.createExpression("array[1] = 1010");
130         result = expr.evaluate(jc);
131         assertEquals(1010, result, expr::toString);
132         expr = jexl.createExpression("array.0");
133         result = expr.evaluate(jc);
134         assertEquals(100, result, expr::toString);
135     }
136 
137     /**
138      * Test assignment.
139      */
140     @Test
141     void testAssignment() {
142         final JexlContext jc = new MapContext();
143         jc.set("aString", "Hello");
144         final Foo foo = new Foo();
145         jc.set("foo", foo);
146         final Parser parser = new Parser(";");
147         parser.parse(null, new JexlFeatures().register(false), "aString = 'World';", null);
148 
149         assertExpression(jc, "hello = 'world'", "world");
150         assertEquals("world", jc.get("hello"), "hello variable not changed");
151         assertExpression(jc, "result = 1 + 1", Integer.valueOf(2));
152         assertEquals(Integer.valueOf(2), jc.get("result"), "result variable not changed");
153         assertExpression(jc, "foo.property1 = '99'", "99");
154         assertEquals("99", foo.getProperty1(), "property not set");
155     }
156 
157     /**
158      * Make sure bad syntax throws ParseException
159      */
160     @Test
161     void testBadParse() {
162         assertThrows(JexlException.class, () -> assertExpression(new MapContext(), "empty()", null));
163     }
164 
165     /**
166      * test some blank strings
167      */
168     @Test
169     void testBlankStrings() {
170         final JexlContext jc = new MapContext();
171         jc.set("bar", "");
172 
173         assertExpression(jc, "bar == ''", Boolean.TRUE);
174         assertExpression(jc, "empty bar", Boolean.TRUE);
175         assertExpression(jc, "bar.length() == 0", Boolean.TRUE);
176         assertExpression(jc, "size(bar) == 0", Boolean.TRUE);
177     }
178 
179     @Test
180     void testBoolean() {
181         final JexlContext jc = new MapContext();
182         jc.set("foo", new Foo());
183         jc.set("a", Boolean.TRUE);
184         jc.set("b", Boolean.FALSE);
185 
186         assertExpression(jc, "foo.convertBoolean(a==b)", "Boolean : false");
187         assertExpression(jc, "foo.convertBoolean(a==true)", "Boolean : true");
188         assertExpression(jc, "foo.convertBoolean(a==false)", "Boolean : false");
189         assertExpression(jc, "foo.convertBoolean(true==false)", "Boolean : false");
190         assertExpression(jc, "true eq false", Boolean.FALSE);
191         assertExpression(jc, "true ne false", Boolean.TRUE);
192     }
193 
194     /**
195      * Test that 'and' only evaluates the second item if needed
196      */
197     @Test
198     void testBooleanShortCircuitAnd() {
199         // handle false for the left arg of 'and'
200         Foo tester = new Foo();
201         final JexlContext jc = new MapContext();
202         jc.set("first", Boolean.FALSE);
203         jc.set("foo", tester);
204         final JexlExpression expr = JEXL.createExpression("first and foo.trueAndModify");
205         expr.evaluate(jc);
206         assertFalse(tester.getModified(), "Short circuit failure: rhs evaluated when lhs FALSE");
207         // handle true for the left arg of 'and'
208         tester = new Foo();
209         jc.set("first", Boolean.TRUE);
210         jc.set("foo", tester);
211         expr.evaluate(jc);
212         assertTrue(tester.getModified(), "Short circuit failure: rhs not evaluated when lhs TRUE");
213     }
214 
215     /**
216      * Test that 'or' only evaluates the second item if needed
217      */
218     @Test
219     void testBooleanShortCircuitOr() {
220         // handle false for the left arg of 'or'
221         Foo tester = new Foo();
222         final JexlContext jc = new MapContext();
223         jc.set("first", Boolean.FALSE);
224         jc.set("foo", tester);
225         final JexlExpression expr = JEXL.createExpression("first or foo.trueAndModify");
226         expr.evaluate(jc);
227         assertTrue(tester.getModified(), "Short circuit failure: rhs not evaluated when lhs FALSE");
228         // handle true for the left arg of 'or'
229         tester = new Foo();
230         jc.set("first", Boolean.TRUE);
231         jc.set("foo", tester);
232         expr.evaluate(jc);
233         assertFalse(tester.getModified(), "Short circuit failure: rhs evaluated when lhs TRUE");
234     }
235 
236     /**
237      * test some simple mathematical calculations
238      */
239     @Test
240     void testCalculations() {
241         final JexlEvalContext jc = new JexlEvalContext();
242         final JexlOptions options = jc.getEngineOptions();
243         options.setStrict(false);
244         options.setStrictArithmetic(false);
245 
246         /*
247          * test to ensure new string cat works
248          */
249         jc.set("stringy", "thingy");
250         assertExpression(jc, "stringy + 2", "thingy2");
251 
252         /*
253          * test new null coercion
254          */
255         jc.set("imanull", null);
256         assertExpression(jc, "imanull + 2", Integer.valueOf(2));
257         assertExpression(jc, "imanull + imanull", Integer.valueOf(0));
258 
259         /* test for bugzilla 31577 */
260         jc.set("n", Integer.valueOf(0));
261         assertExpression(jc, "n != null && n != 0", Boolean.FALSE);
262     }
263 
264     /**
265      * Attempts to recreate bug <a href="https://jira.werken.com/ViewIssue.jspa?key=JELLY-8">...</a>
266      */
267     @Test
268     void testCharAtBug() {
269         final JexlEvalContext jc = new JexlEvalContext();
270         final JexlOptions options = jc.getEngineOptions();
271         options.setSilent(true);
272 
273         jc.set("foo", "abcdef");
274 
275         assertExpression(jc, "foo.substring(2,4)", "cd");
276         assertExpression(jc, "foo.charAt(2)", Character.valueOf('c'));
277         assertExpression(jc, "foo.charAt(-2)", null);
278 
279     }
280 
281     @Test
282     void testCoercionWithComparisonOperators() {
283         final JexlContext jc = new MapContext();
284 
285         assertExpression(jc, "'2' > 1", Boolean.TRUE);
286         assertExpression(jc, "'2' >= 1", Boolean.TRUE);
287         assertExpression(jc, "'2' >= 2", Boolean.TRUE);
288         assertExpression(jc, "'2' < 1", Boolean.FALSE);
289         assertExpression(jc, "'2' <= 1", Boolean.FALSE);
290         assertExpression(jc, "'2' <= 2", Boolean.TRUE);
291 
292         assertExpression(jc, "2 > '1'", Boolean.TRUE);
293         assertExpression(jc, "2 >= '1'", Boolean.TRUE);
294         assertExpression(jc, "2 >= '2'", Boolean.TRUE);
295         assertExpression(jc, "2 < '1'", Boolean.FALSE);
296         assertExpression(jc, "2 <= '1'", Boolean.FALSE);
297         assertExpression(jc, "2 <= '2'", Boolean.TRUE);
298     }
299 
300     /**
301      * Test the ## comment in a string
302      */
303     @Test
304     void testComment() {
305         assertExpression(new MapContext(), "## double or nothing\n 1 + 1", Integer.valueOf("2"));
306     }
307 
308     /**
309      * test some simple conditions
310      */
311     @Test
312     void testComparisons() {
313         final JexlContext jc = new MapContext();
314         jc.set("foo", "the quick and lazy fox");
315 
316         assertExpression(jc, "foo.indexOf('quick') > 0", Boolean.TRUE);
317         assertExpression(jc, "foo.indexOf('bar') >= 0", Boolean.FALSE);
318         assertExpression(jc, "foo.indexOf('bar') < 0", Boolean.TRUE);
319     }
320 
321     /**
322      * test some simple conditions
323      */
324     @Test
325     void testConditions() {
326         final JexlEvalContext jc = new JexlEvalContext();
327         final JexlOptions options = jc.getEngineOptions();
328         jc.set("foo", Integer.valueOf(2));
329         jc.set("aFloat", Float.valueOf(1));
330         jc.set("aDouble", Double.valueOf(2));
331         jc.set("aChar", Character.valueOf('A'));
332         jc.set("aBool", Boolean.TRUE);
333         final StringBuilder buffer = new StringBuilder("abc");
334         final List<Object> list = new ArrayList<>();
335         final List<Object> list2 = new LinkedList<>();
336         jc.set("aBuffer", buffer);
337         jc.set("aList", list);
338         jc.set("bList", list2);
339 
340         assertExpression(jc, "foo == 2", Boolean.TRUE);
341         assertExpression(jc, "2 == 3", Boolean.FALSE);
342         assertExpression(jc, "3 == foo", Boolean.FALSE);
343         assertExpression(jc, "3 != foo", Boolean.TRUE);
344         assertExpression(jc, "foo != 2", Boolean.FALSE);
345         // test float and double equality
346         assertExpression(jc, "aFloat eq aDouble", Boolean.FALSE);
347         assertExpression(jc, "aFloat ne aDouble", Boolean.TRUE);
348         assertExpression(jc, "aFloat == aDouble", Boolean.FALSE);
349         assertExpression(jc, "aFloat != aDouble", Boolean.TRUE);
350         // test number and character equality
351         assertExpression(jc, "foo == aChar", Boolean.FALSE);
352         assertExpression(jc, "foo != aChar", Boolean.TRUE);
353         // test string and boolean
354         assertExpression(jc, "aBool == 'true'", Boolean.TRUE);
355         assertExpression(jc, "aBool == 'false'", Boolean.FALSE);
356         assertExpression(jc, "aBool != 'false'", Boolean.TRUE);
357         // test null and boolean
358         options.setStrict(false);
359         assertExpression(jc, "aBool == notThere", Boolean.FALSE);
360         assertExpression(jc, "aBool != notThere", Boolean.TRUE);
361         // anything and string as a string comparison
362         options.setStrict(true);
363         assertExpression(jc, "aBuffer == 'abc'", Boolean.TRUE);
364         assertExpression(jc, "aBuffer != 'abc'", Boolean.FALSE);
365         // arbitrary equals
366         assertExpression(jc, "aList == bList", Boolean.TRUE);
367         assertExpression(jc, "aList != bList", Boolean.FALSE);
368     }
369 
370     @SuppressWarnings("boxing")
371     @Test
372     void testDuck() {
373         final JexlEngine jexl = JEXL;
374         final JexlContext jc = new MapContext();
375         jc.set("duck", new Duck());
376         JexlExpression expr;
377         Object result;
378         expr = jexl.createExpression("duck.zero");
379         result = expr.evaluate(jc);
380         assertEquals(0, result, expr::toString);
381         expr = jexl.createExpression("duck.one");
382         result = expr.evaluate(jc);
383         assertEquals(1, result, expr::toString);
384         expr = jexl.createExpression("duck.user = 20");
385         result = expr.evaluate(jc);
386         assertEquals(20, result, expr::toString);
387         expr = jexl.createExpression("duck.user");
388         result = expr.evaluate(jc);
389         assertEquals(20, result, expr::toString);
390         expr = jexl.createExpression("duck.user = 'zero'");
391         result = expr.evaluate(jc);
392         assertEquals("zero", result, expr::toString);
393         expr = jexl.createExpression("duck.user");
394         result = expr.evaluate(jc);
395         assertEquals(0, result, expr::toString);
396     }
397 
398     @Test
399     void testEmpty() {
400         final JexlEvalContext jc = new JexlEvalContext();
401         final JexlOptions options = jc.getEngineOptions();
402         options.setStrict(false);
403         jc.set("string", "");
404         jc.set("array", new Object[0]);
405         jc.set("map", new HashMap<>());
406         jc.set("list", new ArrayList<>());
407         jc.set("set", new HashMap<>().keySet());
408         jc.set("longstring", "thingthing");
409 
410         /*
411          *  I can't believe anyone thinks this is a syntax... :)
412          */
413         assertExpression(jc, "empty nullthing", Boolean.TRUE);
414         assertExpression(jc, "empty string", Boolean.TRUE);
415         assertExpression(jc, "empty array", Boolean.TRUE);
416         assertExpression(jc, "empty map", Boolean.TRUE);
417         assertExpression(jc, "empty set", Boolean.TRUE);
418         assertExpression(jc, "empty list", Boolean.TRUE);
419         assertExpression(jc, "empty longstring", Boolean.FALSE);
420         assertExpression(jc, "not empty longstring", Boolean.TRUE);
421     }
422 
423     @Test
424     void testEmptyDottedVariableName() {
425         final JexlContext jc = new MapContext();
426 
427         jc.set("this.is.a.test", "");
428 
429         assertExpression(jc, "empty(this.is.a.test)", Boolean.TRUE);
430     }
431 
432     @Test
433     void testEmptySubListOfMap() {
434         final JexlContext jc = new MapContext();
435         final Map<String, ArrayList<?>> m = new HashMap<>();
436         m.put("aList", new ArrayList<>());
437 
438         jc.set("aMap", m);
439 
440         assertExpression(jc, "empty( aMap.aList )", Boolean.TRUE);
441     }
442 
443     @Test
444     void testExpression() {
445         final JexlContext jc = new MapContext();
446         jc.set("foo", new Foo());
447         jc.set("a", Boolean.TRUE);
448         jc.set("b", Boolean.FALSE);
449         jc.set("num", Integer.valueOf(5));
450         jc.set("now", Calendar.getInstance().getTime());
451         final GregorianCalendar gc = new GregorianCalendar(5000, Calendar.DECEMBER, 20);
452         jc.set("now2", gc.getTime());
453         jc.set("bdec", new BigDecimal("7"));
454         jc.set("bint", new BigInteger("7"));
455 
456         assertExpression(jc, "a == b", Boolean.FALSE);
457         assertExpression(jc, "a==true", Boolean.TRUE);
458         assertExpression(jc, "a==false", Boolean.FALSE);
459         assertExpression(jc, "true==false", Boolean.FALSE);
460 
461         assertExpression(jc, "2 < 3", Boolean.TRUE);
462         assertExpression(jc, "num < 5", Boolean.FALSE);
463         assertExpression(jc, "num < num", Boolean.FALSE);
464         assertExpression(jc, "num < null", Boolean.FALSE);
465         assertExpression(jc, "num < 2.5", Boolean.FALSE);
466         assertExpression(jc, "now2 < now", Boolean.FALSE); // test comparable
467 //
468         assertExpression(jc, "'6' <= '5'", Boolean.FALSE);
469         assertExpression(jc, "num <= 5", Boolean.TRUE);
470         assertExpression(jc, "num <= num", Boolean.TRUE);
471         assertExpression(jc, "num <= null", Boolean.FALSE);
472         assertExpression(jc, "num <= 2.5", Boolean.FALSE);
473         assertExpression(jc, "now2 <= now", Boolean.FALSE); // test comparable
474 
475 //
476         assertExpression(jc, "'6' >= '5'", Boolean.TRUE);
477         assertExpression(jc, "num >= 5", Boolean.TRUE);
478         assertExpression(jc, "num >= num", Boolean.TRUE);
479         assertExpression(jc, "num >= null", Boolean.FALSE);
480         assertExpression(jc, "num >= 2.5", Boolean.TRUE);
481         assertExpression(jc, "now2 >= now", Boolean.TRUE); // test comparable
482 
483         assertExpression(jc, "'6' > '5'", Boolean.TRUE);
484         assertExpression(jc, "num > 4", Boolean.TRUE);
485         assertExpression(jc, "num > num", Boolean.FALSE);
486         assertExpression(jc, "num > null", Boolean.FALSE);
487         assertExpression(jc, "num > 2.5", Boolean.TRUE);
488         assertExpression(jc, "now2 > now", Boolean.TRUE); // test comparable
489 
490         assertExpression(jc, "\"foo\" + \"bar\" == \"foobar\"", Boolean.TRUE);
491 
492         assertExpression(jc, "bdec > num", Boolean.TRUE);
493         assertExpression(jc, "bdec >= num", Boolean.TRUE);
494         assertExpression(jc, "num <= bdec", Boolean.TRUE);
495         assertExpression(jc, "num < bdec", Boolean.TRUE);
496         assertExpression(jc, "bint > num", Boolean.TRUE);
497         assertExpression(jc, "bint == bdec", Boolean.TRUE);
498         assertExpression(jc, "bint >= num", Boolean.TRUE);
499         assertExpression(jc, "num <= bint", Boolean.TRUE);
500         assertExpression(jc, "num < bint", Boolean.TRUE);
501     }
502 
503     /**
504      * test the use of an int based property
505      */
506     @Test
507     void testIntProperty() {
508         final Foo foo = new Foo();
509 
510         // let's check the square function first.
511         assertEquals(4, foo.square(2));
512         assertEquals(4, foo.square(-2));
513 
514         final JexlContext jc = new MapContext();
515         jc.set("foo", foo);
516 
517         assertExpression(jc, "foo.count", Integer.valueOf(5));
518         assertExpression(jc, "foo.square(2)", Integer.valueOf(4));
519         assertExpression(jc, "foo.square(-2)", Integer.valueOf(4));
520     }
521 
522     /**
523      * test some blank strings
524      */
525     @Test
526     void testLogicExpressions() {
527         final JexlContext jc = new MapContext();
528         jc.set("foo", "abc");
529         jc.set("bar", "def");
530 
531         assertExpression(jc, "foo == 'abc' || bar == 'abc'", Boolean.TRUE);
532         assertExpression(jc, "foo == 'abc' or bar == 'abc'", Boolean.TRUE);
533         assertExpression(jc, "foo == 'abc' && bar == 'abc'", Boolean.FALSE);
534         assertExpression(jc, "foo == 'abc' and bar == 'abc'", Boolean.FALSE);
535 
536         assertExpression(jc, "foo == 'def' || bar == 'abc'", Boolean.FALSE);
537         assertExpression(jc, "foo == 'def' or bar == 'abc'", Boolean.FALSE);
538         assertExpression(jc, "foo == 'abc' && bar == 'def'", Boolean.TRUE);
539         assertExpression(jc, "foo == 'abc' and bar == 'def'", Boolean.TRUE);
540     }
541 
542     /**
543      * test the use of dot notation to lookup map entries
544      */
545     @Test
546     void testMapDot() {
547         final Map<String, String> foo = new HashMap<>();
548         foo.put("bar", "123");
549 
550         final JexlContext jc = new MapContext();
551         jc.set("foo", foo);
552 
553         assertExpression(jc, "foo.bar", "123");
554     }
555 
556     /**
557      * test the -1 comparison bug
558      */
559     @Test
560     void testNegativeIntComparison() {
561         final JexlContext jc = new MapContext();
562         final Foo foo = new Foo();
563         jc.set("foo", foo);
564 
565         assertExpression(jc, "foo.count != -1", Boolean.TRUE);
566         assertExpression(jc, "foo.count == 5", Boolean.TRUE);
567         assertExpression(jc, "foo.count == -1", Boolean.FALSE);
568     }
569 
570     /**
571      * Test the new function e.g. constructor invocation.
572      */
573     @Test
574     void testNew() {
575         final JexlContext jc = new MapContext();
576         jc.set("double", Double.class);
577         jc.set("foo", "org.apache.commons.jexl3.Foo");
578         JexlExpression expr;
579         Object value;
580         expr = JEXL.createExpression("new(double, 1)");
581         value = expr.evaluate(jc);
582         assertEquals(Double.valueOf(1.0), value, expr::toString);
583         expr = JEXL.createExpression("new('java.lang.Float', 100)");
584         value = expr.evaluate(jc);
585         assertEquals(Float.valueOf((float) 100.0), value, expr::toString);
586         expr = JEXL.createExpression("new(foo).quux");
587         value = expr.evaluate(jc);
588         assertEquals("String : quux", value, expr::toString);
589     }
590 
591     @Test
592     void testNewImports() {
593         final JexlEngine jexl = new JexlBuilder().imports("java.lang", "java.util").create();
594         JexlExpression expr;
595         Object result;
596         expr = jexl.createExpression("new LinkedList([1,2,3,...])");
597         result = expr.evaluate(null);
598         assertInstanceOf(LinkedList.class, result);
599     }
600 
601     /**
602      * test some simple conditions
603      */
604     @Test
605     void testNotConditions() {
606         final JexlContext jc = new MapContext();
607 
608         final Foo foo = new Foo();
609         jc.set("x", Boolean.TRUE);
610         jc.set("foo", foo);
611         jc.set("bar", "true");
612 
613         assertExpression(jc, "!x", Boolean.FALSE);
614         assertExpression(jc, "x", Boolean.TRUE);
615         assertExpression(jc, "!bar", Boolean.FALSE);
616         assertExpression(jc, "!foo.isSimple()", Boolean.FALSE);
617         assertExpression(jc, "foo.isSimple()", Boolean.TRUE);
618         assertExpression(jc, "!foo.simple", Boolean.FALSE);
619         assertExpression(jc, "foo.simple", Boolean.TRUE);
620         assertExpression(jc, "foo.getCheeseList().size() == 3", Boolean.TRUE);
621         assertExpression(jc, "foo.cheeseList.size() == 3", Boolean.TRUE);
622 
623         jc.set("string", "");
624         assertExpression(jc, "not empty string", Boolean.FALSE);
625         assertExpression(jc, "not(empty string)", Boolean.FALSE);
626         assertExpression(jc, "not empty(string)", Boolean.FALSE);
627         assertExpression(jc, "! empty string", Boolean.FALSE);
628         assertExpression(jc, "!(empty string)", Boolean.FALSE);
629         assertExpression(jc, "! empty(string)", Boolean.FALSE);
630 
631     }
632 
633     /**
634      * test some simple conditions
635      */
636     @Test
637     void testNotConditionsWithDots() {
638         final JexlContext jc = new MapContext();
639 
640         jc.set("x.a", Boolean.TRUE);
641         jc.set("x.b", Boolean.FALSE);
642 
643         assertExpression(jc, "x.a", Boolean.TRUE);
644         assertExpression(jc, "!x.a", Boolean.FALSE);
645         assertExpression(jc, "!x.b", Boolean.TRUE);
646     }
647 
648     /**
649      * test some null conditions
650      */
651     @Test
652     void testNull() {
653         final JexlEvalContext jc = new JexlEvalContext();
654         final JexlOptions options = jc.getEngineOptions();
655         options.setStrict(false);
656         jc.set("bar", Integer.valueOf(2));
657 
658         assertExpression(jc, "empty foo", Boolean.TRUE);
659         assertExpression(jc, "bar == null", Boolean.FALSE);
660         assertExpression(jc, "foo == null", Boolean.TRUE);
661         assertExpression(jc, "bar != null", Boolean.TRUE);
662         assertExpression(jc, "foo != null", Boolean.FALSE);
663         assertExpression(jc, "empty(bar)", Boolean.FALSE);
664         assertExpression(jc, "empty(foo)", Boolean.TRUE);
665     }
666 
667     /**
668      * test a simple property expression
669      */
670     @Test
671     void testProperty() {
672         /*
673          *  tests a simple property expression
674          */
675 
676         final JexlExpression e = JEXL.createExpression("foo.bar");
677         final JexlContext jc = new MapContext();
678 
679         jc.set("foo", new Foo());
680         final Object o = e.evaluate(jc);
681 
682         assertInstanceOf(String.class, o, "o not instanceof String");
683         assertEquals(GET_METHOD_STRING, o, "o incorrect");
684     }
685 
686     @Test
687     void testSize() {
688         final JexlEvalContext jc = new JexlEvalContext();
689         final JexlOptions options = jc.getEngineOptions();
690         options.setStrict(false);
691         jc.set("s", "five!");
692         jc.set("array", new Object[5]);
693 
694         final Map<String, Integer> map = new HashMap<>();
695 
696         map.put("1", Integer.valueOf(1));
697         map.put("2", Integer.valueOf(2));
698         map.put("3", Integer.valueOf(3));
699         map.put("4", Integer.valueOf(4));
700         map.put("5", Integer.valueOf(5));
701 
702         jc.set("map", map);
703 
704         final List<String> list = new ArrayList<>();
705 
706         list.add("1");
707         list.add("2");
708         list.add("3");
709         list.add("4");
710         list.add("5");
711 
712         jc.set("list", list);
713 
714         // 30652 - support for set
715         final Set<String> set = new HashSet<>(list);
716         set.add("1");
717 
718         jc.set("set", set);
719 
720         // support generic int size() method
721         final BitSet bitset = new BitSet(5);
722         jc.set("bitset", bitset);
723 
724         assertExpression(jc, "size(s)", Integer.valueOf(5));
725         assertExpression(jc, "size(array)", Integer.valueOf(5));
726         assertExpression(jc, "size(list)", Integer.valueOf(5));
727         assertExpression(jc, "size(map)", Integer.valueOf(5));
728         assertExpression(jc, "size(set)", Integer.valueOf(5));
729         assertExpression(jc, "size(bitset)", Integer.valueOf(64));
730         assertExpression(jc, "list.size()", Integer.valueOf(5));
731         assertExpression(jc, "map.size()", Integer.valueOf(5));
732         assertExpression(jc, "set.size()", Integer.valueOf(5));
733         assertExpression(jc, "bitset.size()", Integer.valueOf(64));
734 
735         assertExpression(jc, "list.get(size(list) - 1)", "5");
736         assertExpression(jc, "list[size(list) - 1]", "5");
737         assertExpression(jc, "list.get(list.size() - 1)", "5");
738     }
739 
740     @Test
741     void testSizeAsProperty() {
742         final JexlContext jc = new MapContext();
743         final Map<String, Object> map = new HashMap<>();
744         map.put("size", "cheese");
745         map.put("si & ze", "cheese");
746         jc.set("map", map);
747         jc.set("foo", new Foo());
748 
749         assertExpression(jc, "map['size']", "cheese");
750         assertExpression(jc, "map['si & ze']", "cheese");
751         assertExpression(jc, "map.'si & ze'", "cheese");
752         assertExpression(jc, "map.size()", 2);
753         assertExpression(jc, "size(map)", 2);
754         assertExpression(jc, "foo.getSize()", 22);
755         assertExpression(jc, "foo.'size'", 22);
756     }
757 
758     /**
759      * Simple test of '+' as a string concatenation operator.
760      */
761     @Test
762     void testStringConcatenation() {
763         final JexlContext jc = new MapContext();
764         jc.set("first", "Hello");
765         jc.set("second", "World");
766         assertExpression(jc, "first + ' ' + second", "Hello World");
767     }
768 
769     @Test
770     void testStringLit() {
771         /*
772          *  tests a simple property expression
773          */
774         final JexlContext jc = new MapContext();
775         jc.set("foo", new Foo());
776         assertExpression(jc, "foo.repeat(\"woogie\")", "Repeat : woogie");
777     }
778 
779     /**
780      * Tests string literals
781      */
782     @Test
783     void testStringLiterals() {
784         final JexlContext jc = new MapContext();
785         jc.set("foo", "bar");
786 
787         assertExpression(jc, "foo == \"bar\"", Boolean.TRUE);
788         assertExpression(jc, "foo == 'bar'", Boolean.TRUE);
789     }
790 
791     /**
792      * test quoting in strings
793      */
794     @Test
795     void testStringQuoting() {
796         final JexlContext jc = new MapContext();
797         assertExpression(jc, "'\"Hello\"'", "\"Hello\"");
798         assertExpression(jc, "\"I'm testing\"", "I'm testing");
799     }
800 
801     @Test
802     void testToString() {
803         final String code = "abcd";
804         final JexlExpression expr = JEXL.createExpression(code);
805         assertEquals(code, expr.toString(), "Bad expression value");
806     }
807 
808     @Test
809     void testUnicodeSupport() {
810         final JexlContext jc = new MapContext();
811         assertExpression(jc, "'x' == '\\u0032?ytkownik'", Boolean.FALSE);
812         assertExpression(jc, "'c:\\some\\windows\\path'", "c:\\some\\windows\\path");
813         assertExpression(jc, "'foo\\u0020bar'", "foo\u0020bar");
814         assertExpression(jc, "'foo\\u0020\\u0020bar'", "foo\u0020\u0020bar");
815         assertExpression(jc, "'\\u0020foobar\\u0020'", "\u0020foobar\u0020");
816     }
817 
818     /**
819      * test variables with underscore names
820      */
821     @Test
822     void testVariableNames() {
823         final JexlContext jc = new MapContext();
824         jc.set("foo_bar", "123");
825 
826         assertExpression(jc, "foo_bar", "123");
827     }
828 }