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