JEXL:
JEXL-207: split Interpreter in 2 (class was too big), reworked error/exception handling to be more coherent, added tests
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/jexl/trunk@1754746 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java b/src/main/java/org/apache/commons/jexl3/JexlException.java
index 3581d8c..d8cd92d 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -416,16 +416,6 @@
/**
* Creates a new Property exception instance.
*
- * @param node the offending ASTnode
- * @param var the unknown variable
- */
- public Property(JexlNode node, String var) {
- this(node, var, null);
- }
-
- /**
- * Creates a new Property exception instance.
- *
* @param node the offending ASTnode
* @param var the unknown variable
* @param cause the exception causing the error
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index cb93d61..3c2d03c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -23,11 +23,12 @@
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;
@@ -102,7 +103,6 @@
import org.apache.commons.jexl3.parser.ASTWhileStatement;
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;
@@ -110,40 +110,25 @@
import java.util.Map;
import java.util.concurrent.Callable;
-import org.apache.commons.logging.Log;
/**
* An interpreter of JEXL syntax.
*
* @since 2.0
*/
-public class Interpreter extends ParserVisitor {
- /** The JEXL engine. */
- protected final Engine jexl;
- /** The logger. */
- protected final Log logger;
- /** The uberspect. */
- protected final JexlUberspect uberspect;
- /** The arithmetic handler. */
- protected final JexlArithmetic arithmetic;
+public class Interpreter extends InterpreterBase {
/** The operators evaluation delegate. */
protected final Operators operators;
- /** The map of symboled functions. */
- protected final Map<String, Object> functions;
- /** The map of symboled functions. */
- protected Map<String, Object> functors;
- /** The context to store/retrieve variables. */
- protected final JexlContext context;
- /** symbol values. */
+ /** Cache executors. */
+ protected final boolean cache;
+ /** Symbol values. */
protected final Scope.Frame frame;
/** The context to store/retrieve variables. */
protected final JexlContext.NamespaceResolver ns;
- /** Cache executors. */
- protected final boolean cache;
- /** Cancellation support. */
- protected volatile boolean cancelled = false;
- /** Empty parameters for method matching. */
- protected static final Object[] EMPTY_PARAMS = new Object[0];
+ /** The map of 'prefix:function' to object resolving as namespaces. */
+ protected final Map<String, Object> functions;
+ /** The map of dynamically creates namespaces, NamespaceFunctor or duck-types of those. */
+ protected Map<String, Object> functors;
/**
* Creates an interpreter.
@@ -152,32 +137,17 @@
* @param eFrame the interpreter evaluation frame
*/
protected Interpreter(Engine engine, JexlContext aContext, Scope.Frame eFrame) {
- this.jexl = engine;
- this.logger = jexl.logger;
- this.uberspect = jexl.uberspect;
- this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- this.arithmetic = jexl.arithmetic.options(opts);
- if (!arithmetic.getClass().equals(jexl.arithmetic.getClass())) {
- logger.error(
- "expected arithmetic to be " + jexl.arithmetic.getClass().getSimpleName() +
- ", got " + arithmetic.getClass().getSimpleName()
- );
- }
- } else {
- this.arithmetic = jexl.arithmetic;
- }
+ super(engine, aContext);
+ this.operators = new Operators(this);
+ this.cache = jexl.cache != null;
+ this.frame = eFrame;
if (this.context instanceof JexlContext.NamespaceResolver) {
ns = ((JexlContext.NamespaceResolver) context);
} else {
ns = Engine.EMPTY_NS;
}
this.functions = jexl.functions;
- this.cache = jexl.cache != null;
- this.frame = eFrame;
this.functors = null;
- this.operators = new Operators(this);
}
/**
@@ -212,12 +182,17 @@
}
logger.warn(xjexl.getMessage(), xjexl.getCause());
} finally {
- if (functors != null && AUTOCLOSEABLE != null) {
- for (Object functor : functors.values()) {
- closeIfSupported(functor);
+ synchronized(this) {
+ if (functors != null) {
+ if (AUTOCLOSEABLE != null) {
+ for (Object functor : functors.values()) {
+ closeIfSupported(functor);
+ }
+ }
+ functors.clear();
+ functors = null;
}
}
- functors = null;
if (context instanceof JexlContext.ThreadLocal) {
jexl.putThreadLocal(local);
}
@@ -225,249 +200,10 @@
return null;
}
- /** Java7 AutoCloseable interface defined?. */
- private static final Class<?> AUTOCLOSEABLE;
- static {
- Class<?> c;
- try {
- c = Class.forName("java.lang.AutoCloseable");
- } catch (ClassNotFoundException xclass) {
- c = null;
- }
- AUTOCLOSEABLE = c;
- }
-
- /**
- * Attempt to call close() if supported.
- * <p>This is used when dealing with auto-closeable (duck-like) objects
- * @param closeable the object we'd like to close
- */
- 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);
- }
- }
- //}
- }
- }
-
- /**
- * Whether this interpreter is currently evaluating with a strict engine flag.
- * @return true if strict engine, false otherwise
- */
- protected boolean isStrictEngine() {
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- Boolean strict = opts.isStrict();
- if (strict != null) {
- return strict.booleanValue();
- }
- }
- return jexl.isStrict();
- }
-
- /**
- * Whether this interpreter is currently evaluating with a silent mode.
- * @return true if silent, false otherwise
- */
- protected boolean isSilent() {
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- Boolean silent = opts.isSilent();
- if (silent != null) {
- return silent.booleanValue();
- }
- }
- return jexl.isSilent();
- }
-
- /** @return true if interrupt throws a JexlException.Cancel. */
- protected boolean isCancellable() {
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- Boolean ocancellable = opts.isCancellable();
- if (ocancellable != null) {
- return ocancellable.booleanValue();
- }
- }
- return jexl.cancellable;
- }
-
- /**
- * Finds the node causing a NPE for diadic operators.
- * @param xrt the RuntimeException
- * @param node the parent node
- * @param left the left argument
- * @param right the right argument
- * @return the left, right or parent node
- */
- protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
- if (xrt instanceof JexlArithmetic.NullOperand) {
- if (left == null) {
- return node.jjtGetChild(0);
- }
- if (right == null) {
- return node.jjtGetChild(1);
- }
- }
- return node;
- }
-
- /**
- * Triggered when a variable can not be resolved.
- * @param node the node where the error originated from
- * @param var the variable name
- * @param undef whether the variable is undefined or null
- * @return throws JexlException if isStrict, null otherwise
- */
- protected Object unsolvableVariable(JexlNode node, String var, boolean undef) {
- if (!isSilent()) {
- logger.warn(JexlException.variableError(node, var, undef));
- }
- if (isStrictEngine() && (undef || arithmetic.isStrict())) {
- throw new JexlException.Variable(node, var, undef);
- }
- return null;
- }
-
- /**
- * Triggered when a method can not be resolved.
- * @param node the node where the error originated from
- * @param method the method name
- * @return throws JexlException if isStrict, null otherwise
- */
- protected Object unsolvableMethod(JexlNode node, String method) {
- if (!isSilent()) {
- logger.warn(JexlException.methodError(node, method));
- }
- if (isStrictEngine()) {
- throw new JexlException.Method(node, method);
- }
- return null;
- }
-
- /**
- * Triggered when a property can not be resolved.
- * @param node the node where the error originated from
- * @param var the property name
- * @param cause the cause if any
- * @return throws JexlException if isStrict, null otherwise
- */
- protected Object unsolvableProperty(JexlNode node, String var, Throwable cause) {
- if (!isSilent()) {
- logger.warn(JexlException.propertyError(node, var), cause);
- }
- if (isStrictEngine()) {
- throw new JexlException.Property(node, var, cause);
- }
- return null;
- }
-
- /**
- * Triggered when an operator fails.
- * @param node the node where the error originated from
- * @param operator the method name
- * @param cause the cause of error (if any)
- * @throws JexlException if isStrict
- */
- protected void operatorError(JexlNode node, JexlOperator operator, Throwable cause) {
- if (cause != null) {
- if (!isSilent()) {
- logger.warn(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
- }
- if (isStrictEngine()) {
- throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
- }
- }
- }
-
- /**
- * Triggered when method, function or constructor invocation fails.
- * @param xjexl the JexlException wrapping the original error
- * @return throws JexlException if isStrict, null otherwise
- */
- protected Object invocationFailed(JexlException xjexl) {
- if (!isSilent()) {
- logger.warn(xjexl.getMessage(), xjexl.getCause());
- }
- if (isStrictEngine()
- || xjexl instanceof JexlException.Return
- || xjexl instanceof JexlException.Cancel) {
- throw xjexl;
- }
- return null;
- }
-
- /**
- * Wraps an exception thrown by an invocation.
- * @param node the node triggering the exception
- * @param methodName the method/function name
- * @param xany the cause
- * @return a JexlException
- */
- protected JexlException invocationException(JexlNode node, String methodName, Exception xany) {
- Throwable cause = xany.getCause();
- if (cause instanceof JexlException) {
- throw (JexlException) cause;
- }
- if (cause instanceof InterruptedException) {
- cancelled = true;
- return new JexlException.Cancel(node);
- }
- return new JexlException(node, methodName, xany);
- }
-
- /**
- * 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 (!isSilent()) {
- logger.warn(JexlException.annotationError(node, annotation), cause);
- }
- if (isStrictEngine()) {
- 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
- */
- protected boolean cancel() {
- if (cancelled) {
- return false;
- } else {
- cancelled = true;
- return true;
- }
- }
-
- /**
- * Checks whether this interpreter execution was canceled due to thread interruption.
- * @return true if canceled, false otherwise
- */
- protected boolean isCancelled() {
- if (!cancelled) {
- cancelled = Thread.currentThread().isInterrupted();
- }
- return cancelled;
- }
-
/**
* Resolves a namespace, eventually allocating an instance using context as constructor argument.
* <p>
- * The lifetime of
- * such instances span the current expression or script evaluation.</p>
+ * The lifetime of such instances span the current expression or script evaluation.</p>
* @param prefix the prefix name (may be null for global namespace)
* @param node the AST node
* @return the namespace instance
@@ -475,10 +211,12 @@
protected Object resolveNamespace(String prefix, JexlNode node) {
Object namespace;
// check whether this namespace is a functor
- if (functors != null) {
- namespace = functors.get(prefix);
- if (namespace != null) {
- return namespace;
+ synchronized (this) {
+ if (functors != null) {
+ namespace = functors.get(prefix);
+ if (namespace != null) {
+ return namespace;
+ }
}
}
// check if namespace is a resolver
@@ -506,10 +244,12 @@
}
// got a functor, store it and return it
if (functor != null) {
- if (functors == null) {
- functors = new HashMap<String, Object>();
+ synchronized (this) {
+ if (functors == null) {
+ functors = new HashMap<String, Object>();
+ }
+ functors.put(prefix, functor);
}
- functors.put(prefix, functor);
return functor;
} else {
return namespace;
@@ -875,7 +615,7 @@
// get an iterator for the collection/array etc via the introspector.
Object forEach = null;
try {
- forEach = operators.tryForeachOverload(node, iterableValue);
+ forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
Iterator<?> itemsIterator = forEach instanceof Iterator
? (Iterator<?>) forEach
: uberspect.getIterator(iterableValue);
@@ -1257,6 +997,7 @@
JexlNode objectNode;
StringBuilder ant = null;
boolean antish = !(parent instanceof ASTReference);
+ boolean pty = true;
int v = 1;
main:
for (int c = 0; c < numChildren; c++) {
@@ -1279,9 +1020,14 @@
} else if (antish) { // if we still have a null object, check for an antish variable
if (ant == null) {
JexlNode first = node.jjtGetChild(0);
- if (first instanceof ASTIdentifier && ((ASTIdentifier) first).getSymbol() < 0) {
- ant = new StringBuilder(((ASTIdentifier) first).getName());
+ if (first instanceof ASTIdentifier) {
+ if (((ASTIdentifier) first).getSymbol() < 0) {
+ ant = new StringBuilder(((ASTIdentifier) first).getName());
+ } else {
+ break;
+ }
} else {
+ pty = false;
break;
}
}
@@ -1299,10 +1045,14 @@
break;
}
}
- if (object == null && antish && ant != null && !isTernaryProtected(node)) {
- boolean undefined = !(context.has(ant.toString()) || isLocalVariable(node, 0));
- // variable unknown in context and not a local
- return unsolvableVariable(node, ant.toString(), undefined);
+ if (object == null && !isTernaryProtected(node)) {
+ if (antish && ant != null) {
+ boolean undefined = !(context.has(ant.toString()) || isLocalVariable(node, 0));
+ // variable unknown in context and not a local
+ return unsolvableVariable(node, ant.toString(), undefined);
+ } else if (!pty) {
+ return unsolvableProperty(node, "<null>.<?>", null);
+ }
}
return object;
}
@@ -1495,11 +1245,11 @@
}
if (property == null) {
// no property, we fail
- throw new JexlException(propertyNode, "property is null");
+ return unsolvableProperty(propertyNode, "<?>.<null>", null);
}
if (object == null) {
// no object, we fail
- throw new JexlException(objectNode, "bean is null");
+ return unsolvableProperty(objectNode, "<null>.<?>", null);
}
// 3: one before last, assign
if (assignop != null) {
@@ -1536,7 +1286,7 @@
object = data;
if (object == null) {
// no object, we fail
- throw new JexlException(objectNode, "object is null");
+ return unsolvableMethod(objectNode, "<null>.<?>(...)");
}
} else {
method = methodNode.jjtAccept(this, null);
@@ -1545,7 +1295,7 @@
for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
if (result == null) {
// no method, we fail
- throw new JexlException(methodNode, "method is null");
+ return unsolvableMethod(methodNode, "<?>.<null>(...)");
}
ASTArguments argNode = (ASTArguments) node.jjtGetChild(a);
result = call(node, object, result, argNode);
@@ -1849,10 +1599,10 @@
return eval;
}
return unsolvableMethod(node, methodName);
- } catch (JexlException.Method xmethod) {
- throw xmethod;
+ } catch (JexlException xthru) {
+ throw xthru;
} catch (Exception xany) {
- xjexl = invocationException(node, methodName, xany);
+ xjexl = exceptionOnInvocation(node, methodName, xany);
}
return invocationFailed(xjexl);
}
@@ -1901,11 +1651,11 @@
node.jjtSetValue(ctor);
}
return instance;
- } catch (JexlException.Method xmethod) {
- throw xmethod;
+ } catch (JexlException xthru) {
+ throw xthru;
} catch (Exception xany) {
String dbgStr = cobject != null ? cobject.toString() : null;
- xjexl = invocationException(node, dbgStr, xany);
+ xjexl = exceptionOnInvocation(node, dbgStr, xany);
}
return invocationFailed(xjexl);
}
@@ -2117,15 +1867,15 @@
Object result = processAnnotation(aname, argv, jstmt);
// not processing an annotation is an error
if (!processed[0]) {
- annotationError(anode, aname, null);
+ return annotationError(anode, aname, null);
+ } else {
+ return result;
}
- return result;
} catch(JexlException xjexl) {
throw xjexl;
} catch(Exception xany) {
- annotationError(anode, aname, xany);
+ return annotationError(anode, aname, xany);
}
- return null;
}
/**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
new file mode 100644
index 0000000..5a06090
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -0,0 +1,333 @@
+/*
+ * 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.internal;
+
+
+import org.apache.commons.jexl3.JexlArithmetic;
+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.introspection.JexlMethod;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.apache.commons.jexl3.parser.JexlNode;
+import org.apache.commons.jexl3.parser.ParserVisitor;
+
+
+import org.apache.commons.logging.Log;
+
+/**
+ * The helper base of an interpreter of JEXL syntax.
+ * @since 3.0
+ */
+public abstract class InterpreterBase extends ParserVisitor {
+ /** The JEXL engine. */
+ protected final Engine jexl;
+ /** The logger. */
+ protected final Log logger;
+ /** The uberspect. */
+ protected final JexlUberspect uberspect;
+ /** The arithmetic handler. */
+ protected final JexlArithmetic arithmetic;
+ /** The context to store/retrieve variables. */
+ protected final JexlContext context;
+ /** Cancellation support. */
+ protected volatile boolean cancelled = false;
+ /** Empty parameters for method matching. */
+ protected static final Object[] EMPTY_PARAMS = new Object[0];
+
+ /**
+ * Creates an interpreter base.
+ * @param engine the engine creating this interpreter
+ * @param aContext the context to evaluate expression
+ */
+ protected InterpreterBase(Engine engine, JexlContext aContext) {
+ this.jexl = engine;
+ this.logger = jexl.logger;
+ this.uberspect = jexl.uberspect;
+ this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
+ if (this.context instanceof JexlEngine.Options) {
+ JexlEngine.Options opts = (JexlEngine.Options) context;
+ this.arithmetic = jexl.arithmetic.options(opts);
+ if (!arithmetic.getClass().equals(jexl.arithmetic.getClass())) {
+ logger.error("expected arithmetic to be " + jexl.arithmetic.getClass().getSimpleName()
+ + ", got " + arithmetic.getClass().getSimpleName()
+ );
+ }
+ } else {
+ this.arithmetic = jexl.arithmetic;
+ }
+ }
+
+ /** Java7 AutoCloseable interface defined?. */
+ protected static final Class<?> AUTOCLOSEABLE;
+ static {
+ Class<?> c;
+ try {
+ c = Class.forName("java.lang.AutoCloseable");
+ } catch (ClassNotFoundException xclass) {
+ c = null;
+ }
+ AUTOCLOSEABLE = c;
+ }
+
+ /**
+ * Attempt to call close() if supported.
+ * <p>This is used when dealing with auto-closeable (duck-like) objects
+ * @param closeable the object we'd like to close
+ */
+ 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);
+ }
+ }
+ //}
+ }
+ }
+
+ /**
+ * Whether this interpreter is currently evaluating with a strict engine flag.
+ * @return true if strict engine, false otherwise
+ */
+ protected boolean isStrictEngine() {
+ if (this.context instanceof JexlEngine.Options) {
+ JexlEngine.Options opts = (JexlEngine.Options) context;
+ Boolean strict = opts.isStrict();
+ if (strict != null) {
+ return strict.booleanValue();
+ }
+ }
+ return jexl.isStrict();
+ }
+
+ /**
+ * Whether this interpreter is currently evaluating with a silent mode.
+ * @return true if silent, false otherwise
+ */
+ protected boolean isSilent() {
+ if (this.context instanceof JexlEngine.Options) {
+ JexlEngine.Options opts = (JexlEngine.Options) context;
+ Boolean silent = opts.isSilent();
+ if (silent != null) {
+ return silent.booleanValue();
+ }
+ }
+ return jexl.isSilent();
+ }
+
+ /** @return true if interrupt throws a JexlException.Cancel. */
+ protected boolean isCancellable() {
+ if (this.context instanceof JexlEngine.Options) {
+ JexlEngine.Options opts = (JexlEngine.Options) context;
+ Boolean ocancellable = opts.isCancellable();
+ if (ocancellable != null) {
+ return ocancellable.booleanValue();
+ }
+ }
+ return jexl.cancellable;
+ }
+
+ /**
+ * Finds the node causing a NPE for diadic operators.
+ * @param xrt the RuntimeException
+ * @param node the parent node
+ * @param left the left argument
+ * @param right the right argument
+ * @return the left, right or parent node
+ */
+ protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
+ if (xrt instanceof JexlArithmetic.NullOperand) {
+ if (left == null) {
+ return node.jjtGetChild(0);
+ }
+ if (right == null) {
+ return node.jjtGetChild(1);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Triggered when a variable can not be resolved.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @param undef whether the variable is undefined or null
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableVariable(JexlNode node, String var, boolean undef) {
+ if (isStrictEngine()) {
+ if (isSilent()) {
+ logger.warn(JexlException.variableError(node, var, undef));
+ } else if (undef || arithmetic.isStrict()){
+ throw new JexlException.Variable(node, var, undef);
+ }
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.variableError(node, var, undef));
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when a method can not be resolved.
+ * @param node the node where the error originated from
+ * @param method the method name
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableMethod(JexlNode node, String method) {
+ if (isStrictEngine()) {
+ if (isSilent()) {
+ logger.warn(JexlException.methodError(node, method));
+ } else {
+ throw new JexlException.Method(node, method);
+ }
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.methodError(node, method));
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when a property can not be resolved.
+ * @param node the node where the error originated from
+ * @param var the property name
+ * @param cause the cause if any
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object unsolvableProperty(JexlNode node, String var, Throwable cause) {
+ if (isStrictEngine()) {
+ if (isSilent()) {
+ logger.warn(JexlException.propertyError(node, var), cause);
+ } else {
+ throw new JexlException.Property(node, var, cause);
+ }
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.propertyError(node, var), cause);
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when an operator fails.
+ * @param node the node where the error originated from
+ * @param operator the method name
+ * @param cause the cause of error (if any)
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object operatorError(JexlNode node, JexlOperator operator, Throwable cause) {
+ if (isStrictEngine()) {
+ if (isSilent()) {
+ logger.warn(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
+ } else {
+ throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
+ }
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when method, function or constructor invocation fails.
+ * @param xjexl the JexlException wrapping the original error
+ * @return throws a JexlException if strict and not silent, null otherwise
+ */
+ protected Object invocationFailed(JexlException xjexl) {
+ if (xjexl instanceof JexlException.Return
+ || xjexl instanceof JexlException.Cancel) {
+ throw xjexl;
+ }
+ if (isStrictEngine()) {
+ if (isSilent()) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ } else {
+ throw xjexl;
+ }
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(xjexl);
+ }
+ return null;
+ }
+
+ /**
+ * Wraps an exception thrown by an invocation.
+ * @param node the node triggering the exception
+ * @param methodName the method/function name
+ * @param xany the cause
+ * @return a JexlException
+ */
+ protected JexlException exceptionOnInvocation(JexlNode node, String methodName, Exception xany) {
+ Throwable cause = xany.getCause();
+ if (cause instanceof JexlException) {
+ return (JexlException) cause;
+ }
+ if (cause instanceof InterruptedException) {
+ cancelled = true;
+ return new JexlException.Cancel(node);
+ }
+ return new JexlException(node, methodName, xany);
+ }
+
+ /**
+ * 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)
+ * @return throws a JexlException if strict and not silent, null otherwise
+ */
+ protected Object annotationError(JexlNode node, String annotation, Throwable cause) {
+ if (isStrictEngine()) {
+ if (isSilent()) {
+ logger.warn(JexlException.annotationError(node, annotation), cause);
+ } else {
+ throw new JexlException.Annotation(node, annotation, cause);
+ }
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.annotationError(node, annotation), cause);
+ }
+ return null;
+ }
+
+ /**
+ * Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
+ * @return false if already cancelled, true otherwise
+ */
+ protected synchronized boolean cancel() {
+ if (cancelled) {
+ return false;
+ } else {
+ cancelled = true;
+ return true;
+ }
+ }
+
+ /**
+ * Checks whether this interpreter execution was canceled due to thread interruption.
+ * @return true if canceled, false otherwise
+ */
+ protected synchronized boolean isCancelled() {
+ if (!cancelled) {
+ cancelled = Thread.currentThread().isInterrupted();
+ }
+ return cancelled;
+ }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Operators.java b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
index df0b132..ed5fd38 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -24,7 +24,6 @@
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.jexl3.parser.JexlNode;
-import java.util.Iterator;
/**
* Helper class to deal with operator overloading and specifics.
* @since 3.0
@@ -92,45 +91,7 @@
return result;
}
} catch (Exception xany) {
- interpreter.operatorError(node, operator, xany);
- }
- }
- return JexlEngine.TRY_FAILED;
- }
-
- /**
- * Attempts to call an overloaded forEach operator.
- * <p>
- * This takes care of finding and caching the operator method when appropriate
- * @param node the syntactic node
- * @param arg the argument
- * @return the result of the operator evaluation or TRY_FAILED
- */
- protected Object tryForeachOverload(JexlNode node, Object arg) {
- if (operators != null && operators.overloads(JexlOperator.FOR_EACH)) {
- final JexlArithmetic arithmetic = interpreter.arithmetic;
- final boolean cache = interpreter.cache;
- if (cache) {
- Object cached = node.jjtGetValue();
- if (cached instanceof JexlMethod) {
- JexlMethod me = (JexlMethod) cached;
- Object eval = me.tryInvoke(JexlOperator.FOR_EACH.getMethodName(), arithmetic, arg);
- if (!me.tryFailed(eval)) {
- return eval;
- }
- }
- }
- try {
- JexlMethod vm = operators.getOperator(JexlOperator.FOR_EACH, arg);
- if (vm != null && Iterator.class.isAssignableFrom(vm.getReturnType())) {
- Object result = vm.invoke(arithmetic, arg);
- if (cache) {
- node.jjtSetValue(vm);
- }
- return result;
- }
- } catch (Exception xany) {
- interpreter.operatorError(node, JexlOperator.FOR_EACH, xany);
+ return interpreter.operatorError(node, operator, xany);
}
}
return JexlEngine.TRY_FAILED;
diff --git a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
index 1955aa4..5cdd200 100644
--- a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
+++ b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
@@ -124,33 +124,59 @@
@Test
public void testError() throws Exception {
+ testError(true);
+ testError(false);
+ }
+
+ private void testError(boolean silent) throws Exception {
+ CaptureLog log = new CaptureLog();
AnnotationContext jc = new AnnotationContext();
- JexlEngine jexl = new JexlBuilder().create();
+ JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).create();
JexlScript e = jexl.createScript("@error('42') { return 42; }");
try {
Object r = e.execute(jc);
- Assert.fail("should have failed");
+ if (!silent) {
+ Assert.fail("should have failed");
+ } else {
+ Assert.assertEquals(1, log.count("warn"));
+ }
} 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"));
+ if (!silent) {
+ Assert.assertEquals(0, log.count("warn"));
+ }
}
@Test
public void testUnknown() throws Exception {
+ testUnknown(true);
+ testUnknown(false);
+ }
+
+ private void testUnknown(boolean silent) throws Exception {
+ CaptureLog log = new CaptureLog();
AnnotationContext jc = new AnnotationContext();
- JexlEngine jexl = new JexlBuilder().create();
+ JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).create();
JexlScript e = jexl.createScript("@unknown('42') { return 42; }");
try {
Object r = e.execute(jc);
- Assert.fail("should have failed");
+ if (!silent) {
+ Assert.fail("should have failed");
+ } else {
+ Assert.assertEquals(1, log.count("warn"));
+ }
} 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"));
+ if (!silent) {
+ Assert.assertEquals(0, log.count("warn"));
+ }
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
index ae2b988..88308ac 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
@@ -369,6 +369,10 @@
public Date now() {
return new Date(System.currentTimeMillis());
}
+
+ public Date multiply(Date d0, Date d1) {
+ throw new ArithmeticException("unsupported");
+ }
}
public static class DateContext extends MapContext {
@@ -389,6 +393,34 @@
}
@Test
+ public void testOperatorError() throws Exception {
+ testOperatorError(true);
+ testOperatorError(false);
+ }
+
+ private void testOperatorError(boolean silent) throws Exception {
+ CaptureLog log = new CaptureLog();
+ DateContext jc = new DateContext();
+ Date d = new Date();
+ JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).cache(32)
+ .arithmetic(new DateArithmetic(true)).create();
+ JexlScript expr0 = jexl.createScript("date * date", "date");
+ try {
+ Object value0 = expr0.execute(jc, d);
+ if (!silent) {
+ Assert.fail("should have failed");
+ } else {
+ Assert.assertEquals(1, log.count("warn"));
+ }
+ } catch(JexlException.Operator xop) {
+ Assert.assertEquals("*", xop.getSymbol());
+ }
+ if (!silent) {
+ Assert.assertEquals(0, log.count("warn"));
+ }
+ }
+
+ @Test
public void testDateArithmetic() throws Exception {
Date d = new Date();
JexlContext jc = new MapContext();
diff --git a/src/test/java/org/apache/commons/jexl3/CaptureLog.java b/src/test/java/org/apache/commons/jexl3/CaptureLog.java
new file mode 100644
index 0000000..2ae0aa6
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/CaptureLog.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 The Apache Software Foundation.
+ *
+ * Licensed 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.ArrayList;
+import java.util.List;
+import org.apache.commons.logging.Log;
+
+/**
+ * A log implementation to help control tests results.
+ */
+public class CaptureLog implements Log {
+ private List<Object[]> captured = new ArrayList<Object[]>();
+
+ static Object caller() {
+ StackTraceElement[] stack = new Exception().fillInStackTrace().getStackTrace();
+ return stack[2];
+ }
+
+ public boolean isEmpty() {
+ return captured.isEmpty();
+ }
+
+ public int count(String type) {
+ int count = 0;
+ for (Object[] l : captured) {
+ if (type.equals(l[0].toString())) {
+ count += 1;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public void debug(Object o) {
+ captured.add(new Object[]{"debug", caller(), o});
+ }
+
+ @Override
+ public void debug(Object o, Throwable thrwbl) {
+ captured.add(new Object[]{"debug", caller(), o, thrwbl});
+ }
+
+ @Override
+ public void error(Object o) {
+ captured.add(new Object[]{"error", caller(), o});
+ }
+
+ @Override
+ public void error(Object o, Throwable thrwbl) {
+ captured.add(new Object[]{"error", caller(), o, thrwbl});
+ }
+
+ @Override
+ public void fatal(Object o) {
+ captured.add(new Object[]{"fatal", caller(), o});
+ }
+
+ @Override
+ public void fatal(Object o, Throwable thrwbl) {
+ captured.add(new Object[]{"fatal", caller(), o, thrwbl});
+ }
+
+ @Override
+ public void info(Object o) {
+ captured.add(new Object[]{"info", caller(), o});
+ }
+
+ @Override
+ public void info(Object o, Throwable thrwbl) {
+ captured.add(new Object[]{"info", caller(), o, thrwbl});
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isFatalEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return true;
+ }
+
+ @Override
+ public void trace(Object o) {
+ captured.add(new Object[]{"trace", caller(), o});
+ }
+
+ @Override
+ public void trace(Object o, Throwable thrwbl) {
+ captured.add(new Object[]{"trace", caller(), o, thrwbl});
+ }
+
+ @Override
+ public void warn(Object o) {
+ captured.add(new Object[]{"warn", caller(), o});
+ }
+
+ @Override
+ public void warn(Object o, Throwable thrwbl) {
+ captured.add(new Object[]{"warn", caller(), o, thrwbl});
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
index 003aa13..81f870d 100644
--- a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
@@ -16,7 +16,10 @@
*/
package org.apache.commons.jexl3;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.logging.Log;
import org.junit.Assert;
import org.junit.Test;
@@ -160,4 +163,52 @@
Assert.assertTrue(msg.indexOf("c.e") > 0);
}
}
+
+
+ @Test
+ public void test206() throws Exception {
+ String src = "null.1 = 2; return 42";
+ doTest206(src, false, false);
+ doTest206(src, false, true);
+ doTest206(src, true, false);
+ doTest206(src, true, true);
+ src = "x = null.1; return 42";
+ doTest206(src, false, false);
+ doTest206(src, false, true);
+ doTest206(src, true, false);
+ doTest206(src, true, true);
+ src = "x = y.1; return 42";
+ doTest206(src, false, false);
+ doTest206(src, false, true);
+ doTest206(src, true, false);
+ doTest206(src, true, true);
+ }
+ private void doTest206(String src, boolean strict, boolean silent) throws Exception {
+ CaptureLog l = new CaptureLog();
+ JexlContext jc = new MapContext();
+ JexlEngine jexl = new JexlBuilder().logger(l).strict(strict).silent(silent).create();
+ JexlScript e;
+ Object r = -1;
+ e = jexl.createScript(src);
+ try {
+ r = e.execute(jc);
+ if (strict && !silent) {
+ Assert.fail("should have thrown an exception");
+ }
+ } catch(JexlException xjexl) {
+ if (!strict || silent) {
+ Assert.fail("should not have thrown an exception");
+ }
+ }
+ if (strict) {
+ if (silent && l.count("warn") == 0) {
+ Assert.fail("should have generated a warning");
+ }
+ } else {
+ if (l.count("debug") == 0) {
+ Assert.fail("should have generated a debug");
+ }
+ Assert.assertEquals(42, r);
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest.java b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
index 83eafa2..d139f9c 100644
--- a/src/test/java/org/apache/commons/jexl3/IssuesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
@@ -17,17 +17,19 @@
package org.apache.commons.jexl3;
import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.jexl3.internal.introspection.Uberspect;
+
+import java.io.File;
import java.math.BigDecimal;
import java.math.MathContext;
-import java.util.HashMap;
-import java.util.Map;
-import org.apache.commons.jexl3.internal.introspection.Uberspect;
-import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
diff --git a/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java b/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
index 661c0c1..0e4e34b 100644
--- a/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.jexl3;
+import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -27,6 +28,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.jexl3.internal.Script;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Test;
@@ -35,7 +38,7 @@
*/
@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
public class ScriptCallableTest extends JexlTestCase {
- //Logger LOGGER = Logger.getLogger(VarTest.class.getName());
+ //private Log logger = LogFactory.getLog(JexlEngine.class);
public ScriptCallableTest() {
super("ScriptCallableTest");
}
@@ -64,6 +67,7 @@
@Test
public void testCallableCancel() throws Exception {
+ List<Runnable> lr = null;
final Semaphore latch = new Semaphore(0);
JexlContext ctxt = new MapContext();
ctxt.set("latch", latch);
@@ -89,13 +93,15 @@
// ok, ignore
Assert.assertTrue(xexec.getCause() instanceof JexlException.Cancel);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
Assert.assertTrue(c.isCancelled());
+ Assert.assertTrue(lr == null || lr.isEmpty());
}
@Test
public void testCallableTimeout() throws Exception {
+ List<Runnable> lr = null;
final Semaphore latch = new Semaphore(0);
JexlContext ctxt = new MapContext();
ctxt.set("latch", latch);
@@ -114,14 +120,16 @@
// ok, ignore
future.cancel(true);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCallableClosure() throws Exception {
+ List<Runnable> lr = null;
JexlScript e = JEXL.createScript("function(t) {while(t);}");
Callable<Object> c = e.callable(null, Boolean.TRUE);
Object t = 42;
@@ -135,10 +143,11 @@
// ok, ignore
future.cancel(true);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
public static class TestContext extends MapContext implements JexlContext.NamespaceResolver {
@@ -191,6 +200,7 @@
@Test
public void testNoWait() throws Exception {
+ List<Runnable> lr = null;
JexlScript e = JEXL.createScript("wait(0)");
Callable<Object> c = e.callable(new TestContext());
@@ -201,12 +211,14 @@
Assert.assertTrue(future.isDone());
Assert.assertEquals(0, t);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testWait() throws Exception {
+ List<Runnable> lr = null;
JexlScript e = JEXL.createScript("wait(1)");
Callable<Object> c = e.callable(new TestContext());
@@ -216,12 +228,14 @@
Object t = future.get(2, TimeUnit.SECONDS);
Assert.assertEquals(1, t);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelWait() throws Exception {
+ List<Runnable> lr = null;
JexlScript e = JEXL.createScript("wait(10)");
Callable<Object> c = e.callable(new TestContext());
@@ -239,12 +253,14 @@
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelWaitInterrupt() throws Exception {
+ List<Runnable> lr = null;
JexlScript e = JEXL.createScript("waitInterrupt(42)");
Callable<Object> c = e.callable(new TestContext());
@@ -259,14 +275,16 @@
// ok, ignore
future.cancel(true);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelForever() throws Exception {
+ List<Runnable> lr = null;
final Semaphore latch = new Semaphore(0);
JexlContext ctxt = new TestContext();
ctxt.set("latch", latch);
@@ -286,14 +304,16 @@
// ok, ignore
future.cancel(true);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelLoopWait() throws Exception {
+ List<Runnable> lr = null;
JexlScript e = JEXL.createScript("while (true) { wait(10) }");
Callable<Object> c = e.callable(new TestContext());
@@ -307,10 +327,11 @@
} catch (TimeoutException xtimeout) {
future.cancel(true);
} finally {
- executor.shutdown();
+ lr = executor.shutdownNow();
}
Assert.assertTrue(future.isCancelled());
Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
@@ -345,6 +366,7 @@
* @throws Exception if there is a regression
*/
private void runInterrupt(JexlEngine jexl) throws Exception {
+ List<Runnable> lr = null;
ExecutorService exec = Executors.newFixedThreadPool(2);
try {
JexlContext ctxt = new TestContext();
@@ -411,7 +433,7 @@
}
};
exec.submit(cancels);
- t = fc.get();
+ t = f.get(100L, TimeUnit.MILLISECONDS);
Assert.fail("should be cancelled");
} catch (CancellationException xexec) {
// this is the expected result
@@ -452,8 +474,9 @@
}
Assert.assertNotEquals(42, t);
} finally {
- exec.shutdown();
+ lr = exec.shutdownNow();
}
+ Assert.assertTrue(lr.isEmpty());
}
@Test
diff --git a/src/test/java/org/apache/commons/jexl3/ScriptTest.java b/src/test/java/org/apache/commons/jexl3/ScriptTest.java
index 1bdf2c5..a61bbe4 100644
--- a/src/test/java/org/apache/commons/jexl3/ScriptTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ScriptTest.java
@@ -28,6 +28,7 @@
@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
public class ScriptTest extends JexlTestCase {
static final String TEST1 = "src/test/scripts/test1.jexl";
+ static final String TEST_ADD = "src/test/scripts/testAdd.jexl";
// test class for testScriptUpdatesContext
// making this class private static will cause the test to fail.
@@ -74,8 +75,18 @@
Assert.assertEquals("Wrong result", new Integer(7), result);
}
+ @Test public void testArgScriptFromFile() throws Exception {
+ File testScript = new File(TEST_ADD);
+ JexlScript s = JEXL.createScript(testScript,new String[]{"x","y"});
+ JexlContext jc = new MapContext();
+ jc.set("out", System.out);
+ Object result = s.execute(jc, 13, 29);
+ Assert.assertNotNull("No result", result);
+ Assert.assertEquals("Wrong result", new Integer(42), result);
+ }
+
@Test public void testScriptFromURL() throws Exception {
- URL testUrl = new File("src/test/scripts/test1.jexl").toURI().toURL();
+ URL testUrl = new File(TEST1).toURI().toURL();
JexlScript s = JEXL.createScript(testUrl);
JexlContext jc = new MapContext();
jc.set("out", System.out);
@@ -84,6 +95,16 @@
Assert.assertEquals("Wrong result", new Integer(7), result);
}
+ @Test public void testArgScriptFromURL() throws Exception {
+ URL testUrl = new File(TEST_ADD).toURI().toURL();
+ JexlScript s = JEXL.createScript(testUrl,new String[]{"x","y"});
+ JexlContext jc = new MapContext();
+ jc.set("out", System.out);
+ Object result = s.execute(jc, 13, 29);
+ Assert.assertNotNull("No result", result);
+ Assert.assertEquals("Wrong result", new Integer(42), result);
+ }
+
@Test public void testScriptUpdatesContext() throws Exception {
String jexlCode = "resultat.setCode('OK')";
JexlExpression e = JEXL.createExpression(jexlCode);
diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml
index d0ae637..b46ac17 100644
--- a/src/test/resources/log4j.xml
+++ b/src/test/resources/log4j.xml
@@ -21,7 +21,7 @@
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n"/>
</layout>
</appender>
-
+
<logger name="org.apache.commons.jexl3" additivity="false">
<level value="error"/>
<appender-ref ref="Console"/>
diff --git a/src/test/scripts/testAdd.jexl b/src/test/scripts/testAdd.jexl
new file mode 100644
index 0000000..ed97e0c
--- /dev/null
+++ b/src/test/scripts/testAdd.jexl
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+//(x, y)->{ x + y }
+x + y