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