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