This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 8f5fd67884e6880c0062392d662fc62f0fc946e7 Author: henrib <hen...@apache.org> AuthorDate: Thu Jul 16 10:53:38 2020 +0200 JEXL-288: public class exposing annotated statement call, various final nitpicks Task #JEXL-288 - Annotation can not be specified for a standalone statement --- .../org/apache/commons/jexl3/internal/Frame.java | 2 +- .../apache/commons/jexl3/internal/Interpreter.java | 68 +++++++++++---- .../org/apache/commons/jexl3/internal/Scope.java | 2 +- .../org/apache/commons/jexl3/AnnotationTest.java | 97 ++++++++++++++++++++++ 4 files changed, 153 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/internal/Frame.java b/src/main/java/org/apache/commons/jexl3/internal/Frame.java index cb27170..9fe9ac8 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Frame.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java @@ -28,7 +28,7 @@ public final class Frame { /** The actual stack frame. */ private final Object[] stack; /** Number of curried parameters. */ - private int curried = 0; + private final int curried; /** * Creates a new frame. diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java index 10679ac..5b31987 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -1816,6 +1816,57 @@ public class Interpreter extends InterpreterBase { } /** + * An annotated call. + */ + public class AnnotatedCall implements Callable<Object> { + /** The statement. */ + private final ASTAnnotatedStatement stmt; + /** The child index. */ + private final int index; + /** The data. */ + private final Object data; + /** Tracking whether we processed the annotation. */ + private boolean processed = false; + + /** + * Simple ctor. + * @param astmt the statement + * @param aindex the index + * @param adata the data + */ + AnnotatedCall(final ASTAnnotatedStatement astmt, final int aindex, final Object adata) { + stmt = astmt; + index = aindex; + data = adata; + } + + + @Override + public Object call() throws Exception { + processed = true; + try { + return processAnnotation(stmt, index, data); + } catch (JexlException.Return | JexlException.Break | JexlException.Continue xreturn) { + return xreturn; + } + } + + /** + * @return whether the statement has been processed + */ + public boolean isProcessed() { + return processed; + } + + /** + * @return the actual statement. + */ + public Object getStatement() { + return stmt; + } + } + + /** * Processes an annotated statement. * @param stmt the statement * @param index the index of the current annotation being processed @@ -1846,18 +1897,7 @@ public class Interpreter extends InterpreterBase { } } // tracking whether we processed the annotation - final boolean[] processed = new boolean[]{false}; - final Callable<Object> jstmt = new Callable<Object>() { - @Override - public Object call() throws Exception { - processed[0] = true; - try { - return processAnnotation(stmt, index + 1, data); - } catch(JexlException.Return | JexlException.Continue | JexlException.Break xreturn) { - return xreturn; - } - } - }; + final AnnotatedCall jstmt = new AnnotatedCall(stmt, index + 1, data); // the annotation node and name final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index); final String aname = anode.getName(); @@ -1869,7 +1909,7 @@ public class Interpreter extends InterpreterBase { try { result = processAnnotation(aname, argv, jstmt); // not processing an annotation is an error - if (!processed[0]) { + if (!jstmt.isProcessed()) { return annotationError(anode, aname, null); } } catch (JexlException xany) { @@ -1893,7 +1933,7 @@ public class Interpreter extends InterpreterBase { * @throws Exception if anything goes wrong */ protected Object processAnnotation(String annotation, Object[] args, Callable<Object> stmt) throws Exception { - return context instanceof JexlContext.AnnotationProcessor + return context instanceof JexlContext.AnnotationProcessor ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt) : stmt.call(); } diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java b/src/main/java/org/apache/commons/jexl3/internal/Scope.java index 2a5b9b9..0434de2 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java @@ -47,7 +47,7 @@ public final class Scope { /** * The parent scope. */ - private Scope parent = null; + private final Scope parent; /** * The number of parameters. */ diff --git a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java index b299179..e9ea1cb 100644 --- a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java +++ b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java @@ -19,6 +19,10 @@ package org.apache.commons.jexl3; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.apache.commons.jexl3.internal.Interpreter; import org.junit.Assert; import org.junit.Test; @@ -30,6 +34,9 @@ import org.junit.Test; public class AnnotationTest extends JexlTestCase { + public final static int NUM_THREADS = 10; + public final static int NUM_ITERATIONS = 1000; + public AnnotationTest() { super("AnnotationTest"); } @@ -60,6 +67,21 @@ public class AnnotationTest extends JexlTestCase { throw new IllegalArgumentException(args[0].toString()); } else if ("unknown".equals(name)) { return null; + } else if ("synchronized".equals(name)) { + if (statement instanceof Interpreter.AnnotatedCall) { + Object sa = ((Interpreter.AnnotatedCall) statement).getStatement(); + if (sa != null) { + synchronized (sa) { + return statement.call(); + } + } + } + final JexlEngine jexl = JexlEngine.getThreadEngine(); + if (jexl != null) { + synchronized (jexl) { + return statement.call(); + } + } } return statement.call(); } @@ -291,4 +313,79 @@ public class AnnotationTest extends JexlTestCase { Assert.assertEquals(0, log.count("warn")); } } + + /** + * A counter whose inc method will misbehave if not mutex-ed. + */ + public static class Counter { + private int value = 0; + + public void inc() { + int v = value; + // introduce some concurency + for (int i = (int) System.currentTimeMillis() % 5; i >= 0; --i) { + Thread.yield(); + } + value = v + 1; + } + + public int getValue() { + return value; + } + } + + /** + * Runs a counter test with n-thread in //. + */ + public static class TestRunner { + public final Counter syncCounter = new Counter(); + public final Counter concCounter = new Counter(); + + public void run(Runnable runnable) throws InterruptedException { + ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); + for (int i = 0; i < NUM_THREADS; i++) { + executor.submit(runnable); + } + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + Assert.assertEquals(NUM_THREADS * NUM_ITERATIONS, syncCounter.getValue()); + Assert.assertNotEquals(NUM_THREADS * NUM_ITERATIONS, concCounter.getValue()); + } + } + + @Test + /** + * A base test to ensure synchronized makes a difference. + */ + public void testSynchronized() throws InterruptedException { + final TestRunner tr = new TestRunner(); + final Counter syncCounter = tr.syncCounter; + final Counter concCounter = tr.concCounter; + tr.run(() -> { + for (int i = 0; i < NUM_ITERATIONS; i++) { + synchronized (syncCounter) { + syncCounter.inc(); + } + concCounter.inc(); + } + }); + } + + @Test + public void testJexlSynchronized0() throws InterruptedException { + final TestRunner tr = new TestRunner(); + final AnnotationContext ctxt = new AnnotationContext(); + final JexlScript script = JEXL.createScript( + "for(var i : 1..NUM_ITERATIONS) {" + + "@synchronized { syncCounter.inc(); }" + + "concCounter.inc();" + + "}", + "NUM_ITERATIONS", + "syncCounter", + "concCounter"); + // will sync on syncCounter + tr.run(() -> { + script.execute(ctxt, NUM_ITERATIONS, tr.syncCounter, tr.concCounter); + }); + } }