You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2016/07/03 17:27:00 UTC

svn commit: r1751163 - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl3/ main/java/org/apache/commons/jexl3/internal/ main/java/org/apache/commons/jexl3/parser/ test/java/org/apache/commons/jexl3/

Author: henrib
Date: Sun Jul  3 17:26:59 2016
New Revision: 1751163

URL: http://svn.apache.org/viewvc?rev=1751163&view=rev
Log:
JEXL: 
Initial code for annotations JEXL-197 - @syntax
The @default part of the specification is not implemented (and probably wont) to avoid incurring a prohibitive evaluation cost);
The JexStatement/Interceptor parts have been simplified with Callable and JexlContext.AnnotationProcessor.

Added:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java
      - copied, changed from r1719037, commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AnnotationTest.java   (with props)
Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/JXLTTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java Sun Jul  3 17:26:59 2016
@@ -17,28 +17,30 @@
 
 package org.apache.commons.jexl3;
 
+import java.util.concurrent.Callable;
+
 /**
  * Manages variables which can be referenced in a JEXL expression.
- * 
+ *
  * <p>JEXL variable names in their simplest form are 'java-like' identifiers.
  * JEXL also considers 'ant' inspired variables expressions as valid.
  * For instance, the expression 'x.y.z' is an 'antish' variable and will be resolved as a whole by the context,
  * i.e. using the key "x.y.z". This proves to be useful to solve "fully qualified class names".</p>
- * 
+ *
  * <p>The interpreter variable resolution algorithm will try the different sequences of identifiers till it finds
  * one that exists in the context; if "x" is an object known in the context (JexlContext.has("x") returns true),
  * "x.y" will <em>not</em> be looked up in the context but will most likely refer to "x.getY()".</p>
- * 
+ *
  * <p>Note that JEXL may use '$jexl' and '$ujexl' variables for internal purpose; setting or getting those
  * variables may lead to unexpected results unless specified otherwise.</p>
- * 
+ *
  * @since 1.0
  */
 public interface JexlContext {
 
     /**
      * Gets the value of a variable.
-     * 
+     *
      * @param name the variable's name
      * @return the value
      */
@@ -46,7 +48,7 @@ public interface JexlContext {
 
     /**
      * Sets the value of a variable.
-     * 
+     *
      * @param name the variable's name
      * @param value the variable's value
      */
@@ -54,10 +56,10 @@ public interface JexlContext {
 
     /**
      * Checks whether a variable is defined in this context.
-     * 
+     *
      * <p>A variable may be defined with a null value; this method checks whether the
      * value is null or if the variable is undefined.</p>
-     * 
+     *
      * @param name the variable's name
      * @return true if it exists, false otherwise
      */
@@ -65,16 +67,16 @@ public interface JexlContext {
 
     /**
      * This interface declares how to resolve a namespace from its name; it is used by the interpreter during
-     * evalutation.
-     * 
+     * evaluation.
+     *
      * <p>In JEXL, a namespace is an object that serves the purpose of encapsulating functions; for instance,
      * the "math" namespace would be the proper object to expose functions like "log(...)", "sinus(...)", etc.</p>
-     * 
+     *
      * In expressions like "ns:function(...)", the resolver is called with resolveNamespace("ns").
-     * 
+     *
      * <p>JEXL itself reserves 'jexl' and 'ujexl' as namespaces for internal purpose; resolving those may lead to
      * unexpected results.</p>
-     * 
+     *
      * @since 3.0
      */
     interface NamespaceResolver {
@@ -89,7 +91,7 @@ public interface JexlContext {
 
     /**
      * Namespace type that allows creating an instance to delegate namespace methods calls to.
-     * 
+     *
      * <p>The functor is created once during the lifetime of a script evaluation.</p>
      */
     interface NamespaceFunctor {
@@ -109,7 +111,7 @@ public interface JexlContext {
      * keeping a reference to such a context is to be considered with great care and caution.
      * It should also be noted that sharing such a context between threads should implicate synchronizing variable
      * accessing the implementation class.
-     * 
+     *
      * @see JexlEngine#setThreadContext(JexlContext.ThreadLocal)
      * @see JexlEngine#getThreadContext()
      */
@@ -117,4 +119,21 @@ public interface JexlContext {
         // no specific method
     }
 
+    /**
+     * This interface declares how to process annotations; it is used by the interpreter during
+     * evaluation.
+     * <p>All annotations are processed through this method; the statement should be
+     */
+    interface AnnotationProcessor {
+        /**
+         * Processes an annotation.
+         * @param name the annotation name
+         * @param args the arguments
+         * @param statement the statement that was annotated; the processor should invoke this statement 'call' method
+         * @return the result of statement.call()
+         * @throws Exception if annotation processing fails
+         */
+        Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception;
+    }
+
 }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java Sun Jul  3 17:26:59 2016
@@ -31,7 +31,7 @@ import java.util.List;
 
 /**
  * Wraps any error that might occur during interpretation of a script or expression.
- * 
+ *
  * @since 2.0
  */
 public class JexlException extends RuntimeException {
@@ -47,7 +47,7 @@ public class JexlException extends Runti
 
     /**
      * Creates a new JexlException.
-     * 
+     *
      * @param node the node causing the error
      * @param msg  the error message
      */
@@ -57,7 +57,7 @@ public class JexlException extends Runti
 
     /**
      * Creates a new JexlException.
-     * 
+     *
      * @param node  the node causing the error
      * @param msg   the error message
      * @param cause the exception causing the error
@@ -75,7 +75,7 @@ public class JexlException extends Runti
 
     /**
      * Creates a new JexlException.
-     * 
+     *
      * @param jinfo the debugging information associated
      * @param msg   the error message
      * @param cause the exception causing the error
@@ -88,7 +88,7 @@ public class JexlException extends Runti
 
     /**
      * Gets the specific information for this exception.
-     * 
+     *
      * @return the information
      */
     public JexlInfo getInfo() {
@@ -97,7 +97,7 @@ public class JexlException extends Runti
 
     /**
      * Creates a string builder pre-filled with common error information (if possible).
-     * 
+     *
      * @param node the node
      * @return a string builder
      */
@@ -115,7 +115,7 @@ public class JexlException extends Runti
 
     /**
      * Gets the most specific information attached to a node.
-     * 
+     *
      * @param node the node
      * @param info the information
      * @return the information or null
@@ -137,7 +137,7 @@ public class JexlException extends Runti
 
     /**
      * Cleans a JexlException from any org.apache.commons.jexl3.internal stack trace element.
-     * 
+     *
      * @return this exception
      */
     public JexlException clean() {
@@ -146,7 +146,7 @@ public class JexlException extends Runti
 
     /**
      * Cleans a Throwable from any org.apache.commons.jexl3.internal stack trace element.
-     * 
+     *
      * @param <X>    the throwable type
      * @param xthrow the thowable
      * @return the throwable
@@ -168,7 +168,7 @@ public class JexlException extends Runti
 
     /**
      * Unwraps the cause of a throwable due to reflection.
-     * 
+     *
      * @param xthrow the throwable
      * @return the cause
      */
@@ -184,7 +184,7 @@ public class JexlException extends Runti
 
     /**
      * Merge the node info and the cause info to obtain best possible location.
-     * 
+     *
      * @param info  the node
      * @param cause the cause
      * @return the info to use
@@ -202,7 +202,7 @@ public class JexlException extends Runti
 
     /**
      * Accesses detailed message.
-     * 
+     *
      * @return the message
      */
     protected String detailedMessage() {
@@ -211,7 +211,7 @@ public class JexlException extends Runti
 
     /**
      * Formats an error message from the parser.
-     * 
+     *
      * @param prefix the prefix to the message
      * @param expr   the expression in error
      * @return the formatted message
@@ -235,7 +235,7 @@ public class JexlException extends Runti
 
     /**
      * Thrown when tokenization fails.
-     * 
+     *
      * @since 3.0
      */
     public static class Tokenization extends JexlException {
@@ -263,13 +263,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown when parsing fails.
-     * 
+     *
      * @since 3.0
      */
     public static class Parsing extends JexlException {
         /**
          * Creates a new Parsing exception instance.
-         * 
+         *
          * @param info  the location information
          * @param cause the javacc cause
          */
@@ -279,7 +279,7 @@ public class JexlException extends Runti
 
         /**
          * Creates a new Parsing exception instance.
-         * 
+         *
          * @param info the location information
          * @param msg  the message
          */
@@ -302,7 +302,7 @@ public class JexlException extends Runti
 
     /**
      * Thrown when parsing fails due to an ambiguous statement.
-     * 
+     *
      * @since 3.0
      */
     public static class Ambiguous extends Parsing {
@@ -323,13 +323,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown when parsing fails due to an invalid assigment.
-     * 
+     *
      * @since 3.0
      */
     public static class Assignment extends Parsing {
         /**
          * Creates a new Assignment statement exception instance.
-         * 
+         *
          * @param info  the location information
          * @param expr  the source expression line
          */
@@ -345,7 +345,7 @@ public class JexlException extends Runti
 
     /**
      * Thrown when a variable is unknown.
-     * 
+     *
      * @since 3.0
      */
     public static class Variable extends JexlException {
@@ -355,7 +355,7 @@ public class JexlException extends Runti
         private final boolean undefined;
         /**
          * Creates a new Variable exception instance.
-         * 
+         *
          * @param node the offending ASTnode
          * @param var  the unknown variable
          * @param undef whether the variable is undefined or evaluated as null
@@ -367,7 +367,7 @@ public class JexlException extends Runti
 
         /**
          * Whether the variable causing an error is undefined or evaluated as null.
-         * 
+         *
          * @return true if undefined, false otherwise
          */
         public boolean isUndefined() {
@@ -389,7 +389,7 @@ public class JexlException extends Runti
 
     /**
      * Generates a message for a variable error.
-     * 
+     *
      * @param node the node where the error occurred
      * @param variable the variable
      * @param undef whether the variable is null or undefined
@@ -409,13 +409,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown when a property is unknown.
-     * 
+     *
      * @since 3.0
      */
     public static class Property extends JexlException {
         /**
          * Creates a new Property exception instance.
-         * 
+         *
          * @param node the offending ASTnode
          * @param var  the unknown variable
          */
@@ -425,7 +425,7 @@ public class JexlException extends Runti
 
         /**
          * Creates a new Property exception instance.
-         * 
+         *
          * @param node  the offending ASTnode
          * @param var   the unknown variable
          * @param cause the exception causing the error
@@ -449,7 +449,7 @@ public class JexlException extends Runti
 
     /**
      * Generates a message for an unsolvable property error.
-     * 
+     *
      * @param node the node where the error occurred
      * @param var the variable
      * @return the error message
@@ -464,13 +464,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown when a method or ctor is unknown, ambiguous or inaccessible.
-     * 
+     *
      * @since 3.0
      */
     public static class Method extends JexlException {
         /**
          * Creates a new Method exception instance.
-         * 
+         *
          * @param node  the offending ASTnode
          * @param name  the method name
          */
@@ -480,7 +480,7 @@ public class JexlException extends Runti
 
         /**
          * Creates a new Method exception instance.
-         * 
+         *
          * @param info  the location information
          * @param name  the unknown method
          * @param cause the exception causing the error
@@ -504,7 +504,7 @@ public class JexlException extends Runti
 
     /**
      * Generates a message for a unsolvable method error.
-     * 
+     *
      * @param node the node where the error occurred
      * @param method the method name
      * @return the error message
@@ -519,13 +519,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown when an operator fails.
-     * 
+     *
      * @since 3.0
      */
     public static class Operator extends JexlException {
         /**
          * Creates a new Operator exception instance.
-         * 
+         *
          * @param node  the location information
          * @param symbol  the operator name
          * @param cause the exception causing the error
@@ -549,7 +549,7 @@ public class JexlException extends Runti
 
     /**
      * Generates a message for an operator error.
-     * 
+     *
      * @param node the node where the error occurred
      * @param symbol the operator name
      * @return the error message
@@ -563,8 +563,53 @@ public class JexlException extends Runti
     }
 
     /**
+     * Thrown when an annotation handler throws an exception.
+     *
+     * @since 3.1
+     */
+    public static class Annotation extends JexlException {
+        /**
+         * Creates a new Annotation exception instance.
+         *
+         * @param node  the annotated statement node
+         * @param name  the annotation name
+         * @param cause the exception causing the error
+         */
+        public Annotation(JexlNode node, String name, Throwable cause) {
+            super(node, name, cause);
+        }
+
+        /**
+         * @return the annotation name
+         */
+        public String getAnnotation() {
+            return super.detailedMessage();
+        }
+
+        @Override
+        protected String detailedMessage() {
+            return "error processing annotation '" + getAnnotation() + "'";
+        }
+    }
+
+    /**
+     * Generates a message for an annotation error.
+     *
+     * @param node the node where the error occurred
+     * @param annotation the annotation name
+     * @return the error message
+     */
+    public static String annotationError(JexlNode node, String annotation) {
+        StringBuilder msg = errorAt(node);
+        msg.append("error processing annotation '");
+        msg.append(annotation);
+        msg.append('\'');
+        return msg.toString();
+    }
+
+    /**
      * Thrown to return a value.
-     * 
+     *
      * @since 3.0
      */
     public static class Return extends JexlException {
@@ -574,7 +619,7 @@ public class JexlException extends Runti
 
         /**
          * Creates a new instance of Return.
-         * 
+         *
          * @param node  the return node
          * @param msg   the message
          * @param value the returned value
@@ -594,13 +639,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown to cancel a script execution.
-     * 
+     *
      * @since 3.0
      */
     public static class Cancel extends JexlException {
         /**
          * Creates a new instance of Cancel.
-         * 
+         *
          * @param node the node where the interruption was detected
          */
         public Cancel(JexlNode node) {
@@ -610,13 +655,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown to break a loop.
-     * 
+     *
      * @since 3.0
      */
     public static class Break extends JexlException {
         /**
          * Creates a new instance of Break.
-         * 
+         *
          * @param node the break
          */
         public Break(JexlNode node) {
@@ -626,13 +671,13 @@ public class JexlException extends Runti
 
     /**
      * Thrown to continue a loop.
-     * 
+     *
      * @since 3.0
      */
     public static class Continue extends JexlException {
         /**
          * Creates a new instance of Continue.
-         * 
+         *
          * @param node the continue
          */
         public Continue(JexlNode node) {
@@ -648,7 +693,7 @@ public class JexlException extends Runti
      * - begin, end are character offsets in the string for the precise location of the error
      * - string is the string representation of the offending expression
      * - msg is the actual explanation message for this error
-     * 
+     *
      * @return this error as a string
      */
     @Override

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java Sun Jul  3 17:26:59 2016
@@ -94,6 +94,8 @@ import org.apache.commons.jexl3.parser.J
 import org.apache.commons.jexl3.parser.ParserVisitor;
 
 import java.util.regex.Pattern;
+import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
+import org.apache.commons.jexl3.parser.ASTAnnotation;
 
 /**
  * Helps pinpoint the cause of problems in expressions that fail during evaluation.
@@ -959,4 +961,32 @@ public class Debugger extends ParserVisi
         String img = node.getLiteral().replace("`", "\\`");
         return check(node, "`" + img + "`", data);
     }
+
+    @Override
+    protected Object visit(ASTAnnotation node, Object data) {
+        int num = node.jjtGetNumChildren();
+        builder.append('@');
+        builder.append(node.getName());
+        if (num > 0) {
+            builder.append("(");
+            accept(node.jjtGetChild(0), data);
+            for(int i = 0; i < num; ++i) {
+                builder.append(", ");
+                JexlNode child = node.jjtGetChild(i);
+                acceptStatement(child, data);
+            }
+            builder.append(")");
+        }
+        return null;
+    }
+
+    @Override
+    protected Object visit(ASTAnnotatedStatement node, Object data) {
+        int num = node.jjtGetNumChildren();
+        for (int i = 0; i < num; ++i) {
+            JexlNode child = node.jjtGetChild(i);
+            acceptStatement(child, data);
+        }
+        return data;
+    }
 }
\ No newline at end of file

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java Sun Jul  3 17:26:59 2016
@@ -16,21 +16,22 @@
  */
 package org.apache.commons.jexl3.internal;
 
+
 import org.apache.commons.jexl3.JexlArithmetic;
-import org.apache.commons.jexl3.JexlOperator;
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlOperator;
 import org.apache.commons.jexl3.JexlScript;
-
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 import org.apache.commons.jexl3.introspection.JexlPropertySet;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.jexl3.introspection.JexlUberspect.PropertyResolver;
-
 import org.apache.commons.jexl3.parser.ASTAddNode;
 import org.apache.commons.jexl3.parser.ASTAndNode;
+import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
+import org.apache.commons.jexl3.parser.ASTAnnotation;
 import org.apache.commons.jexl3.parser.ASTArguments;
 import org.apache.commons.jexl3.parser.ASTArrayAccess;
 import org.apache.commons.jexl3.parser.ASTArrayLiteral;
@@ -102,10 +103,13 @@ import org.apache.commons.jexl3.parser.A
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.Node;
 import org.apache.commons.jexl3.parser.ParserVisitor;
+
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
+
 import org.apache.commons.logging.Log;
 
 /**
@@ -135,7 +139,7 @@ public class Interpreter extends ParserV
     /** The context to store/retrieve variables. */
     protected final JexlContext.NamespaceResolver ns;
     /** Strict interpreter flag (may temporarily change when calling size and empty as functions). */
-    protected boolean strictEngine;
+    protected final boolean strictEngine;
     /** Strict interpreter flag. */
     protected final boolean strictArithmetic;
     /** Silent interpreter flag. */
@@ -167,7 +171,7 @@ public class Interpreter extends ParserV
             Boolean ocancellable = opts.isCancellable();
             this.strictEngine = ostrict == null ? jexl.isStrict() : ostrict;
             this.silent = osilent == null ? jexl.isSilent() : osilent;
-            this.cancellable = ocancellable == null? jexl.cancellable : ocancellable;
+            this.cancellable = ocancellable == null ? jexl.cancellable : ocancellable;
             this.arithmetic = jexl.arithmetic.options(opts);
         } else {
             this.strictEngine = jexl.isStrict();
@@ -250,14 +254,14 @@ public class Interpreter extends ParserV
     protected void closeIfSupported(Object closeable) {
         if (closeable != null) {
             //if (AUTOCLOSEABLE == null || AUTOCLOSEABLE.isAssignableFrom(closeable.getClass())) {
-                JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
-                if (mclose != null) {
-                    try {
-                        mclose.invoke(closeable, EMPTY_PARAMS);
-                    } catch (Exception xignore) {
-                        logger.warn(xignore);
-                    }
+            JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
+            if (mclose != null) {
+                try {
+                    mclose.invoke(closeable, EMPTY_PARAMS);
+                } catch (Exception xignore) {
+                    logger.warn(xignore);
                 }
+            }
             //}
         }
     }
@@ -360,8 +364,8 @@ public class Interpreter extends ParserV
             logger.warn(xjexl.getMessage(), xjexl.getCause());
         }
         if (strictEngine
-            || xjexl instanceof JexlException.Return
-            || xjexl instanceof JexlException.Cancel) {
+                || xjexl instanceof JexlException.Return
+                || xjexl instanceof JexlException.Cancel) {
             throw xjexl;
         }
         return null;
@@ -369,9 +373,9 @@ public class Interpreter extends ParserV
 
     /**
      * Wraps an exception thrown by an invocation.
-     * @param node the node triggering the exception
+     * @param node       the node triggering the exception
      * @param methodName the method/function name
-     * @param xany the cause
+     * @param xany       the cause
      * @return a JexlException
      */
     protected JexlException invocationException(JexlNode node, String methodName, Exception xany) {
@@ -387,6 +391,22 @@ public class Interpreter extends ParserV
     }
 
     /**
+     * Triggered when an annotation processing fails.
+     * @param node     the node where the error originated from
+     * @param annotation the annotation name
+     * @param cause    the cause of error (if any)
+     * @throws JexlException if isStrict
+     */
+    protected void annotationError(JexlNode node, String annotation, Throwable cause) {
+        if (!silent) {
+            logger.warn(JexlException.annotationError(node, annotation), cause);
+        }
+        if (strictEngine) {
+            throw new JexlException.Annotation(node, annotation, cause);
+        }
+    }
+
+    /**
      * Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
      * @return false if already cancelled, true otherwise
      */
@@ -404,10 +424,10 @@ public class Interpreter extends ParserV
      * @return true if canceled, false otherwise
      */
     protected boolean isCancelled() {
-         if (!cancelled) {
-             cancelled = Thread.currentThread().isInterrupted();
-         }
-         return cancelled;
+        if (!cancelled) {
+            cancelled = Thread.currentThread().isInterrupted();
+        }
+        return cancelled;
     }
 
     /** @return true if interrupt throws a JexlException.Cancel. */
@@ -766,7 +786,7 @@ public class Interpreter extends ParserV
                 n = 1;
                 result = node.jjtGetChild(n).jjtAccept(this, null);
             } else {
-                // if there is a false, execute it. false statement is the second
+                    // if there is a false, execute it. false statement is the second
                 // objectNode
                 if (node.jjtGetNumChildren() == 3) {
                     n = 2;
@@ -829,8 +849,8 @@ public class Interpreter extends ParserV
             try {
                 forEach = operators.tryForeachOverload(node, iterableValue);
                 Iterator<?> itemsIterator = forEach instanceof Iterator
-                                ? (Iterator<?>) forEach
-                                : uberspect.getIterator(iterableValue);
+                                            ? (Iterator<?>) forEach
+                                            : uberspect.getIterator(iterableValue);
                 if (itemsIterator != null) {
                     while (itemsIterator.hasNext()) {
                         if (isCancelled()) {
@@ -897,7 +917,7 @@ public class Interpreter extends ParserV
             if (!leftValue) {
                 return Boolean.FALSE;
             }
-        } catch (RuntimeException xrt) {
+        } catch (ArithmeticException xrt) {
             throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
         }
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
@@ -1057,13 +1077,11 @@ public class Interpreter extends ParserV
 
     @Override
     protected Object visit(ASTSizeFunction node, Object data) {
-        boolean isStrict = this.strictEngine;
         try {
-            strictEngine = false;
             Object val = node.jjtGetChild(0).jjtAccept(this, data);
             return operators.size(node, val);
-        } finally {
-            strictEngine = isStrict;
+        } catch(JexlException xany) {
+            return 0;
         }
     }
 
@@ -1075,13 +1093,11 @@ public class Interpreter extends ParserV
 
     @Override
     protected Object visit(ASTEmptyFunction node, Object data) {
-        boolean isStrict = this.strictEngine;
         try {
-            strictEngine = false;
             Object value = node.jjtGetChild(0).jjtAccept(this, data);
             return operators.empty(node, value);
-        } finally {
-            strictEngine = isStrict;
+        } catch(JexlException xany) {
+            return true;
         }
     }
 
@@ -1531,9 +1547,9 @@ public class Interpreter extends ParserV
     /**
      * Concatenate arguments in call(...).
      * <p>When target == context, we are dealing with a global namespace function call
-     * @param target    the pseudo-method owner, first to-be argument
-     * @param narrow    whether we should attempt to narrow number arguments
-     * @param args      the other (non null) arguments
+     * @param target the pseudo-method owner, first to-be argument
+     * @param narrow whether we should attempt to narrow number arguments
+     * @param args   the other (non null) arguments
      * @return the arguments array
      */
     private Object[] functionArguments(Object target, boolean narrow, Object[] args) {
@@ -1548,7 +1564,7 @@ public class Interpreter extends ParserV
         Object[] nargv = new Object[args.length + 1];
         if (narrow) {
             nargv[0] = functionArgument(true, target);
-            for(int a = 1; a <= args.length; ++a) {
+            for (int a = 1; a <= args.length; ++a) {
                 nargv[a] = functionArgument(true, args[a - 1]);
             }
         } else {
@@ -1561,11 +1577,11 @@ public class Interpreter extends ParserV
     /**
      * Optionally narrows an argument for a function call.
      * @param narrow whether narrowing should occur
-     * @param arg the argument
+     * @param arg    the argument
      * @return the narrowed argument
      */
     private Object functionArgument(boolean narrow, Object arg) {
-       return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
+        return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
     }
 
     /**
@@ -1578,7 +1594,7 @@ public class Interpreter extends ParserV
         protected final JexlMethod me;
         /**
          * Constructor.
-         * @param jme the method
+         * @param jme  the method
          * @param flag the narrow flag
          */
         protected Funcall(JexlMethod jme, boolean flag) {
@@ -1588,10 +1604,10 @@ public class Interpreter extends ParserV
 
         /**
          * Try invocation.
-         * @param ii the interpreter
-         * @param name the method name
+         * @param ii     the interpreter
+         * @param name   the method name
          * @param target the method target
-         * @param args the method arguments
+         * @param args   the method arguments
          * @return the method invocation result (or JexlEngine.TRY_FAILED)
          */
         protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
@@ -1605,7 +1621,7 @@ public class Interpreter extends ParserV
     private static class ArithmeticFuncall extends Funcall {
         /**
          * Constructor.
-         * @param jme the method
+         * @param jme  the method
          * @param flag the narrow flag
          */
         protected ArithmeticFuncall(JexlMethod jme, boolean flag) {
@@ -1617,13 +1633,14 @@ public class Interpreter extends ParserV
             return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
         }
     }
+
     /**
      * Cached context function call.
      */
     private static class ContextFuncall extends Funcall {
         /**
          * Constructor.
-         * @param jme the method
+         * @param jme  the method
          * @param flag the narrow flag
          */
         protected ContextFuncall(JexlMethod jme, boolean flag) {
@@ -1733,13 +1750,12 @@ public class Interpreter extends ParserV
                         if (namespace == context) {
                             // we can not solve it
                             break;
-                        }
-                        else if (namespace != null) {
+                        } else if (namespace != null) {
                             target = namespace;
                             caller = null;
                             continue;
                         }
-                    // could not find a method, try as a property of a non-context target (performed once)
+                        // could not find a method, try as a property of a non-context target (performed once)
                     } else if (!narrow) {
                         // the method may be a functor stored in a property of the target
                         JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
@@ -1762,7 +1778,7 @@ public class Interpreter extends ParserV
                     if (vm != null) {
                         return vm.invoke(functor, argv);
                     }
-                // try JexlArithmetic or JexlContext function
+                    // try JexlArithmetic or JexlContext function
                 } else {
                     // no need to narrow since this has been performed in previous loop
                     Object[] nargv = functionArguments(caller, narrow, argv);
@@ -1893,7 +1909,7 @@ public class Interpreter extends ParserV
             throw new JexlException.Cancel(node);
         }
         final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
-                                    ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
+                                      ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
         Object result = operators.tryOverload(node, operator, object, attribute);
         if (result != JexlEngine.TRY_FAILED) {
             return result;
@@ -1912,7 +1928,7 @@ public class Interpreter extends ParserV
         // resolve that property
         Exception xcause = null;
         List<PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
-        JexlPropertyGet vg =  uberspect.getPropertyGet(resolvers, object, attribute);
+        JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
         if (vg != null) {
             try {
                 Object value = vg.invoke(object);
@@ -1962,7 +1978,7 @@ public class Interpreter extends ParserV
             throw new JexlException.Cancel(node);
         }
         final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
-                                    ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
+                                      ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
         Object result = operators.tryOverload(node, operator, object, attribute, value);
         if (result != JexlEngine.TRY_FAILED) {
             return;
@@ -2019,14 +2035,82 @@ public class Interpreter extends ParserV
     protected Object visit(ASTJxltLiteral node, Object data) {
         TemplateEngine.TemplateExpression tp = (TemplateEngine.TemplateExpression) node.jjtGetValue();
         if (tp == null) {
-           TemplateEngine jxlt = jexl.jxlt();
-           tp = jxlt.parseExpression(node.jexlInfo(), node.getLiteral(), frame != null? frame.getScope() : null);
-           node.jjtSetValue(tp);
+            TemplateEngine jxlt = jexl.jxlt();
+            tp = jxlt.parseExpression(node.jexlInfo(), node.getLiteral(), frame != null ? frame.getScope() : null);
+            node.jjtSetValue(tp);
         }
         if (tp != null) {
-           return tp.evaluate(frame, context);
+            return tp.evaluate(frame, context);
+        }
+        return null;
+    }
+
+    @Override
+    protected Object visit(ASTAnnotation node, Object data) {
+        throw new UnsupportedOperationException(ASTAnnotation.class.getName() + ": Not supported.");
+    }
+
+    @Override
+    protected Object visit(ASTAnnotatedStatement node, Object data) {
+        return processAnnotation(node, 0, data);
+    }
+
+    /**
+     * Processes an annotated statement.
+     * @param stmt the statement
+     * @param index the index of the current annotation being processed
+     * @param data the contextual data
+     * @return  the result of the statement block evaluation
+     */
+    protected Object processAnnotation(final ASTAnnotatedStatement stmt, final int index, final Object data) {
+        // are we evaluating the block ?
+        final int last = stmt.jjtGetNumChildren() - 1;
+        if (index == last) {
+            JexlNode block = stmt.jjtGetChild(last);
+            return block.jjtAccept(Interpreter.this, data);
+        }
+        // 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;
+                return processAnnotation(stmt, index + 1, data);
+            }
+        };
+        // the annotation node and name
+        final ASTAnnotation anode = (ASTAnnotation) stmt.jjtGetChild(index);
+        final String aname = anode.getName();
+        // evaluate the arguments
+        Object[] argv = anode.jjtGetNumChildren() > 0
+                ? visit((ASTArguments) anode.jjtGetChild(0), null) : null;
+        // wrap the future, will recurse through annotation processor
+        try {
+            Object result = processAnnotation(aname, argv, jstmt);
+            // not processing an annotation is an error
+            if (!processed[0]) {
+                annotationError(anode, aname, null);
+            }
+            return result;
+        } catch(JexlException xjexl) {
+            throw xjexl;
+        } catch(Exception xany) {
+            annotationError(anode, aname, xany);
         }
         return null;
     }
 
+    /**
+     * Delegates the annotation processing to the JexlContext if it is an AnnotationProcessor.
+     * @param annotation    the annotation name
+     * @param args          the annotation arguments
+     * @param stmt          the statement / block that was annotated
+     * @return the result of statement.call()
+     * @throws Exception if anything goes wrong
+     */
+    protected Object processAnnotation(String annotation, Object[] args, Callable<Object> stmt) throws Exception {
+        return context instanceof JexlContext.AnnotationProcessor
+                ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
+                : stmt.call();
+    }
 }

Copied: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java (from r1719037, commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java)
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java?p2=commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java&p1=commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java&r1=1719037&r2=1751163&rev=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTIdentifier.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java Sun Jul  3 17:26:59 2016
@@ -17,17 +17,16 @@
 package org.apache.commons.jexl3.parser;
 
 /**
- * Identifiers, variables, ie symbols.
+ * Annotation.
  */
-public class ASTIdentifier extends JexlNode {
+public class ASTAnnotation extends JexlNode {
     private String name = null;
-    private int symbol = -1;
 
-    ASTIdentifier(int id) {
+    ASTAnnotation(int id) {
         super(id);
     }
 
-    ASTIdentifier(Parser p, int id) {
+    ASTAnnotation(Parser p, int id) {
         super(p, id);
     }
 
@@ -36,20 +35,12 @@ public class ASTIdentifier extends JexlN
         return name;
     }
 
-    void setSymbol(String identifier) {
-        if (identifier.charAt(0) == '#') {
-            symbol = Integer.parseInt(identifier.substring(1));
+    void setName(String identifier) {
+        if (identifier.charAt(0) == '@') {
+            name = identifier.substring(1);
+        } else {
+            name = identifier;
         }
-        name = identifier;
-    }
-
-    void setSymbol(int r, String identifier) {
-        symbol = r;
-        name = identifier;
-    }
-
-    public int getSymbol() {
-        return symbol;
     }
 
     public String getName() {

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt Sun Jul  3 17:26:59 2016
@@ -203,6 +203,11 @@ TOKEN_MGR_DECLS : {
     < NAN_LITERAL : "NaN" >
 }
 
+<*> TOKEN : /* ANNOTATION */
+{
+  < ANNOTATION: "@" ( [ "0"-"9", "a"-"z", "A"-"Z", "_", "$" ])+ >
+}
+
 <DOT_ID> TOKEN : /* IDENTIFIERS */
 {
   < DOT_IDENTIFIER: ( [ "0"-"9", "a"-"z", "A"-"Z", "_", "$", "@" ])+ > { popDot(); } /* Revert state to default. */
@@ -278,11 +283,25 @@ ASTJexlScript JexlExpression(Scope frame
    }
 }
 
+void Annotation() #Annotation :
+{
+    Token t;
+}
+{
+    t=<ANNOTATION> (LOOKAHEAD(<LPAREN>) Arguments() )? { jjtThis.setName(t.image); }
+}
+
+void AnnotatedStatement() #AnnotatedStatement() : {}
+{
+    (LOOKAHEAD(<ANNOTATION>) Annotation())+ (LOOKAHEAD(1) Block() | Expression())
+}
+
 void Statement() #void : {}
 {
     <SEMICOL>
-    | LOOKAHEAD(<LCURLY> Expression() <SEMICOL>) Block() // to diasmbiguate the set literals
-    | LOOKAHEAD(<LCURLY> Statement() <SEMICOL>) Block() //  to diasmbiguate the set literals
+    | LOOKAHEAD(<ANNOTATION>) AnnotatedStatement()
+    | LOOKAHEAD(<LCURLY> Expression() <SEMICOL>) Block() // to disambiguate the set literals
+    | LOOKAHEAD(<LCURLY> Statement() <SEMICOL>) Block() //  to disambiguate the set literals
     | IfStatement()
     | ForeachStatement()
     | WhileStatement()
@@ -841,7 +860,3 @@ void ValueExpression() #void : {}
     ( PrimaryExpression() ( LOOKAHEAD(2) MemberExpression() )*) #Reference(>1)
 }
 
-
-
-
-

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java Sun Jul  3 17:26:59 2016
@@ -27,7 +27,7 @@ public abstract class ParserVisitor {
      * @return does not return
      */
     protected final Object visit(SimpleNode node, Object data) {
-        throw new UnsupportedOperationException("Not supported yet.");
+        throw new UnsupportedOperationException(node.getClass().getSimpleName() + " : not supported yet.");
     }
 
     /**
@@ -177,4 +177,8 @@ public abstract class ParserVisitor {
     protected abstract Object visit(ASTSetXorNode node, Object data);
 
     protected abstract Object visit(ASTJxltLiteral node, Object data);
+
+    protected abstract Object visit(ASTAnnotation node, Object data);
+
+    protected abstract Object visit(ASTAnnotatedStatement node, Object data);
 }

Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AnnotationTest.java?rev=1751163&view=auto
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AnnotationTest.java (added)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AnnotationTest.java Sun Jul  3 17:26:59 2016
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3;
+
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test cases for annotations.
+ * @since 3.1
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+
+public class AnnotationTest extends JexlTestCase {
+
+    public AnnotationTest() {
+        super("AnnotationTest");
+    }
+
+    @Test
+    public void test197a() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@synchronized { return 42; }");
+        Object r = e.execute(jc);
+        Assert.assertEquals(42, r);
+    }
+
+    public static class AnnotationContext extends MapContext implements JexlContext.AnnotationProcessor {
+        private int count = 0;
+        private final Set<String> names = new TreeSet<String>();
+
+        @Override
+        public Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception {
+            count += 1;
+            names.add(name);
+            if ("one".equals(name)) {
+                names.add(args[0].toString());
+            } else if ("two".equals(name)) {
+                names.add(args[0].toString());
+                names.add(args[1].toString());
+            } else if ("error".equals(name)) {
+                names.add(args[0].toString());
+                throw new IllegalArgumentException(args[0].toString());
+            } else if ("unknown".equals(name)) {
+                return null;
+            }
+            return statement.call();
+        }
+
+        public int getCount() {
+            return count;
+        }
+
+        public Set<String> getNames() {
+            return names;
+        }
+    }
+
+    @Test
+    public void testNoArg() throws Exception {
+        AnnotationContext jc = new AnnotationContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@synchronized { return 42; }");
+        Object r = e.execute(jc);
+        Assert.assertEquals(42, r);
+        Assert.assertEquals(1, jc.getCount());
+        Assert.assertTrue(jc.getNames().contains("synchronized"));
+    }
+
+    @Test
+    public void testNoArgExpression() throws Exception {
+        AnnotationContext jc = new AnnotationContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@synchronized 42");
+        Object r = e.execute(jc);
+        Assert.assertEquals(42, r);
+        Assert.assertEquals(1, jc.getCount());
+        Assert.assertTrue(jc.getNames().contains("synchronized"));
+    }
+
+
+    @Test
+    public void testOneArg() throws Exception {
+        AnnotationContext jc = new AnnotationContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@one(1) { return 42; }");
+        Object r = e.execute(jc);
+        Assert.assertEquals(42, r);
+        Assert.assertEquals(1, jc.getCount());
+        Assert.assertTrue(jc.getNames().contains("one"));
+        Assert.assertTrue(jc.getNames().contains("1"));
+    }
+
+    @Test
+    public void testMultiple() throws Exception {
+        AnnotationContext jc = new AnnotationContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@one(1) @synchronized { return 42; }");
+        Object r = e.execute(jc);
+        Assert.assertEquals(42, r);
+        Assert.assertEquals(2, jc.getCount());
+        Assert.assertTrue(jc.getNames().contains("synchronized"));
+        Assert.assertTrue(jc.getNames().contains("one"));
+        Assert.assertTrue(jc.getNames().contains("1"));
+    }
+
+    @Test
+    public void testError() throws Exception {
+        AnnotationContext jc = new AnnotationContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@error('42') { return 42; }");
+        try {
+            Object r = e.execute(jc);
+            Assert.fail("should have failed");
+        } catch (JexlException.Annotation xjexl) {
+            Assert.assertEquals("error", xjexl.getAnnotation());
+        }
+        Assert.assertEquals(1, jc.getCount());
+        Assert.assertTrue(jc.getNames().contains("error"));
+        Assert.assertTrue(jc.getNames().contains("42"));
+    }
+
+    @Test
+    public void testUnknown() throws Exception {
+        AnnotationContext jc = new AnnotationContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("@unknown('42') { return 42; }");
+        try {
+            Object r = e.execute(jc);
+            Assert.fail("should have failed");
+        } catch (JexlException.Annotation xjexl) {
+            Assert.assertEquals("unknown", xjexl.getAnnotation());
+        }
+        Assert.assertEquals(1, jc.getCount());
+        Assert.assertTrue(jc.getNames().contains("unknown"));
+        Assert.assertFalse(jc.getNames().contains("42"));
+    }
+}

Propchange: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java Sun Jul  3 17:26:59 2016
@@ -95,6 +95,16 @@ public class ArithmeticOperatorTest exte
     }
 
     @Test
+    public void testStartsEndsWithStringDot() throws Exception {
+        asserter.setVariable("x.y", "foobar");
+        asserter.assertExpression("x.y =^ 'foo'", Boolean.TRUE);
+        asserter.assertExpression("x.y =$ 'foo'", Boolean.FALSE);
+        asserter.setVariable("x.y", "barfoo");
+        asserter.assertExpression("x.y =^ 'foo'", Boolean.FALSE);
+        asserter.assertExpression("x.y =$ 'foo'", Boolean.TRUE);
+    }
+
+    @Test
     public void testNotStartsEndsWithString() throws Exception {
         asserter.setVariable("x", "foobar");
         asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
@@ -104,6 +114,16 @@ public class ArithmeticOperatorTest exte
         asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
     }
 
+    @Test
+    public void testNotStartsEndsWithStringDot() throws Exception {
+        asserter.setVariable("x.y", "foobar");
+        asserter.assertExpression("x.y !^ 'foo'", Boolean.FALSE);
+        asserter.assertExpression("x.y !$ 'foo'", Boolean.TRUE);
+        asserter.setVariable("x.y", "barfoo");
+        asserter.assertExpression("x.y !^ 'foo'", Boolean.TRUE);
+        asserter.assertExpression("x.y !$ 'foo'", Boolean.FALSE);
+    }
+
     public static class MatchingContainer {
         private final Set<Integer> values;
 

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java Sun Jul  3 17:26:59 2016
@@ -1163,4 +1163,51 @@ public class IssuesTest extends JexlTest
         jc.set("t", null);
         Assert.assertNull(js0.evaluate(jc));
     }
+
+    @Test
+    public void test199() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(false)).create();
+
+        JexlScript e = jexl.createScript("(x, y)->{ x + y }");
+        Object r = e.execute(jc, true, "EURT");
+        Assert.assertEquals("trueEURT", r);
+        r = e.execute(jc, "ELSAF", false);
+        Assert.assertEquals("ELSAFfalse", r);
+    }
+
+    public static class Eval {
+        private JexlEngine jexl;
+
+        public JexlScript fn(String src) {
+            return jexl.createScript(src);
+        }
+
+        void setJexl(JexlEngine je) {
+            jexl = je;
+        }
+    }
+
+    @Test
+    public void test200() throws Exception {
+        JexlContext jc = new MapContext();
+        Map<String, Object> funcs = new HashMap<String, Object>();
+        Eval eval = new Eval();
+        funcs.put(null, eval);
+        JexlEngine jexl = new JexlBuilder().namespaces(funcs).create();
+        eval.setJexl(jexl);
+        String src = "var f = fn(\'(x)->{x + 42}\'); f(y)";
+        JexlScript s200 = jexl.createScript(src, "y");
+        Assert.assertEquals(142, s200.execute(jc, 100));
+        Assert.assertEquals(52, s200.execute(jc, 10));
+    }
+
+    @Test
+    public void test200b() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlScript e = jexl.createScript("var x = 0; var f = (y)->{ x = y; }; f(42); x");
+        Object r = e.execute(jc);
+        Assert.assertEquals(0, r);
+    }
 }

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/JXLTTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/JXLTTest.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/JXLTTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/JXLTTest.java Sun Jul  3 17:26:59 2016
@@ -680,10 +680,14 @@ public class JXLTTest extends JexlTestCa
 
     @Test
     public void testInterpolation() throws Exception {
-        context.set("user", "Dimitri");
         String expr =  "`Hello \n${user}`";
-        Object value = JEXL.createScript(expr).execute(context);
+        JexlScript script = JEXL.createScript(expr);
+        context.set("user", "Dimitri");
+        Object value = script.execute(context);
         Assert.assertEquals(expr, "Hello \nDimitri", value);
+        context.set("user", "Rahul");
+        value = script.execute(context);
+        Assert.assertEquals(expr, "Hello \nRahul", value);
     }
 
     @Test
@@ -718,6 +722,8 @@ public class JXLTTest extends JexlTestCa
         String expr =  "(user)->{`Hello \n${user}`}";
         Object value = JEXL.createScript(expr).execute(context, "Henrib");
         Assert.assertEquals(expr, "Hello \nHenrib", value);
+        value = JEXL.createScript(expr).execute(context, "Dimitri");
+        Assert.assertEquals(expr, "Hello \nDimitri", value);
     }
 //
 //

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java Sun Jul  3 17:26:59 2016
@@ -62,7 +62,35 @@ public class ScriptCallableTest extends
     }
 
     @Test
-    public void testCallable() throws Exception {
+    public void testCallableCancel() throws Exception {
+        JexlScript e = JEXL.createScript("while(true);");
+        final Script.Callable c = (Script.Callable) e.callable(null);
+        Object t = 42;
+        Callable<Object> kc = new Callable<Object>() {
+            @Override
+            public Object call() throws Exception {
+                return c.cancel();
+            }
+
+        };
+        ExecutorService executor = Executors.newFixedThreadPool(2);
+        Future<?> future = executor.submit(c);
+        Future<?> kfc = executor.submit(kc);
+        try {
+            Assert.assertTrue((Boolean) kfc.get());
+            t = future.get();
+            Assert.fail("should have been cancelled");
+        } catch (ExecutionException xexec) {
+            // ok, ignore
+            Assert.assertTrue(xexec.getCause() instanceof JexlException.Cancel);
+        } finally {
+            executor.shutdown();
+        }
+        Assert.assertTrue(c.isCancelled());
+    }
+
+    @Test
+    public void testCallableTimeout() throws Exception {
         JexlScript e = JEXL.createScript("while(true);");
         Callable<Object> c = e.callable(null);
         Object t = 42;

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java Sun Jul  3 17:26:59 2016
@@ -84,6 +84,45 @@ public class SideEffectTest extends Jexl
     }
 
     @Test
+    public void testSideEffectVarDots() throws Exception {
+        Map<String,Object> context = asserter.getVariables();
+        Integer i41 = Integer.valueOf(4141);
+        Object foo = i41;
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux += 2", i41 + 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 + 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux -= 2", i41 - 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 - 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux *= 2", i41 * 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 * 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux /= 2", i41 / 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 / 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux %= 2", i41 % 2);
+        Assert.assertEquals(context.get("foo.bar.quux"), i41 % 2);
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux &= 3", (long) (i41 & 3));
+        Assert.assertEquals(context.get("foo.bar.quux"), (long)(i41 & 3));
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux |= 2", (long)(i41 | 2));
+        Assert.assertEquals(context.get("foo.bar.quux"), (long)(i41 | 2));
+
+        context.put("foo.bar.quux", foo);
+        asserter.assertExpression("foo.bar.quux ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(context.get("foo.bar.quux"), (long)(i41 ^ 2));
+    }
+
+    @Test
     public void testSideEffectArray() throws Exception {
         Integer i41 = Integer.valueOf(4141);
         Integer i42 = Integer.valueOf(42);
@@ -120,6 +159,43 @@ public class SideEffectTest extends Jexl
         Assert.assertEquals(foo[0], (long)(i41 ^ 2));
     }
 
+    @Test
+    public void testSideEffectDotArray() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        String s42 = "fourty-two";
+        String s43 = "fourty-three";
+        Object[] foo = new Object[3];
+        foo[1] = i42;
+        foo[2] = i43;
+        asserter.setVariable("foo", foo);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 += 2", i41 + 2);
+        Assert.assertEquals(foo[0], i41 + 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 -= 2", i41 - 2);
+        Assert.assertEquals(foo[0], i41 - 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 *= 2", i41 * 2);
+        Assert.assertEquals(foo[0], i41 * 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 /= 2", i41 / 2);
+        Assert.assertEquals(foo[0], i41 / 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 %= 2", i41 % 2);
+        Assert.assertEquals(foo[0], i41 % 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 &= 3", (long) (i41 & 3));
+        Assert.assertEquals(foo[0], (long)(i41 & 3));
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 |= 2", (long)(i41 | 2));
+        Assert.assertEquals(foo[0], (long)(i41 | 2));
+        foo[0] = i41;
+        asserter.assertExpression("foo.0 ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(foo[0], (long)(i41 ^ 2));
+    }
+
     @Test
     public void testSideEffectAntishArray() throws Exception {
         Integer i41 = Integer.valueOf(4141);

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java Sun Jul  3 17:26:59 2016
@@ -16,10 +16,12 @@
  */
 package org.apache.commons.jexl3;
 
+import java.util.concurrent.Callable;
+
 /**
  * Exposes a synchronized call to a script and synchronizes access to get/set methods.
  */
-public class SynchronizedContext extends MapContext {
+public class SynchronizedContext extends MapContext implements JexlContext.AnnotationProcessor {
     private final JexlContext context;
 
     public SynchronizedContext(JexlContext ctxt) {
@@ -58,4 +60,15 @@ public class SynchronizedContext extends
         }
     }
 
+    @Override
+    public Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception {
+        if ("synchronized".equals(name)) {
+            final Object arg = args[0];
+            synchronized(arg) {
+                return statement.call();
+            }
+        }
+        throw new IllegalArgumentException("unknown annotation " + name);
+    }
+
 }

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java?rev=1751163&r1=1751162&r2=1751163&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java Sun Jul  3 17:26:59 2016
@@ -53,6 +53,16 @@ public class SynchronizedOverloadsTest e
     }
 
     @Test
+    public void testSynchronized() throws Exception {
+        Map<String, Object> ns = new TreeMap<String, Object>();
+        JexlContext jc = new SynchronizedContext(new MapContext());
+        JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
+        JexlScript js0 = jexl.createScript("@synchronized(y) {return y.size(); }", "y");
+        Object size = js0.execute(jc, "foobar");
+        Assert.assertEquals(6, size);
+    }
+
+    @Test
     public void testUnsafeMonitor() throws Exception {
         SynchronizedArithmetic.Monitor monitor = new SynchronizedArithmetic.UnsafeMonitor();
         Map<String, Object> foo = new TreeMap<String, Object>();
@@ -66,5 +76,9 @@ public class SynchronizedOverloadsTest e
         Assert.assertEquals(10.0d, t);
         Assert.assertTrue(monitor.isBalanced());
         Assert.assertEquals(2, monitor.getCount());
+        t = js0.execute(jc, foo);
+        Assert.assertEquals(10.0d, t);
+        Assert.assertTrue(monitor.isBalanced());
+        Assert.assertEquals(4, monitor.getCount());
     }
 }