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