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