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.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNull;
21 import static org.junit.jupiter.api.Assertions.assertThrows;
22 import static org.junit.jupiter.api.Assertions.fail;
23
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.Future;
30 import java.util.concurrent.TimeUnit;
31
32 import org.junit.jupiter.api.AfterEach;
33 import org.junit.jupiter.api.BeforeEach;
34 import org.junit.jupiter.api.Test;
35
36
37
38
39 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
40 public class CacheTest extends JexlTestCase {
41
42
43
44 public static class AssignBooleanTask extends Task {
45 public AssignBooleanTask(final int loops) {
46 super(loops);
47 }
48
49 @Override
50 public Integer call() throws Exception {
51 return runAssignBoolean(Boolean.TRUE);
52 }
53
54
55 private Integer runAssignBoolean(final Boolean value) {
56 args.value = new Object[]{value};
57 final JexlExpression cacheGetValue = jexl.createExpression("cache.flag");
58 final JexlExpression cacheSetValue = jexl.createExpression("cache.flag = value");
59 Object result;
60
61 for (int l = 0; l < loops; ++l) {
62 final int px = (int) Thread.currentThread().getId();
63 final int mix = MIX[(l + px) % MIX.length];
64
65 vars.put("cache", args.ca[mix]);
66 vars.put("value", args.value[0]);
67 result = cacheSetValue.evaluate(jc);
68 assertEquals(args.value[0], result, cacheSetValue::toString);
69
70 result = cacheGetValue.evaluate(jc);
71 assertEquals(args.value[0], result, cacheSetValue::toString);
72
73 }
74
75 return Integer.valueOf(loops);
76 }
77 }
78
79
80
81 public static class AssignListTask extends Task {
82 public AssignListTask(final int loops) {
83 super(loops);
84 }
85
86 @Override
87 public Integer call() throws Exception {
88 return runAssignList();
89 }
90
91
92 private Integer runAssignList() {
93 args.value = new Object[]{"foo"};
94 final java.util.ArrayList<String> c1 = new java.util.ArrayList<>(2);
95 c1.add("foo");
96 c1.add("bar");
97 args.ca = new Object[]{
98 new String[]{"one", "two"},
99 c1
100 };
101
102 final JexlExpression cacheGetValue = jexl.createExpression("cache.0");
103 final JexlExpression cacheSetValue = jexl.createExpression("cache[0] = value");
104 Object result;
105
106 for (int l = 0; l < loops; ++l) {
107 final int px = (int) Thread.currentThread().getId();
108 final int mix = MIX[(l + px) % MIX.length] % args.ca.length;
109
110 vars.put("cache", args.ca[mix]);
111 vars.put("value", args.value[0]);
112 result = cacheSetValue.evaluate(jc);
113 assertEquals(args.value[0], result, cacheSetValue::toString);
114
115 result = cacheGetValue.evaluate(jc);
116 assertEquals(args.value[0], result, cacheGetValue::toString);
117 }
118
119 return Integer.valueOf(loops);
120 }
121 }
122
123
124
125 public static class AssignNullTask extends Task {
126 public AssignNullTask(final int loops) {
127 super(loops);
128 }
129
130 @Override
131 public Integer call() throws Exception {
132 return runAssign(null);
133 }
134 }
135
136
137
138
139 public static class AssignTask extends Task {
140 public AssignTask(final int loops) {
141 super(loops);
142 }
143
144 @Override
145 public Integer call() throws Exception {
146 return runAssign("foo");
147 }
148 }
149
150
151
152
153
154 public static class Cached {
155 public static String COMPUTE(final int arg) {
156 return "CACHED@i#" + arg;
157 }
158
159 public static String COMPUTE(final int arg0, final int arg1) {
160 return "CACHED@i#" + arg0 + ",i#" + arg1;
161 }
162
163 public static String COMPUTE(String arg) {
164 if (arg == null) {
165 arg = "na";
166 }
167 return "CACHED@s#" + arg;
168 }
169
170 public static String COMPUTE(String arg0, String arg1) {
171 if (arg0 == null) {
172 arg0 = "na";
173 }
174 if (arg1 == null) {
175 arg1 = "na";
176 }
177 return "CACHED@s#" + arg0 + ",s#" + arg1;
178 }
179
180 public String ambiguous(final int arg0, final Integer arg1) {
181 return getClass().getSimpleName() + "!i#" + arg0 + ",i#" + arg1;
182 }
183
184 public String ambiguous(final Integer arg0, final int arg1) {
185 return getClass().getSimpleName() + "!i#" + arg0 + ",i#" + arg1;
186 }
187
188 public String compute(final float arg) {
189 return getClass().getSimpleName() + "@f#" + arg;
190 }
191
192 public String compute(final int arg0, final int arg1) {
193 return getClass().getSimpleName() + "@i#" + arg0 + ",i#" + arg1;
194 }
195
196 public String compute(final Integer arg) {
197 return getClass().getSimpleName() + "@i#" + arg;
198 }
199
200 public String compute(String arg) {
201 if (arg == null) {
202 arg = "na";
203 }
204 return getClass().getSimpleName() + "@s#" + arg;
205 }
206
207 public String compute(String arg0, String arg1) {
208 if (arg0 == null) {
209 arg0 = "na";
210 }
211 if (arg1 == null) {
212 arg1 = "na";
213 }
214 return getClass().getSimpleName() + "@s#" + arg0 + ",s#" + arg1;
215 }
216 }
217 public static class Cached0 extends Cached {
218 protected String value = "Cached0:new";
219 protected Boolean flag = Boolean.FALSE;
220
221 public Cached0() {
222 }
223
224 public String getValue() {
225 return value;
226 }
227
228 public boolean isFlag() {
229 return flag;
230 }
231
232 public void setFlag(final boolean b) {
233 flag = Boolean.valueOf(b);
234 }
235
236 public void setValue(String arg) {
237 if (arg == null) {
238 arg = "na";
239 }
240 value = "Cached0:" + arg;
241 }
242 }
243 public static class Cached1 extends Cached0 {
244 @Override
245 public void setValue(String arg) {
246 if (arg == null) {
247 arg = "na";
248 }
249 value = "Cached1:" + arg;
250 }
251 }
252
253 public static class Cached2 extends Cached {
254 boolean flag;
255 protected String value;
256
257 public Cached2() {
258 value = "Cached2:new";
259 }
260
261 public Object get(final String prop) {
262 if ("value".equals(prop)) {
263 return value;
264 }
265 if ("flag".equals(prop)) {
266 return Boolean.valueOf(flag);
267 }
268 throw new IllegalArgumentException("no such property");
269 }
270
271 public void set(final String p, Object v) {
272 if (v == null) {
273 v = "na";
274 }
275 if ("value".equals(p)) {
276 value = getClass().getSimpleName() + ":" + v;
277 } else if ("flag".equals(p)) {
278 flag = Boolean.parseBoolean(v.toString());
279 } else {
280 throw new IllegalArgumentException("no such property");
281 }
282 }
283 }
284
285 public static class Cached3 extends java.util.TreeMap<String, Object> {
286 private static final long serialVersionUID = 1L;
287 boolean flag;
288
289 public Cached3() {
290 put("value", "Cached3:new");
291 put("flag", "false");
292 }
293
294 @Override
295 public Object get(final Object key) {
296 return super.get(key.toString());
297 }
298
299 public boolean isflag() {
300 return flag;
301 }
302
303 @Override
304 public final Object put(final String key, Object arg) {
305 if (arg == null) {
306 arg = "na";
307 }
308 arg = "Cached3:" + arg;
309 return super.put(key, arg);
310 }
311
312 public void setflag(final boolean b) {
313 flag = b;
314 }
315 }
316
317 public static class Cached4 extends java.util.ArrayList<String> {
318 private static final long serialVersionUID = 1L;
319
320 public Cached4() {
321 super.add("Cached4:new");
322 super.add("false");
323 }
324
325 public String getValue() {
326 return super.get(0);
327 }
328
329 public boolean isflag() {
330 return Boolean.parseBoolean(super.get(1));
331 }
332
333 public void setflag(final Boolean b) {
334 super.set(1, b.toString());
335 }
336
337 public void setValue(String arg) {
338 if (arg == null) {
339 arg = "na";
340 }
341 super.set(0, "Cached4:" + arg);
342 }
343 }
344
345
346
347
348 public static class ComputeTask extends Task {
349 public ComputeTask(final int loops) {
350 super(loops);
351 }
352
353 @Override
354 public Integer call() throws Exception {
355 args.ca = new Object[]{args.c0, args.c1, args.c2};
356 args.value = new Object[]{Integer.valueOf(2), "quux"};
357
358 final JexlExpression compute2 = jexl.createExpression("cache.compute(a0, a1)");
359 final JexlExpression compute1 = jexl.createExpression("cache.compute(a0)");
360 final JexlExpression compute1null = jexl.createExpression("cache.compute(a0)");
361 final JexlExpression ambiguous = jexl.createExpression("cache.ambiguous(a0, a1)");
362
363
364 Object result = null;
365 String expected = null;
366 for (int l = 0; l < loops; ++l) {
367 final int mix = MIX[l % MIX.length] % args.ca.length;
368 final Object value = args.value[l % args.value.length];
369
370 vars.put("cache", args.ca[mix]);
371 if (value instanceof String) {
372 vars.put("a0", "S0");
373 vars.put("a1", "S1");
374 expected = "Cached" + mix + "@s#S0,s#S1";
375 } else if (value instanceof Integer) {
376 vars.put("a0", Integer.valueOf(7));
377 vars.put("a1", Integer.valueOf(9));
378 expected = "Cached" + mix + "@i#7,i#9";
379 } else {
380 fail("unexpected value type");
381 }
382 result = compute2.evaluate(jc);
383 assertEquals(expected, result, compute2::toString);
384
385 if (value instanceof Integer) {
386 vars.put("a0", Short.valueOf((short) 17));
387 vars.put("a1", Short.valueOf((short) 19));
388 assertThrows(JexlException.class, () -> ambiguous.evaluate(jc));
389 }
390
391 if (value instanceof String) {
392 vars.put("a0", "X0");
393 expected = "Cached" + mix + "@s#X0";
394 } else if (value instanceof Integer) {
395 vars.put("a0", Integer.valueOf(5));
396 expected = "Cached" + mix + "@i#5";
397 } else {
398 fail("unexpected value type");
399 }
400 result = compute1.evaluate(jc);
401 assertEquals(expected, result, compute1::toString);
402
403 vars.put("a0", null);
404 final JexlException xany = assertThrows(JexlException.class, () -> compute1null.evaluate(jc));
405
406 final String sany = xany.getMessage();
407 final String tname = getClass().getName();
408 if (!sany.startsWith(tname)) {
409 fail("debug mode should carry caller information, "
410 + sany + ", "
411 + tname);
412 }
413 }
414 return Integer.valueOf(loops);
415 }
416 }
417
418 public static class JexlContextNS extends JexlEvalContext {
419 final Map<String, Object> funcs;
420
421 JexlContextNS(final Map<String, Object> vars, final Map<String, Object> funcs) {
422 super(vars);
423 this.funcs = funcs;
424 }
425
426 @Override
427 public Object resolveNamespace(final String name) {
428 return funcs.get(name);
429 }
430
431 }
432
433
434
435
436 public abstract static class Task implements Callable<Integer> {
437 final TestCacheArguments args = new TestCacheArguments();
438 final int loops;
439 final Map<String, Object> vars = new HashMap<>();
440 final JexlEvalContext jc = new JexlEvalContext(vars);
441
442 Task(final int loops) {
443 this.loops = loops;
444 }
445
446 @Override
447 public abstract Integer call() throws Exception;
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464 public Integer runAssign(final Object value) {
465 args.value = new Object[]{value};
466 Object result;
467
468 final JexlExpression cacheGetValue = jexl.createExpression("cache.value");
469 final JexlExpression cacheSetValue = jexl.createExpression("cache.value = value");
470 for (int l = 0; l < loops; ++l) {
471 final int px = (int) Thread.currentThread().getId();
472 final int mix = MIX[(l + px) % MIX.length];
473
474 vars.put("cache", args.ca[mix]);
475 vars.put("value", args.value[0]);
476 result = cacheSetValue.evaluate(jc);
477 if (args.value[0] == null) {
478 assertNull(result);
479 } else {
480 assertEquals(args.value[0], result, cacheSetValue::toString);
481 }
482
483 result = cacheGetValue.evaluate(jc);
484 if (args.value[0] == null) {
485 assertEquals("Cached" + mix + ":na", result, cacheGetValue::toString);
486 } else {
487 assertEquals("Cached" + mix + ":" + args.value[0], result, cacheGetValue::toString);
488 }
489
490 }
491
492 return Integer.valueOf(loops);
493 }
494 }
495
496
497
498
499 static class TestCacheArguments {
500 Cached0 c0 = new Cached0();
501 Cached1 c1 = new Cached1();
502 Cached2 c2 = new Cached2();
503 Cached3 c3 = new Cached3();
504 Cached4 c4 = new Cached4();
505 Object[] ca = {
506 c0, c1, c2, c3, c4
507 };
508 Object[] value;
509 }
510
511
512 private static final int LOOPS = 4096;
513
514 private static final int NTHREADS = 4;
515
516
517 private static final int[] MIX = {
518 0, 0, 3, 3, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 1, 2, 2, 2,
519 3, 3, 3, 4, 4, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 2, 2, 3, 3, 0
520 };
521
522 private static final JexlEngine jexlCache = new JexlBuilder()
523 .cache(1024)
524 .debug(true)
525 .strict(true)
526 .create();
527
528 private static final JexlEngine jexlNoCache = new JexlBuilder()
529 .cache(0)
530 .debug(true)
531 .strict(true)
532 .create();
533
534 private static JexlEngine jexl = jexlCache;
535
536 public CacheTest() {
537 super("CacheTest", null);
538 }
539
540
541
542
543
544
545
546
547 void doCOMPUTE(final TestCacheArguments x, int loops, final boolean cache) throws Exception {
548 if (loops == 0) {
549 loops = MIX.length;
550 }
551 if (!cache) {
552 jexl.clearCache();
553 }
554 final Map<String, Object> vars = new HashMap<>();
555 final java.util.Map<String, Object> funcs = new java.util.HashMap<>();
556 final JexlEvalContext jc = new JexlContextNS(vars, funcs);
557 final JexlExpression compute2 = jexl.createExpression("cached:COMPUTE(a0, a1)");
558 final JexlExpression compute1 = jexl.createExpression("cached:COMPUTE(a0)");
559 Object result = null;
560 String expected = null;
561 for (int l = 0; l < loops; ++l) {
562 final int mix = MIX[l % MIX.length] % x.ca.length;
563 final Object value = x.value[l % x.value.length];
564
565 funcs.put("cached", x.ca[mix]);
566 if (value instanceof String) {
567 vars.put("a0", "S0");
568 vars.put("a1", "S1");
569 expected = "CACHED@s#S0,s#S1";
570 } else if (value instanceof Integer) {
571 vars.put("a0", Integer.valueOf(7));
572 vars.put("a1", Integer.valueOf(9));
573 expected = "CACHED@i#7,i#9";
574 } else {
575 fail("unexpected value type");
576 }
577 result = compute2.evaluate(jc);
578 assertEquals(expected, result, compute2::toString);
579
580 if (value instanceof String) {
581 vars.put("a0", "X0");
582 expected = "CACHED@s#X0";
583 } else if (value instanceof Integer) {
584 vars.put("a0", Integer.valueOf(5));
585 expected = "CACHED@i#5";
586 } else {
587 fail("unexpected value type");
588 }
589 result = compute1.evaluate(jc);
590 assertEquals(expected, result, compute1::toString);
591 }
592 }
593
594
595
596
597
598
599
600
601 @SuppressWarnings("boxing")
602 void runThreaded(final Class<? extends Task> ctask, int loops, final boolean cache) throws Exception {
603 if (loops == 0) {
604 loops = MIX.length;
605 }
606 if (!cache) {
607 jexl = jexlNoCache;
608 } else {
609 jexl = jexlCache;
610 }
611 final java.util.concurrent.ExecutorService execs = java.util.concurrent.Executors.newFixedThreadPool(NTHREADS);
612 final List<Callable<Integer>> tasks = new ArrayList<>(NTHREADS);
613 for (int t = 0; t < NTHREADS; ++t) {
614 tasks.add(jexl.newInstance(ctask, loops));
615 }
616
617 final List<Future<Integer>> futures = execs.invokeAll(tasks, 60, TimeUnit.SECONDS);
618
619 for (final Future<Integer> future : futures) {
620 assertEquals(Integer.valueOf(loops), future.get());
621 }
622 }
623
624 @BeforeEach
625 @Override
626 public void setUp() throws Exception {
627
628 java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
629 }
630
631 @AfterEach
632 @Override
633 public void tearDown() throws Exception {
634 debuggerCheck(jexl);
635 }
636
637 @Test
638 public void testAssignBooleanCache() throws Exception {
639 runThreaded(AssignBooleanTask.class, LOOPS, true);
640 }
641
642 @Test
643 public void testAssignBooleanNoCache() throws Exception {
644 runThreaded(AssignBooleanTask.class, LOOPS, false);
645 }
646
647 @Test
648 public void testAssignCache() throws Exception {
649 runThreaded(AssignTask.class, LOOPS, true);
650 }
651
652 @Test
653 public void testAssignListCache() throws Exception {
654 runThreaded(AssignListTask.class, LOOPS, true);
655 }
656
657 @Test
658 public void testAssignListNoCache() throws Exception {
659 runThreaded(AssignListTask.class, LOOPS, false);
660 }
661
662 @Test
663 public void testAssignNoCache() throws Exception {
664 runThreaded(AssignTask.class, LOOPS, false);
665 }
666
667 @Test
668 public void testComputeCache() throws Exception {
669 runThreaded(ComputeTask.class, LOOPS, true);
670 }
671
672 @Test
673 public void testCOMPUTECache() throws Exception {
674 final TestCacheArguments args = new TestCacheArguments();
675 args.ca = new Object[]{
676 Cached.class, Cached1.class, Cached2.class
677 };
678 args.value = new Object[]{Integer.valueOf(2), "quux"};
679 doCOMPUTE(args, LOOPS, true);
680 }
681
682 @Test
683 public void testComputeNoCache() throws Exception {
684 runThreaded(ComputeTask.class, LOOPS, false);
685 }
686
687 @Test
688 public void testCOMPUTENoCache() throws Exception {
689 final TestCacheArguments args = new TestCacheArguments();
690 args.ca = new Object[]{
691 Cached.class, Cached1.class, Cached2.class
692 };
693 args.value = new Object[]{Integer.valueOf(2), "quux"};
694 doCOMPUTE(args, LOOPS, false);
695 }
696
697 @Test
698 public void testNullAssignCache() throws Exception {
699 runThreaded(AssignNullTask.class, LOOPS, true);
700 }
701
702 @Test
703 public void testNullAssignNoCache() throws Exception {
704 runThreaded(AssignNullTask.class, LOOPS, false);
705 }
706 }