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