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.assertFalse;
21 import static org.junit.jupiter.api.Assertions.assertNull;
22 import static org.junit.jupiter.api.Assertions.assertThrows;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24 import static org.junit.jupiter.api.Assertions.fail;
25
26 import java.util.Set;
27 import java.util.TreeSet;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.TimeUnit;
32
33 import org.apache.commons.jexl3.internal.Interpreter;
34 import org.junit.jupiter.api.Test;
35
36
37
38
39 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
40
41 class AnnotationTest extends JexlTestCase {
42
43 public static class AnnotationContext extends MapContext implements JexlContext.AnnotationProcessor {
44 private int count;
45 private final Set<String> names = new TreeSet<>();
46
47 public int getCount() {
48 return count;
49 }
50
51 public Set<String> getNames() {
52 return names;
53 }
54
55 @Override
56 public Object processAnnotation(final String name, final Object[] args, final Callable<Object> statement) throws Exception {
57 count += 1;
58 names.add(name);
59 switch (name) {
60 case "one":
61 names.add(args[0].toString());
62 break;
63 case "two":
64 names.add(args[0].toString());
65 names.add(args[1].toString());
66 break;
67 case "error":
68 names.add(args[0].toString());
69 throw new IllegalArgumentException(args[0].toString());
70 case "unknown":
71 return null;
72 case "synchronized": {
73 if (statement instanceof Interpreter.AnnotatedCall) {
74 final Object sa = ((Interpreter.AnnotatedCall) statement).getStatement();
75 if (sa != null) {
76 synchronized (sa) {
77 return statement.call();
78 }
79 }
80 }
81 final JexlEngine jexl = JexlEngine.getThreadEngine();
82 if (jexl != null) {
83 synchronized (jexl) {
84 return statement.call();
85 }
86 }
87 break;
88 }
89 default:
90 break;
91 }
92 return statement.call();
93 }
94 }
95
96
97
98
99 public static class Counter {
100 private int value;
101
102 public int getValue() {
103 return value;
104 }
105
106 public void inc() {
107 final int v = value;
108
109 for (int i = (int) System.currentTimeMillis() % 5; i >= 0; --i) {
110 Thread.yield();
111 }
112 value = v + 1;
113 }
114 }
115
116 public static class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
117 @Override
118 public Object processAnnotation(final String name, final Object[] args, final Callable<Object> statement) throws Exception {
119 final JexlOptions options = getEngineOptions();
120
121
122
123
124
125 switch (name) {
126 case "strict": {
127 final boolean s = (Boolean) args[0];
128 final boolean b = options.isStrict();
129 options.setStrict(s);
130 final Object r = statement.call();
131 options.setStrict(b);
132 return r;
133 }
134 case "silent": {
135 if (args != null && args.length != 0) {
136 final boolean s = (Boolean) args[0];
137 final boolean b = options.isSilent();
138 options.setSilent(s);
139 assertEquals(s, options.isSilent());
140 final Object r = statement.call();
141 options.setSilent(b);
142 return r;
143 }
144 final boolean b = options.isSilent();
145 try {
146 return statement.call();
147 } catch (final JexlException xjexl) {
148 return null;
149 } finally {
150 options.setSilent(b);
151 }
152 }
153 case "scale":
154 options.setMathScale((Integer) args[0]);
155 return statement.call();
156 default:
157 break;
158 }
159 return statement.call();
160 }
161 }
162
163
164
165
166 public static class TestRunner {
167 public final Counter syncCounter = new Counter();
168 public final Counter concCounter = new Counter();
169
170 public void run(final Runnable runnable) throws InterruptedException {
171 final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
172 for (int i = 0; i < NUM_THREADS; i++) {
173 executor.submit(runnable);
174 }
175 executor.shutdown();
176 executor.awaitTermination(5, TimeUnit.SECONDS);
177
178
179
180 if (NUM_THREADS * NUM_ITERATIONS != concCounter.getValue()) {
181 assertEquals(NUM_THREADS * NUM_ITERATIONS, syncCounter.getValue());
182 }
183 }
184 }
185
186 public static final int NUM_THREADS = 10;
187
188 public static final int NUM_ITERATIONS = 1000;
189
190 public AnnotationTest() {
191 super("AnnotationTest");
192 }
193
194 @Test
195 void test197a() throws Exception {
196 final JexlContext jc = new MapContext();
197 final JexlScript e = JEXL.createScript("@synchronized { return 42; }");
198 final Object r = e.execute(jc);
199 assertEquals(42, r);
200 }
201
202 @Test
203 void testError() throws Exception {
204 testError(true);
205 testError(false);
206 }
207
208 private void testError(final boolean silent) throws Exception {
209 final CaptureLog log = new CaptureLog();
210 final AnnotationContext jc = new AnnotationContext();
211 final JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).create();
212 final JexlScript e = jexl.createScript("@error('42') { return 42; }");
213 try {
214 final Object r = e.execute(jc);
215 if (!silent) {
216 fail("should have failed");
217 } else {
218 assertEquals(1, log.count("warn"));
219 }
220 } catch (final JexlException.Annotation xjexl) {
221 assertEquals("error", xjexl.getAnnotation());
222 }
223 assertEquals(1, jc.getCount());
224 assertTrue(jc.getNames().contains("error"));
225 assertTrue(jc.getNames().contains("42"));
226 if (!silent) {
227 assertEquals(0, log.count("warn"));
228 }
229 }
230
231 @Test
232 void testHoistingStatement() throws Exception {
233 final AnnotationContext jc = new AnnotationContext();
234 final JexlScript e = JEXL.createScript("var t = 1; @synchronized for(var x : [2,3,7]) t *= x; t");
235 final Object r = e.execute(jc);
236 assertEquals(42, r);
237 assertEquals(1, jc.getCount());
238 assertTrue(jc.getNames().contains("synchronized"));
239 }
240
241 @Test
242 void testJexlSynchronized0() throws InterruptedException {
243 final TestRunner tr = new TestRunner();
244 final AnnotationContext ctxt = new AnnotationContext();
245 final JexlScript script = JEXL.createScript(
246 "for(var i : 1..NUM_ITERATIONS) {"
247 + "@synchronized { syncCounter.inc(); }"
248 + "concCounter.inc();"
249 + "}",
250 "NUM_ITERATIONS",
251 "syncCounter",
252 "concCounter");
253
254 tr.run(() -> {
255 script.execute(ctxt, NUM_ITERATIONS, tr.syncCounter, tr.concCounter);
256 });
257 }
258
259 @Test
260 void testMultiple() throws Exception {
261 final AnnotationContext jc = new AnnotationContext();
262 final JexlScript e = JEXL.createScript("@one(1) @synchronized { return 42; }");
263 final Object r = e.execute(jc);
264 assertEquals(42, r);
265 assertEquals(2, jc.getCount());
266 assertTrue(jc.getNames().contains("synchronized"));
267 assertTrue(jc.getNames().contains("one"));
268 assertTrue(jc.getNames().contains("1"));
269 }
270
271 @Test
272 void testNoArg() throws Exception {
273 final AnnotationContext jc = new AnnotationContext();
274 final JexlScript e = JEXL.createScript("@synchronized { return 42; }");
275 final Object r = e.execute(jc);
276 assertEquals(42, r);
277 assertEquals(1, jc.getCount());
278 assertTrue(jc.getNames().contains("synchronized"));
279 }
280
281 @Test
282 void testNoArgExpression() throws Exception {
283 final AnnotationContext jc = new AnnotationContext();
284 final JexlScript e = JEXL.createScript("@synchronized 42");
285 final Object r = e.execute(jc);
286 assertEquals(42, r);
287 assertEquals(1, jc.getCount());
288 assertTrue(jc.getNames().contains("synchronized"));
289 }
290
291 @Test
292 void testNoArgStatement() throws Exception {
293 final AnnotationContext jc = new AnnotationContext();
294 final JexlScript e = JEXL.createScript("@synchronized if (true) 2 * 3 * 7; else -42;");
295 final Object r = e.execute(jc);
296 assertEquals(42, r);
297 assertEquals(1, jc.getCount());
298 assertTrue(jc.getNames().contains("synchronized"));
299 }
300
301 @Test
302 void testOneArg() throws Exception {
303 final AnnotationContext jc = new AnnotationContext();
304 final JexlScript e = JEXL.createScript("@one(1) { return 42; }");
305 final Object r = e.execute(jc);
306 assertEquals(42, r);
307 assertEquals(1, jc.getCount());
308 assertTrue(jc.getNames().contains("one"));
309 assertTrue(jc.getNames().contains("1"));
310 }
311
312 @Test
313
314
315
316 void testSynchronized() throws InterruptedException {
317 final TestRunner tr = new TestRunner();
318 final Counter syncCounter = tr.syncCounter;
319 final Counter concCounter = tr.concCounter;
320 tr.run(() -> {
321 for (int i = 0; i < NUM_ITERATIONS; i++) {
322 synchronized (syncCounter) {
323 syncCounter.inc();
324 }
325 concCounter.inc();
326 }
327 });
328 }
329
330 @Test
331 void testUnknown() throws Exception {
332 testUnknown(true);
333 testUnknown(false);
334 }
335
336 private void testUnknown(final boolean silent) throws Exception {
337 final CaptureLog log = new CaptureLog();
338 final AnnotationContext jc = new AnnotationContext();
339 final JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).create();
340 final JexlScript e = jexl.createScript("@unknown('42') { return 42; }");
341 try {
342 final Object r = e.execute(jc);
343 if (!silent) {
344 fail("should have failed");
345 } else {
346 assertEquals(1, log.count("warn"));
347 }
348 } catch (final JexlException.Annotation xjexl) {
349 assertEquals("unknown", xjexl.getAnnotation());
350 }
351 assertEquals(1, jc.getCount());
352 assertTrue(jc.getNames().contains("unknown"));
353 assertFalse(jc.getNames().contains("42"));
354 if (!silent) {
355 assertEquals(0, log.count("warn"));
356 }
357 }
358
359 @Test
360 void testVarStmt() throws Exception {
361 final OptAnnotationContext jc = new OptAnnotationContext();
362 final JexlOptions options = jc.getEngineOptions();
363 jc.getEngineOptions().set(JEXL);
364 options.setSharedInstance(true);
365 Object r;
366 final JexlScript e = JEXL.createScript("(s, v)->{ @strict(s) @silent(v) var x = y ; 42; }");
367
368
369 r = e.execute(jc, false, true);
370 assertEquals(42, r);
371
372 r = null;
373
374 options.setSafe(false);
375 assertThrows(JexlException.Variable.class, () -> e.execute(jc, true, false));
376
377 r = null;
378
379 r = e.execute(jc, true, true);
380 assertNull(r);
381 options.setSafe(true);
382
383 r = null;
384
385 r = e.execute(jc, false, false);
386 assertEquals(42, r);
387
388 assertTrue(options.isStrict());
389 final JexlScript e2 = JEXL.createScript("@scale(5) 42;");
390 r = e2.execute(jc);
391 assertEquals(42, r);
392 assertTrue(options.isStrict());
393 assertEquals(5, options.getMathScale());
394 }
395 }