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()); } }