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  
18  package org.apache.commons.jexl3;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.SortedSet;
28  import java.util.TreeSet;
29  import java.util.Calendar;
30  import java.util.Date;
31  import java.util.Locale;
32  import java.util.TimeZone;
33  
34  import org.apache.commons.jexl3.junit.Asserter;
35  import java.io.StringWriter;
36  import java.text.DecimalFormat;
37  import java.text.SimpleDateFormat;
38  
39  import org.junit.Assert;
40  import org.junit.Before;
41  import org.junit.Test;
42  
43  /**
44   * Tests for the startsWith, endsWith, match and range operators.
45   * @since 3.0
46   */
47  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
48  public class ArithmeticOperatorTest extends JexlTestCase {
49      private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
50      private Asserter asserter;
51  
52      @Before
53      @Override
54      public void setUp() {
55          asserter = new Asserter(JEXL);
56          asserter.setStrict(false);
57      }
58  
59      /**
60       * Create the named test.
61       */
62      public ArithmeticOperatorTest() {
63          super("ArithmeticOperatorTest");
64      }
65  
66      @Test
67      public void testRegexp() throws Exception {
68          asserter.setVariable("str", "abc456");
69          asserter.assertExpression("str =~ '.*456'", Boolean.TRUE);
70          asserter.assertExpression("str !~ 'ABC.*'", Boolean.TRUE);
71          asserter.setVariable("match", "abc.*");
72          asserter.setVariable("nomatch", ".*123");
73          asserter.assertExpression("str =~ match", Boolean.TRUE);
74          asserter.assertExpression("str !~ match", Boolean.FALSE);
75          asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
76          asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
77          asserter.setVariable("match", new StringBuilder("abc.*"));
78          asserter.setVariable("nomatch", new StringBuilder(".*123"));
79          asserter.assertExpression("str =~ match", Boolean.TRUE);
80          asserter.assertExpression("str !~ match", Boolean.FALSE);
81          asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
82          asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
83          asserter.setVariable("match", java.util.regex.Pattern.compile("abc.*"));
84          asserter.setVariable("nomatch", java.util.regex.Pattern.compile(".*123"));
85          asserter.assertExpression("str =~ match", Boolean.TRUE);
86          asserter.assertExpression("str !~ match", Boolean.FALSE);
87          asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
88          asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
89          // check the in/not-in variant
90          asserter.assertExpression("'a' =~ ['a','b','c','d','e','f']", Boolean.TRUE);
91          asserter.assertExpression("'a' !~ ['a','b','c','d','e','f']", Boolean.FALSE);
92          asserter.assertExpression("'z' =~ ['a','b','c','d','e','f']", Boolean.FALSE);
93          asserter.assertExpression("'z' !~ ['a','b','c','d','e','f']", Boolean.TRUE);
94      }
95  
96      @Test
97      public void test391() throws Exception {
98          // with literals
99          for(String src : Arrays.asList(
100                 "2 =~ [1, 2, 3, 4]",
101                 "[2, 3] =~ [1, 2, 3, 4]",
102                 "[2, 3,...] =~ [1, 2, 3, 4]",
103                 "3 =~ [1, 2, 3, 4,...]",
104                 "[2, 3] =~ [1, 2, 3, 4,...]",
105                 "[2, 3,...] =~ [1, 2, 3, 4,...]")) {
106             asserter.assertExpression(src, Boolean.TRUE);
107         }
108         // with variables
109         int[] ic = new int[]{1, 2,  3, 4};
110         List<Integer> iic = new ArrayList<>();
111         for(int v : ic) { iic.add(v); }
112         int[] iv = new int[]{2, 3};
113         List<Integer> iiv = new ArrayList<>();
114         for(int v : iv) { iiv.add(v); }
115         String src = "(x,y) -> x =~ y ";
116         for(Object v : Arrays.asList(iv, iiv, 2)) {
117             for(Object c : Arrays.asList(ic, iic)) {
118                 asserter.assertExpression(src, Boolean.TRUE, v, c);
119             }
120         }
121     }
122 
123     @Test
124     public void testRegexp2() throws Exception {
125         asserter.setVariable("str", "abc456");
126         asserter.assertExpression("str =~ ~/.*456/", Boolean.TRUE);
127         asserter.assertExpression("str !~ ~/ABC.*/", Boolean.TRUE);
128         asserter.assertExpression("str =~ ~/abc\\d{3}/", Boolean.TRUE);
129         // legacy, deprecated
130         asserter.assertExpression("matches(str, ~/.*456/)", Boolean.TRUE);
131         asserter.setVariable("str", "4/6");
132         asserter.assertExpression("str =~ ~/\\d\\/\\d/", Boolean.TRUE);
133     }
134 
135     @Test
136     public void testStartsEndsWithString() throws Exception {
137         asserter.setVariable("x", "foobar");
138         asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
139         asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
140         asserter.setVariable("x", "barfoo");
141         asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
142         asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
143     }
144 
145     @Test
146     public void testStartsEndsWithStringDot() throws Exception {
147         asserter.setVariable("x.y", "foobar");
148         asserter.assertExpression("x.y =^ 'foo'", Boolean.TRUE);
149         asserter.assertExpression("x.y =$ 'foo'", Boolean.FALSE);
150         asserter.setVariable("x.y", "barfoo");
151         asserter.assertExpression("x.y =^ 'foo'", Boolean.FALSE);
152         asserter.assertExpression("x.y =$ 'foo'", Boolean.TRUE);
153     }
154 
155     @Test
156     public void testNotStartsEndsWithString() throws Exception {
157         asserter.setVariable("x", "foobar");
158         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
159         asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
160         asserter.setVariable("x", "barfoo");
161         asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
162         asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
163     }
164 
165     @Test
166     public void testNotStartsEndsWithStringDot() throws Exception {
167         asserter.setVariable("x.y", "foobar");
168         asserter.assertExpression("x.y !^ 'foo'", Boolean.FALSE);
169         asserter.assertExpression("x.y !$ 'foo'", Boolean.TRUE);
170         asserter.setVariable("x.y", "barfoo");
171         asserter.assertExpression("x.y !^ 'foo'", Boolean.TRUE);
172         asserter.assertExpression("x.y !$ 'foo'", Boolean.FALSE);
173     }
174 
175     @Test
176     public void testStartsEndsWithStringBuilder() throws Exception {
177         asserter.setVariable("x", new StringBuilder("foobar"));
178         asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
179         asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
180         asserter.setVariable("x", new StringBuilder("barfoo"));
181         asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
182         asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
183     }
184 
185     @Test
186     public void testNotStartsEndsWithStringBuilder() throws Exception {
187         asserter.setVariable("x", new StringBuilder("foobar"));
188         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
189         asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
190         asserter.setVariable("x", new StringBuilder("barfoo"));
191         asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
192         asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
193     }
194 
195     public static class MatchingContainer {
196         private final Set<Integer> values;
197 
198         public MatchingContainer(final int[] is) {
199             values = new HashSet<>();
200             for (final int value : is) {
201                 values.add(value);
202             }
203         }
204 
205         public boolean contains(final int value) {
206             return values.contains(value);
207         }
208     }
209 
210     public static class IterableContainer implements Iterable<Integer> {
211         private final SortedSet<Integer> values;
212 
213         public IterableContainer(final int[] is) {
214             values = new TreeSet<>();
215             for (final int value : is) {
216                 values.add(value);
217             }
218         }
219 
220         @Override
221         public Iterator<Integer> iterator() {
222             return values.iterator();
223         }
224 
225         public boolean contains(final int i) {
226             return values.contains(i);
227         }
228 
229         public boolean contains(final int[] i) {
230             for(int ii : i) if (!values.contains(ii)) return false;
231             return true;
232         }
233 
234         public boolean startsWith(final int i) {
235             return values.first().equals(i);
236         }
237 
238         public boolean endsWith(final int i) {
239             return values.last().equals(i);
240         }
241 
242         public boolean startsWith(final int[] i) {
243             final SortedSet<Integer> sw =  values.headSet(i.length);
244             int n = 0;
245             for(final Integer value : sw) {
246                 if(!value.equals(i[n++])) {
247                     return false;
248                 }
249             }
250             return true;
251         }
252         public boolean endsWith(final int[] i) {
253             final SortedSet<Integer> sw =  values.tailSet(values.size() - i.length);
254             int n = 0;
255             for(final Integer value : sw) {
256                 if(!value.equals(i[n++])) {
257                     return false;
258                 }
259             }
260             return true;
261         }
262     }
263 
264     @Test
265     public void testMatch() throws Exception {
266         // check in/not-in on array, list, map, set and duck-type collection
267         final int[] ai = {2, 4, 42, 54};
268         final List<Integer> al = new ArrayList<>();
269         for (final int i : ai) {
270             al.add(i);
271         }
272         final Map<Integer, String> am = new HashMap<>();
273         am.put(2, "two");
274         am.put(4, "four");
275         am.put(42, "forty-two");
276         am.put(54, "fifty-four");
277         final MatchingContainer ad = new MatchingContainer(ai);
278         final IterableContainer ic = new IterableContainer(ai);
279         final Set<Integer> as = ad.values;
280         final Object[] vars = {ai, al, am, ad, as, ic};
281 
282         for (final Object var : vars) {
283             asserter.setVariable("container", var);
284             for (final int x : ai) {
285                 asserter.setVariable("x", x);
286                 asserter.assertExpression("x =~ container", Boolean.TRUE);
287             }
288             asserter.setVariable("x", 169);
289             asserter.assertExpression("x !~ container", Boolean.TRUE);
290         }
291     }
292 
293     @Test
294     public void testStartsEndsWith() throws Exception {
295         asserter.setVariable("x", "foobar");
296         asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
297         asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
298         asserter.setVariable("x", "barfoo");
299         asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
300         asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
301 
302         final int[] ai = {2, 4, 42, 54};
303         final IterableContainer ic = new IterableContainer(ai);
304         asserter.setVariable("x", ic);
305         asserter.assertExpression("x =^ 2", Boolean.TRUE);
306         asserter.assertExpression("x =$ 54", Boolean.TRUE);
307         asserter.assertExpression("x =^ 4", Boolean.FALSE);
308         asserter.assertExpression("x =$ 42", Boolean.FALSE);
309         asserter.assertExpression("x =^ [2, 4]", Boolean.TRUE);
310         asserter.assertExpression("x =^ [42, 54]", Boolean.TRUE);
311     }
312 
313     @Test
314     public void testNotStartsEndsWith() throws Exception {
315         asserter.setVariable("x", "foobar");
316         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
317         asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
318         asserter.setVariable("x", "barfoo");
319         asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
320         asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
321 
322         final int[] ai = {2, 4, 42, 54};
323         final IterableContainer ic = new IterableContainer(ai);
324         asserter.setVariable("x", ic);
325         asserter.assertExpression("x !^ 2", Boolean.FALSE);
326         asserter.assertExpression("x !$ 54", Boolean.FALSE);
327         asserter.assertExpression("x !^ 4", Boolean.TRUE);
328         asserter.assertExpression("x !$ 42", Boolean.TRUE);
329         asserter.assertExpression("x !^ [2, 4]", Boolean.FALSE);
330         asserter.assertExpression("x !^ [42, 54]", Boolean.FALSE);
331     }
332 
333     public static class Aggregate {
334         private Aggregate() {}
335         public static int sum(final Iterable<Integer> ii) {
336             int sum = 0;
337             for(final Integer i : ii) {
338                 sum += i;
339             }
340             return sum;
341         }
342     }
343 
344     @Test
345     @SuppressWarnings("unchecked")
346     public void testInterval() throws Exception {
347         final Map<String, Object> ns = new HashMap<>();
348         ns.put("calc", Aggregate.class);
349         final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
350         JexlScript script;
351         Object result;
352 
353         script = jexl.createScript("1 .. 3");
354         result = script.execute(null);
355         Assert.assertTrue(result instanceof Iterable<?>);
356         Iterator<Integer> ii = ((Iterable<Integer>) result).iterator();
357         Assert.assertEquals(Integer.valueOf(1), ii.next());
358         Assert.assertEquals(Integer.valueOf(2), ii.next());
359         Assert.assertEquals(Integer.valueOf(3), ii.next());
360 
361         script = jexl.createScript("(4 - 3) .. (9 / 3)");
362         result = script.execute(null);
363         Assert.assertTrue(result instanceof Iterable<?>);
364         ii = ((Iterable<Integer>) result).iterator();
365         Assert.assertEquals(Integer.valueOf(1), ii.next());
366         Assert.assertEquals(Integer.valueOf(2), ii.next());
367         Assert.assertEquals(Integer.valueOf(3), ii.next());
368 
369         // sum of 1, 2, 3
370         script = jexl.createScript("var x = 0; for(var y : ((5 - 4) .. (12 / 4))) { x = x + y }; x");
371         result = script.execute(null);
372         Assert.assertEquals(Integer.valueOf(6), result);
373 
374         script = jexl.createScript("calc:sum(1 .. 3)");
375         result = script.execute(null);
376         Assert.assertEquals(Integer.valueOf(6), result);
377 
378         script = jexl.createScript("calc:sum(-3 .. 3)");
379         result = script.execute(null);
380         Assert.assertEquals(Integer.valueOf(0), result);
381     }
382 
383     public static class DateArithmetic extends JexlArithmetic {
384         DateArithmetic(final boolean flag) {
385             super(flag);
386         }
387 
388         protected Object getDateValue(final Date date, final String key) {
389             try {
390                 final Calendar cal = Calendar.getInstance(UTC);
391                 cal.setTime(date);
392                 if ("yyyy".equals(key)) {
393                     return cal.get(Calendar.YEAR);
394                 }
395                 if ("MM".equals(key)) {
396                     return cal.get(Calendar.MONTH) + 1;
397                 }
398                 if ("dd".equals(key)) {
399                     return cal.get(Calendar.DAY_OF_MONTH);
400                 }
401                 // Otherwise treat as format mask
402                 final SimpleDateFormat df = new SimpleDateFormat(key);//, dfs);
403                 return df.format(date);
404 
405             } catch (final Exception ex) {
406                 return null;
407             }
408         }
409 
410         protected Object setDateValue(final Date date, final String key, final Object value) throws Exception {
411             final Calendar cal = Calendar.getInstance(UTC);
412             cal.setTime(date);
413             if ("yyyy".equals(key)) {
414                 cal.set(Calendar.YEAR, toInteger(value));
415             } else if ("MM".equals(key)) {
416                 cal.set(Calendar.MONTH, toInteger(value) - 1);
417             } else if ("dd".equals(key)) {
418                 cal.set(Calendar.DAY_OF_MONTH, toInteger(value));
419             }
420             date.setTime(cal.getTimeInMillis());
421             return date;
422         }
423 
424         public Object propertyGet(final Date date, final String identifier) {
425             return getDateValue(date, identifier);
426         }
427 
428         public Object propertySet(final Date date, final String identifier, final Object value) throws Exception {
429             return setDateValue(date, identifier, value);
430         }
431 
432         public Object arrayGet(final Date date, final String identifier) {
433             return getDateValue(date, identifier);
434         }
435 
436         public Object arraySet(final Date date, final String identifier, final Object value) throws Exception {
437             return setDateValue(date, identifier, value);
438         }
439 
440         public Date now() {
441             return new Date(System.currentTimeMillis());
442         }
443 
444         public Date multiply(final Date d0, final Date d1) {
445             throw new ArithmeticException("unsupported");
446         }
447     }
448 
449     public static class DateContext extends MapContext {
450         private Locale locale = Locale.US;
451 
452         void setLocale(final Locale l10n) {
453             this.locale = l10n;
454         }
455 
456         public String format(final Date date, final String fmt) {
457             final SimpleDateFormat sdf = new SimpleDateFormat(fmt, locale);
458             sdf.setTimeZone(UTC);
459             return sdf.format(date);
460         }
461 
462         public String format(final Number number, final String fmt) {
463             return new DecimalFormat(fmt).format(number);
464         }
465     }
466 
467     @Test
468     public void testOperatorError() throws Exception {
469         testOperatorError(true);
470         testOperatorError(false);
471     }
472 
473     private void testOperatorError(final boolean silent) throws Exception {
474         final CaptureLog log = new CaptureLog();
475         final DateContext jc = new DateContext();
476         final Date d = new Date();
477         final JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).cache(32)
478                                            .arithmetic(new DateArithmetic(true)).create();
479         final JexlScript expr0 = jexl.createScript("date * date", "date");
480         try {
481             final Object value0 = expr0.execute(jc, d);
482             if (!silent) {
483                 Assert.fail("should have failed");
484             } else {
485                 Assert.assertEquals(1, log.count("warn"));
486             }
487         } catch(final JexlException.Operator xop) {
488             Assert.assertEquals("*", xop.getSymbol());
489         }
490         if (!silent) {
491             Assert.assertEquals(0, log.count("warn"));
492         }
493     }
494 
495     @Test
496     public void testDateArithmetic() throws Exception {
497         final Date d = new Date();
498         final JexlContext jc = new MapContext();
499         final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new DateArithmetic(true)).create();
500         final JexlScript expr0 = jexl.createScript("date.yyyy = 1969; date.MM=7; date.dd=20; ", "date");
501         Object value0 = expr0.execute(jc, d);
502         Assert.assertNotNull(value0);
503         value0 = d;
504         //d = new Date();
505         Assert.assertEquals(1969, jexl.createScript("date.yyyy", "date").execute(jc, value0));
506         Assert.assertEquals(7, jexl.createScript("date.MM", "date").execute(jc, value0));
507         Assert.assertEquals(20, jexl.createScript("date.dd", "date").execute(jc, value0));
508     }
509 
510     @Test
511     public void testFormatArithmetic() throws Exception {
512         final Calendar cal = Calendar.getInstance(UTC);
513         cal.set(1969, Calendar.AUGUST, 20);
514         final Date x0 = cal.getTime();
515         final String y0 =  "MM/yy/dd";
516         final Number x1 = 42.12345;
517         final String y1 = "##0.##";
518         final DateContext jc = new DateContext();
519         final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new DateArithmetic(true)).create();
520         final JexlScript expr0 = jexl.createScript("x.format(y)", "x", "y");
521         Object value10 = expr0.execute(jc, x0, y0);
522         final Object value20 = expr0.execute(jc, x0, y0);
523         Assert.assertEquals(value10, value20);
524         Object value11 = expr0.execute(jc, x1, y1);
525         final Object value21 = expr0.execute(jc, x1, y1);
526         Assert.assertEquals(value11, value21);
527         value10 = expr0.execute(jc, x0, y0);
528         Assert.assertEquals(value10, value20);
529         value11 = expr0.execute(jc, x1, y1);
530         Assert.assertEquals(value11, value21);
531         value10 = expr0.execute(jc, x0, y0);
532         Assert.assertEquals(value10, value20);
533         value11 = expr0.execute(jc, x1, y1);
534         Assert.assertEquals(value11, value21);
535 
536         JexlScript expr1 = jexl.createScript("format(x, y)", "x", "y");
537         value10 = expr1.execute(jc, x0, y0);
538         Assert.assertEquals(value10, value20);
539         Object s0 = expr1.execute(jc, x0, "EEE dd MMM yyyy");
540         Assert.assertEquals("Wed 20 Aug 1969", s0);
541         jc.setLocale(Locale.FRANCE);
542         s0 = expr1.execute(jc, x0, "EEE dd MMM yyyy");
543         Assert.assertEquals("mer. 20 ao\u00fbt 1969", s0);
544 
545         expr1 = jexl.createScript("format(now(), y)", "y");
546         final Object n0 = expr1.execute(jc, y0);
547         Assert.assertNotNull(n0);
548         expr1 = jexl.createScript("now().format(y)", "y");
549         final Object n1 = expr1.execute(jc, y0);
550         Assert.assertNotNull(n0);
551         Assert.assertEquals(n0, n1);
552     }
553 
554     @Test
555     public void testFormatArithmeticJxlt() throws Exception {
556         final Map<String, Object> ns = new HashMap<>();
557         ns.put("calc", Aggregate.class);
558         final Calendar cal = Calendar.getInstance(UTC);
559         cal.set(1969, Calendar.AUGUST, 20);
560         final Date x0 = cal.getTime();
561         final String y0 =  "yyyy-MM-dd";
562         final DateContext jc = new DateContext();
563         final JexlEngine jexl = new JexlBuilder().cache(32).namespaces(ns).arithmetic(new DateArithmetic(true)).create();
564         final JxltEngine jxlt = jexl.createJxltEngine();
565 
566         JxltEngine.Template expr0 = jxlt.createTemplate("${x.format(y)}", "x", "y");
567         StringWriter strw = new StringWriter();
568         expr0.evaluate(jc, strw, x0, y0);
569         String strws = strw.toString();
570         Assert.assertEquals("1969-08-20", strws);
571 
572         expr0 = jxlt.createTemplate("${calc:sum(x .. y)}", "x", "y");
573         strw = new StringWriter();
574         expr0.evaluate(jc, strw, 1, 3);
575         strws = strw.toString();
576         Assert.assertEquals("6", strws);
577 
578         final JxltEngine.Template expr1 = jxlt.createTemplate("${jexl:include(s, x, y)}", "s", "x", "y");
579         strw = new StringWriter();
580         expr1.evaluate(jc, strw, expr0, 1, 3);
581         strws = strw.toString();
582         Assert.assertEquals("6", strws);
583 
584         expr0 = jxlt.createTemplate("${now().format(y)}", "y");
585         strw = new StringWriter();
586         expr0.evaluate(jc, strw, y0);
587         strws = strw.toString();
588         Assert.assertNotNull(strws);
589     }
590 
591     @Test public void test373a() {
592         testSelfAssignOperators("y.add(x++)", 42, 42, 43);
593     }
594     @Test public void test373b() {
595         testSelfAssignOperators("y.add(++x)", 42, 43, 43);
596     }
597     @Test public void test373c() {
598         testSelfAssignOperators("y.add(x--)", 42, 42, 41);
599     }
600     @Test public void test373d() {
601         testSelfAssignOperators("y.add(--x)", 42, 41, 41);
602     }
603 
604     void testSelfAssignOperators(String text, int x, int y0, int x0) {
605         //String text = "y.add(x++)";
606         JexlEngine jexl = new JexlBuilder().safe(true).create();
607         JexlScript script = jexl.createScript(text);
608         JexlContext context = new MapContext();
609         context.set("x", x);
610         List<Number> y = new ArrayList<>();
611         context.set("y", y);
612         Object result = script.execute(context);
613         Assert.assertEquals("x0", x0, context.get("x"));
614         Assert.assertEquals("y0", y0, y.get(0));
615     }
616 
617     @Test
618     public void testIncrementOperatorOnNull() throws Exception {
619         final JexlEngine jexl = new JexlBuilder().strict(false).create();
620         JexlScript script;
621         Object result;
622         script = jexl.createScript("var i = null; ++i");
623         result = script.execute(null);
624         Assert.assertEquals(1, result);
625         script = jexl.createScript("var i = null; --i");
626         result = script.execute(null);
627         Assert.assertEquals(-1, result);
628     }
629 
630 }