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