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 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 .strict(true)
525 .create();
526
527 private static final JexlEngine jexlNoCache = new JexlBuilder()
528 .cache(0)
529 .strict(true)
530 .create();
531
532 private static JexlEngine jexl = jexlCache;
533
534 public CacheTest() {
535 super("CacheTest", null);
536 }
537
538
539
540
541
542
543
544
545 void doCOMPUTE(final TestCacheArguments x, int loops, final boolean cache) throws Exception {
546 if (loops == 0) {
547 loops = MIX.length;
548 }
549 if (!cache) {
550 jexl.clearCache();
551 }
552 final Map<String, Object> vars = new HashMap<>();
553 final java.util.Map<String, Object> funcs = new java.util.HashMap<>();
554 final JexlEvalContext jc = new JexlContextNS(vars, funcs);
555 final JexlExpression compute2 = jexl.createExpression("cached:COMPUTE(a0, a1)");
556 final JexlExpression compute1 = jexl.createExpression("cached:COMPUTE(a0)");
557 Object result = null;
558 String expected = null;
559 for (int l = 0; l < loops; ++l) {
560 final int mix = MIX[l % MIX.length] % x.ca.length;
561 final Object value = x.value[l % x.value.length];
562
563 funcs.put("cached", x.ca[mix]);
564 if (value instanceof String) {
565 vars.put("a0", "S0");
566 vars.put("a1", "S1");
567 expected = "CACHED@s#S0,s#S1";
568 } else if (value instanceof Integer) {
569 vars.put("a0", Integer.valueOf(7));
570 vars.put("a1", Integer.valueOf(9));
571 expected = "CACHED@i#7,i#9";
572 } else {
573 fail("unexpected value type");
574 }
575 result = compute2.evaluate(jc);
576 assertEquals(expected, result, compute2::toString);
577
578 if (value instanceof String) {
579 vars.put("a0", "X0");
580 expected = "CACHED@s#X0";
581 } else if (value instanceof Integer) {
582 vars.put("a0", Integer.valueOf(5));
583 expected = "CACHED@i#5";
584 } else {
585 fail("unexpected value type");
586 }
587 result = compute1.evaluate(jc);
588 assertEquals(expected, result, compute1::toString);
589 }
590 }
591
592
593
594
595
596
597
598
599 @SuppressWarnings("boxing")
600 void runThreaded(final Class<? extends Task> ctask, int loops, final boolean cache) throws Exception {
601 if (loops == 0) {
602 loops = MIX.length;
603 }
604 if (!cache) {
605 jexl = jexlNoCache;
606 } else {
607 jexl = jexlCache;
608 }
609 final java.util.concurrent.ExecutorService execs = java.util.concurrent.Executors.newFixedThreadPool(NTHREADS);
610 final List<Callable<Integer>> tasks = new ArrayList<>(NTHREADS);
611 for (int t = 0; t < NTHREADS; ++t) {
612 tasks.add(jexl.newInstance(ctask, loops));
613 }
614
615 final List<Future<Integer>> futures = execs.invokeAll(tasks, 60, TimeUnit.SECONDS);
616
617 for (final Future<Integer> future : futures) {
618 assertEquals(Integer.valueOf(loops), future.get());
619 }
620 }
621
622 @BeforeEach
623 @Override
624 public void setUp() throws Exception {
625
626 java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
627 }
628
629 @AfterEach
630 @Override
631 public void tearDown() throws Exception {
632 debuggerCheck(jexl);
633 }
634
635 @Test
636 void testAssignBooleanCache() throws Exception {
637 runThreaded(AssignBooleanTask.class, LOOPS, true);
638 }
639
640 @Test
641 void testAssignBooleanNoCache() throws Exception {
642 runThreaded(AssignBooleanTask.class, LOOPS, false);
643 }
644
645 @Test
646 void testAssignCache() throws Exception {
647 runThreaded(AssignTask.class, LOOPS, true);
648 }
649
650 @Test
651 void testAssignListCache() throws Exception {
652 runThreaded(AssignListTask.class, LOOPS, true);
653 }
654
655 @Test
656 void testAssignListNoCache() throws Exception {
657 runThreaded(AssignListTask.class, LOOPS, false);
658 }
659
660 @Test
661 void testAssignNoCache() throws Exception {
662 runThreaded(AssignTask.class, LOOPS, false);
663 }
664
665 @Test
666 void testComputeCache() throws Exception {
667 runThreaded(ComputeTask.class, LOOPS, true);
668 }
669
670 @Test
671 void testCOMPUTECache() throws Exception {
672 final TestCacheArguments args = new TestCacheArguments();
673 args.ca = new Object[]{
674 Cached.class, Cached1.class, Cached2.class
675 };
676 args.value = new Object[]{Integer.valueOf(2), "quux"};
677 doCOMPUTE(args, LOOPS, true);
678 }
679
680 @Test
681 void testComputeNoCache() throws Exception {
682 runThreaded(ComputeTask.class, LOOPS, false);
683 }
684
685 @Test
686 void testCOMPUTENoCache() throws Exception {
687 final TestCacheArguments args = new TestCacheArguments();
688 args.ca = new Object[]{
689 Cached.class, Cached1.class, Cached2.class
690 };
691 args.value = new Object[]{Integer.valueOf(2), "quux"};
692 doCOMPUTE(args, LOOPS, false);
693 }
694
695 @Test
696 void testNullAssignCache() throws Exception {
697 runThreaded(AssignNullTask.class, LOOPS, true);
698 }
699
700 @Test
701 void testNullAssignNoCache() throws Exception {
702 runThreaded(AssignNullTask.class, LOOPS, false);
703 }
704 }