1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3;
18
19 import java.text.SimpleDateFormat;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Calendar;
23 import java.util.Date;
24 import java.util.HashSet;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.TimeZone;
30
31 import org.apache.commons.jexl3.parser.StringParser;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.junit.Assert;
35 import org.junit.Test;
36
37
38
39
40 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
41 public class VarTest extends JexlTestCase {
42 static final Log LOGGER = LogFactory.getLog(VarTest.class.getName());
43 public static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
44 static {
45 SDF.setTimeZone(TimeZone.getTimeZone("UTC"));
46 }
47
48 public VarTest() {
49 super("VarTest");
50 }
51
52 @Test
53 public void testStrict() throws Exception {
54 final JexlEvalContext env = new JexlEvalContext();
55 final JexlOptions options = env.getEngineOptions();
56 final JexlContext ctxt = new ReadonlyContext(env, options);
57 options.setStrict(true);
58 options.setSilent(false);
59 options.setSafe(false);
60 JexlScript e;
61
62 e = JEXL.createScript("x");
63 try {
64 final Object o = e.execute(ctxt);
65 Assert.fail("should have thrown an unknown var exception");
66 } catch(final JexlException xjexl) {
67
68 }
69 e = JEXL.createScript("x = 42");
70 try {
71 final Object o = e.execute(ctxt);
72 Assert.fail("should have thrown a readonly context exception");
73 } catch(final JexlException xjexl) {
74
75 }
76
77 env.set("x", "fourty-two");
78 e = JEXL.createScript("x.theAnswerToEverything()");
79 try {
80 final Object o = e.execute(ctxt);
81 Assert.fail("should have thrown an unknown method exception");
82 } catch(final JexlException xjexl) {
83
84 }
85 }
86
87 @Test
88 public void testLocalBasic() throws Exception {
89 final JexlScript e = JEXL.createScript("var x; x = 42");
90 final Object o = e.execute(null);
91 Assert.assertEquals("Result is not 42", new Integer(42), o);
92 }
93
94 @Test
95 public void testLocalSimple() throws Exception {
96 final JexlScript e = JEXL.createScript("var x = 21; x + x");
97 final Object o = e.execute(null);
98 Assert.assertEquals("Result is not 42", new Integer(42), o);
99 }
100
101 @Test
102 public void testLocalFor() throws Exception {
103 final JexlScript e = JEXL.createScript("var y = 0; for(var x : [5, 17, 20]) { y = y + x; } y;");
104 final Object o = e.execute(null);
105 Assert.assertEquals("Result is not 42", new Integer(42), o);
106 }
107
108 public static class NumbersContext extends MapContext implements JexlContext.NamespaceResolver {
109 @Override
110 public Object resolveNamespace(final String name) {
111 return name == null ? this : null;
112 }
113
114 public Object numbers() {
115 return new int[]{5, 17, 20};
116 }
117 }
118
119 @Test
120 public void testLocalForFunc() throws Exception {
121 final JexlContext jc = new NumbersContext();
122 final JexlScript e = JEXL.createScript("var y = 0; for(var x : numbers()) { y = y + x; } y;");
123 final Object o = e.execute(jc);
124 Assert.assertEquals("Result is not 42", new Integer(42), o);
125 }
126
127 @Test
128 public void testLocalForFuncReturn() throws Exception {
129 final JexlContext jc = new NumbersContext();
130 final JexlScript e = JEXL.createScript("var y = 42; for(var x : numbers()) { if (x > 10) return x } y;");
131 final Object o = e.execute(jc);
132 Assert.assertEquals("Result is not 17", new Integer(17), o);
133
134 Assert.assertTrue(toString(e.getVariables()), e.getVariables().isEmpty());
135 }
136
137
138
139
140
141
142 String toString(final Set<List<String>> refs) {
143 final StringBuilder strb = new StringBuilder("{");
144 int r = 0;
145 for (final List<String> strs : refs) {
146 if (r++ > 0) {
147 strb.append(", ");
148 }
149 strb.append("{");
150 for (int s = 0; s < strs.size(); ++s) {
151 if (s > 0) {
152 strb.append(", ");
153 }
154 strb.append('"');
155 strb.append(strs.get(s));
156 strb.append('"');
157 }
158 strb.append("}");
159 }
160 strb.append("}");
161 return strb.toString();
162 }
163
164
165
166
167
168
169 Set<List<String>> mkref(final String[][] refs) {
170 final Set<List<String>> set = new HashSet<>();
171 for(final String[] ref : refs) {
172 set.add(Arrays.asList(ref));
173 }
174 return set;
175 }
176
177
178
179
180
181
182
183 boolean eq(final Set<List<String>> lhs, final Set<List<String>> rhs) {
184 if (lhs.size() != rhs.size()) {
185 return false;
186 }
187 final List<String> llhs = stringify(lhs);
188 final List<String> lrhs = stringify(rhs);
189 for(int s = 0; s < llhs.size(); ++s) {
190 final String l = llhs.get(s);
191 final String r = lrhs.get(s);
192 if (!l.equals(r)) {
193 return false;
194 }
195 }
196 return true;
197 }
198
199 List<String> stringify(final Set<List<String>> sls) {
200 final List<String> ls = new ArrayList<>();
201 for(final List<String> l : sls) {
202 final StringBuilder strb = new StringBuilder();
203 for(final String s : l) {
204 strb.append(s);
205 strb.append('|');
206 }
207 ls.add(strb.toString());
208 }
209 ls.sort(null);
210 return ls;
211 }
212
213 @Test
214 public void testRefs() throws Exception {
215 JexlScript e;
216 Set<List<String>> vars;
217 Set<List<String>> expect;
218
219 e = JEXL.createScript("a[b]['c']");
220 vars = e.getVariables();
221 expect = mkref(new String[][]{{"a"},{"b"}});
222 Assert.assertTrue(eq(expect, vars));
223
224 e = JEXL.createScript("a.'b + c'");
225 vars = e.getVariables();
226 expect = mkref(new String[][]{{"a", "b + c"}});
227 Assert.assertTrue(eq(expect, vars));
228
229 e = JEXL.createScript("e[f]");
230 vars = e.getVariables();
231 expect = mkref(new String[][]{{"e"},{"f"}});
232 Assert.assertTrue(eq(expect, vars));
233
234
235 e = JEXL.createScript("e[f][g]");
236 vars = e.getVariables();
237 expect = mkref(new String[][]{{"e"},{"f"},{"g"}});
238 Assert.assertTrue(eq(expect, vars));
239
240 e = JEXL.createScript("e['f'].goo");
241 vars = e.getVariables();
242 expect = mkref(new String[][]{{"e","f","goo"}});
243 Assert.assertTrue(eq(expect, vars));
244
245 e = JEXL.createScript("e['f']");
246 vars = e.getVariables();
247 expect = mkref(new String[][]{{"e","f"}});
248 Assert.assertTrue(eq(expect, vars));
249
250 e = JEXL.createScript("e[f]['g']");
251 vars = e.getVariables();
252 expect = mkref(new String[][]{{"e"},{"f"}});
253 Assert.assertTrue(eq(expect, vars));
254
255 e = JEXL.createScript("e['f']['g']");
256 vars = e.getVariables();
257 expect = mkref(new String[][]{{"e","f","g"}});
258 Assert.assertTrue(eq(expect, vars));
259
260 e = JEXL.createScript("a['b'].c['d'].e");
261 vars = e.getVariables();
262 expect = mkref(new String[][]{{"a", "b", "c", "d", "e"}});
263 Assert.assertTrue(eq(expect, vars));
264
265 e = JEXL.createScript("a + b.c + b.c.d + e['f']");
266 vars = e.getVariables();
267 expect = mkref(new String[][]{{"a"}, {"b", "c"}, {"b", "c", "d"}, {"e", "f"}});
268 Assert.assertTrue(eq(expect, vars));
269
270 e = JEXL.createScript("D[E[F]]");
271 vars = e.getVariables();
272 expect = mkref(new String[][]{{"D"}, {"E"}, {"F"}});
273 Assert.assertTrue(eq(expect, vars));
274
275 e = JEXL.createScript("D[E[F[G[H]]]]");
276 vars = e.getVariables();
277 expect = mkref(new String[][]{{"D"}, {"E"}, {"F"}, {"G"}, {"H"}});
278 Assert.assertTrue(eq(expect, vars));
279
280 e = JEXL.createScript(" A + B[C] + D[E[F]] + x[y[z]] ");
281 vars = e.getVariables();
282 expect = mkref(new String[][]{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"x"} , {"y"}, {"z"}});
283 Assert.assertTrue(eq(expect, vars));
284
285 e = JEXL.createScript(" A + B[C] + D.E['F'] + x[y.z] ");
286 vars = e.getVariables();
287 expect = mkref(new String[][]{{"A"}, {"B"}, {"C"}, {"D", "E", "F"}, {"x"} , {"y", "z"}});
288 Assert.assertTrue(eq(expect, vars));
289
290 e = JEXL.createScript("(A)");
291 vars = e.getVariables();
292 expect = mkref(new String[][]{{"A"}});
293 Assert.assertTrue(eq(expect, vars));
294
295 e = JEXL.createScript("not(A)");
296 vars = e.getVariables();
297 expect = mkref(new String[][]{{"A"}});
298 Assert.assertTrue(eq(expect, vars));
299
300 e = JEXL.createScript("not((A))");
301 vars = e.getVariables();
302 expect = mkref(new String[][]{{"A"}});
303 Assert.assertTrue(eq(expect, vars));
304
305 e = JEXL.createScript("a[b]['c']");
306 vars = e.getVariables();
307 expect = mkref(new String[][]{{"a"}, {"b"}});
308 Assert.assertTrue(eq(expect, vars));
309
310 e = JEXL.createScript("a['b'][c]");
311 vars = e.getVariables();
312 expect = mkref(new String[][]{{"a", "b"}, {"c"}});
313 Assert.assertTrue(eq(expect, vars));
314
315 e = JEXL.createScript("a[b].c");
316 vars = e.getVariables();
317 expect = mkref(new String[][]{{"a"}, {"b"}});
318 Assert.assertTrue(eq(expect, vars));
319
320 e = JEXL.createScript("a[b].c[d]");
321 vars = e.getVariables();
322 expect = mkref(new String[][]{{"a"}, {"b"}, {"d"}});
323 Assert.assertTrue(eq(expect, vars));
324
325 e = JEXL.createScript("a[b][e].c[d][f]");
326 vars = e.getVariables();
327 expect = mkref(new String[][]{{"a"}, {"b"}, {"d"}, {"e"}, {"f"}});
328 Assert.assertTrue(eq(expect, vars));
329 }
330
331 @Test
332 public void testVarCollectNotAll() throws Exception {
333 JexlScript e;
334 Set<List<String>> vars;
335 Set<List<String>> expect;
336 final JexlEngine jexl = new JexlBuilder().strict(true).silent(false).cache(32).collectAll(false).create();
337
338 e = jexl.createScript("a['b'][c]");
339 vars = e.getVariables();
340 expect = mkref(new String[][]{{"a"}, {"c"}});
341 Assert.assertTrue(eq(expect, vars));
342
343 e = jexl.createScript(" A + B[C] + D[E[F]] + x[y[z]] ");
344 vars = e.getVariables();
345 expect = mkref(new String[][]{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"x"} , {"y"}, {"z"}});
346 Assert.assertTrue(eq(expect, vars));
347
348 e = jexl.createScript("e['f']['g']");
349 vars = e.getVariables();
350 expect = mkref(new String[][]{{"e"}});
351 Assert.assertTrue(eq(expect, vars));
352
353 e = jexl.createScript("a[b][e].c[d][f]");
354 vars = e.getVariables();
355 expect = mkref(new String[][]{{"a"}, {"b"}, {"d"}, {"e"}, {"f"}});
356 Assert.assertTrue(eq(expect, vars));
357
358 e = jexl.createScript("a + b.c + b.c.d + e['f']");
359 vars = e.getVariables();
360 expect = mkref(new String[][]{{"a"}, {"b", "c"}, {"b", "c", "d"}, {"e"}});
361 Assert.assertTrue(eq(expect, vars));
362 }
363
364 @Test
365 public void testMix() throws Exception {
366 JexlScript e;
367
368 e = JEXL.createScript("if (x) { y } else { var z = 2 * x}", "x");
369 final Set<List<String>> vars = e.getVariables();
370 final String[] parms = e.getParameters();
371 final String[] locals = e.getLocalVariables();
372
373 Assert.assertTrue(eq(mkref(new String[][]{{"y"}}), vars));
374 Assert.assertEquals(1, parms.length);
375 Assert.assertEquals("x", parms[0]);
376 Assert.assertEquals(1, locals.length);
377 Assert.assertEquals("z", locals[0]);
378 }
379
380
381
382
383 public static class VarDate {
384 private final Calendar cal;
385
386 public VarDate(final String date) throws Exception {
387 this(SDF.parse(date));
388 }
389 public VarDate(final Date date) {
390 cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
391 cal.setTime(date);
392 cal.setLenient(true);
393 }
394
395
396
397
398
399
400 public String get(final String property) {
401 if ("yyyy".equals(property)) {
402 return Integer.toString(cal.get(Calendar.YEAR));
403 }
404 if ("MM".equals(property)) {
405 return Integer.toString(cal.get(Calendar.MONTH) + 1);
406 }
407
408 if ("dd".equals(property)) {
409 return Integer.toString(cal.get(Calendar.DAY_OF_MONTH));
410 }
411 return null;
412 }
413
414
415
416
417
418
419 public List<String> get(final String[] keys) {
420 return get(Arrays.asList(keys));
421 }
422
423
424
425
426
427
428 public List<String> get(final List<String> keys) {
429 final List<String> values = new ArrayList<>();
430 for(final String key : keys) {
431 final String value = get(key);
432 if (value != null) {
433 values.add(value);
434 }
435 }
436 return values;
437 }
438
439
440
441
442
443
444
445
446 public Map<String,Object> get(final Map<String,String> map) {
447 final Map<String,Object> values = new LinkedHashMap<>();
448 for(final Map.Entry<String,String> entry : map.entrySet()) {
449 final String value = get(entry.getKey());
450 if (value != null) {
451 values.put(entry.getValue(), value);
452 }
453 }
454 return values;
455 }
456 }
457
458
459
460
461
462
463 private static String[] readIdentifiers(final String str) {
464 final List<String> ids = new ArrayList<>();
465 StringBuilder strb = null;
466 String id = null;
467 char kind = 0;
468 for (int i = 0; i < str.length(); ++i) {
469 final char c = str.charAt(i);
470
471 if (strb == null) {
472 if (c == '{' || c == '(' || c == '[') {
473 strb = new StringBuilder();
474 kind = c;
475 }
476 continue;
477 }
478
479 if (id != null && c == ']' || c == ')'
480 || (kind != '{' && c == ',')
481 || (kind == '{' && c == ':'))
482 {
483 ids.add(id);
484 id = null;
485 }
486 else if (c == '\'' || c == '"') {
487 strb.append(c);
488 final int l = StringParser.readString(strb, str, i + 1, c);
489 if (l > 0) {
490 id = strb.substring(1, strb.length() - 1);
491 strb.delete(0, l + 1);
492 i = l;
493 }
494 }
495
496 }
497 return ids.toArray(new String[0]);
498 }
499
500 @Test
501 public void testReferenceLiteral() throws Exception {
502 final JexlEngine jexld = new JexlBuilder().collectMode(2).create();
503 JexlScript script;
504 List<String> result;
505 Set<List<String>> vars;
506
507
508 final JexlContext ctxt = new MapContext();
509
510 ctxt.set("moon.landing", new VarDate("1969-07-20"));
511
512 script = jexld.createScript("moon.landing[['yyyy', 'MM', 'dd']]");
513 result = (List<String>) script.execute(ctxt);
514 Assert.assertEquals(Arrays.asList("1969", "7", "20"), result);
515
516 vars = script.getVariables();
517 Assert.assertEquals(1, vars.size());
518 List<String> var = vars.iterator().next();
519 Assert.assertEquals("moon", var.get(0));
520 Assert.assertEquals("landing", var.get(1));
521 Assert.assertArrayEquals(new String[]{"yyyy", "MM", "dd"}, readIdentifiers(var.get(2)));
522
523 script = jexld.createScript("moon.landing[ { 'yyyy' : 'year', 'MM' : 'month', 'dd' : 'day' } ]");
524 final Map<String, String> mapr = (Map<String, String>) script.execute(ctxt);
525 Assert.assertEquals(3, mapr.size());
526 Assert.assertEquals("1969", mapr.get("year"));
527 Assert.assertEquals("7", mapr.get("month"));
528 Assert.assertEquals("20", mapr.get("day"));
529
530 vars = script.getVariables();
531 Assert.assertEquals(1, vars.size());
532 var = vars.iterator().next();
533 Assert.assertEquals("moon", var.get(0));
534 Assert.assertEquals("landing", var.get(1));
535 Assert.assertArrayEquals(new String[]{"yyyy", "MM", "dd"}, readIdentifiers(var.get(2)));
536 }
537
538 @Test
539 public void testLiteral() throws Exception {
540 JexlBuilder builder = new JexlBuilder().collectMode(2);
541 Assert.assertEquals(2, builder.collectMode());
542 Assert.assertTrue(builder.collectAll());
543
544 JexlEngine jexld = builder.create();
545 JexlScript e = jexld.createScript("x.y[['z', 't']]");
546 Set<List<String>> vars = e.getVariables();
547 Assert.assertEquals(1, vars.size());
548 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y", "[ 'z', 't' ]"}}), vars));
549
550 e = jexld.createScript("x.y[{'z': 't'}]");
551 vars = e.getVariables();
552 Assert.assertEquals(1, vars.size());
553 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y", "{ 'z' : 't' }"}}), vars));
554
555 e = jexld.createScript("x.y.'{ \\'z\\' : \\'t\\' }'");
556 vars = e.getVariables();
557 Assert.assertEquals(1, vars.size());
558 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y", "{ 'z' : 't' }"}}), vars));
559
560
561 builder = builder.collectAll(true);
562 Assert.assertEquals(1, builder.collectMode());
563 Assert.assertTrue(builder.collectAll());
564
565 jexld = builder.create();
566 e = jexld.createScript("x.y[{'z': 't'}]");
567 vars = e.getVariables();
568 Assert.assertEquals(1, vars.size());
569 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y"}}), vars));
570
571 e = jexld.createScript("x.y[['z', 't']]");
572 vars = e.getVariables();
573 Assert.assertEquals(1, vars.size());
574 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y"}}), vars));
575
576 e = jexld.createScript("x.y['z']");
577 vars = e.getVariables();
578 Assert.assertEquals(1, vars.size());
579 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y", "z"}}), vars));
580
581 e = jexld.createScript("x.y[42]");
582 vars = e.getVariables();
583 Assert.assertEquals(1, vars.size());
584 Assert.assertTrue(eq(mkref(new String[][]{{"x", "y", "42"}}), vars));
585 }
586
587 @Test
588 public void testSyntacticVariations() throws Exception {
589 final JexlScript script = JEXL.createScript("sum(TOTAL) - partial.sum() + partial['sub'].avg() - sum(partial.sub)");
590 final Set<List<String>> vars = script.getVariables();
591
592 Assert.assertEquals(3, vars.size());
593 }
594
595 public static class TheVarContext {
596 private int x;
597 private String color;
598
599 public void setX(final int x) {
600 this.x = x;
601 }
602
603 public void setColor(final String color) {
604 this.color = color;
605 }
606
607 public int getX() {
608 return x;
609 }
610
611 public String getColor() {
612 return color;
613 }
614 }
615
616 @Test
617 public void testObjectContext() throws Exception {
618 final TheVarContext vars = new TheVarContext();
619 final JexlContext jc = new ObjectContext<>(JEXL, vars);
620 try {
621 JexlScript script;
622 Object result;
623 script = JEXL.createScript("x = 3");
624 result = script.execute(jc);
625 Assert.assertEquals(3, vars.getX());
626 Assert.assertEquals(3, result);
627 script = JEXL.createScript("x == 3");
628 result = script.execute(jc);
629 Assert.assertTrue((Boolean) result);
630 Assert.assertTrue(jc.has("x"));
631
632 script = JEXL.createScript("color = 'blue'");
633 result = script.execute(jc);
634 Assert.assertEquals("blue", vars.getColor());
635 Assert.assertEquals("blue", result);
636 script = JEXL.createScript("color == 'blue'");
637 result = script.execute(jc);
638 Assert.assertTrue((Boolean) result);
639 Assert.assertTrue(jc.has("color"));
640 } catch (final JexlException.Method ambiguous) {
641 Assert.fail("total() is solvable");
642 }
643 }
644
645 }