(...)");
}
ASTArguments argNode = (ASTArguments) node.jjtGetChild(a);
result = call(node, object, result, argNode);
- object = null;
+ object = result;
}
return result;
}
@@ -1416,13 +1358,9 @@ protected Object visit(final ASTMethodNode node, Object data) {
protected Object visit(ASTFunctionNode node, Object data) {
int argc = node.jjtGetNumChildren();
if (argc == 2) {
- Object namespace = resolveNamespace(null, node);
- if (namespace == null) {
- namespace = context;
- }
ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
- return call(node, namespace, functionNode, argNode);
+ return call(node, context, functionNode, argNode);
} else {
// objectNode 0 is the prefix
String prefix = ((ASTIdentifier) node.jjtGetChild(0)).getName();
@@ -1437,34 +1375,41 @@ protected Object visit(ASTFunctionNode node, Object data) {
/**
* Concatenate arguments in call(...).
* When target == context, we are dealing with a global namespace function call
- * @param target the pseudo-method owner, first to-be argument
- * @param args the other 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, Object[] args) {
- if (args == null) {
- return new Object[]{target};
- }
- if (context == target) {
+ private Object[] functionArguments(Object target, boolean narrow, Object[] args) {
+ // when target == context, we are dealing with the null namespace
+ if (target == null || target == context) {
+ if (narrow) {
+ arithmetic.narrowArguments(args);
+ }
return args;
}
+ // makes target 1st args, copy others - optionally narrow numbers
Object[] nargv = new Object[args.length + 1];
- nargv[0] = target;
- System.arraycopy(args, 0, nargv, 1, args.length);
+ if (narrow) {
+ nargv[0] = functionArgument(true, target);
+ for (int a = 1; a <= args.length; ++a) {
+ nargv[a] = functionArgument(true, args[a - 1]);
+ }
+ } else {
+ nargv[0] = target;
+ System.arraycopy(args, 0, nargv, 1, args.length);
+ }
return nargv;
}
/**
- * Narrow arguments in call(...).
- * @param narrow predicate
- * @param args the arguments to narrow
- * @return the narrowed arguments (or the original ones)
+ * Optionally narrows an argument for a function call.
+ * @param narrow whether narrowing should occur
+ * @param arg the argument
+ * @return the narrowed argument
*/
- private Object[] narrowArguments(boolean narrow, Object[] args) {
- if (narrow) {
- arithmetic.narrowArguments(args);
- }
- return args;
+ private Object functionArgument(boolean narrow, Object arg) {
+ return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
}
/**
@@ -1477,7 +1422,7 @@ private static class Funcall {
protected final JexlMethod me;
/**
* Constructor.
- * @param jme the method
+ * @param jme the method
* @param flag the narrow flag
*/
protected Funcall(JexlMethod jme, boolean flag) {
@@ -1487,14 +1432,14 @@ protected Funcall(JexlMethod jme, boolean flag) {
/**
* 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) {
- return me.tryInvoke(name, target, ii.narrowArguments(narrow, args));
+ return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
}
}
@@ -1504,7 +1449,7 @@ protected Object tryInvoke(Interpreter ii, String name, Object target, Object[]
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) {
@@ -1513,17 +1458,17 @@ protected ArithmeticFuncall(JexlMethod jme, boolean flag) {
@Override
protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
- Object[] nargs = ii.functionArguments(target, args);
- return me.tryInvoke(name, ii.arithmetic, ii.narrowArguments(narrow, nargs));
+ 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) {
@@ -1532,8 +1477,7 @@ protected ContextFuncall(JexlMethod jme, boolean flag) {
@Override
protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
- Object[] nargs = ii.functionArguments(target, args);
- return me.tryInvoke(name, ii.context, ii.narrowArguments(narrow, nargs));
+ return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
}
}
@@ -1558,12 +1502,11 @@ protected Object call(final JexlNode node, Object target, Object functor, final
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
- JexlException xjexl;
// evaluate the arguments
Object[] argv = visit(argNode, null);
// get the method name if identifier
- String methodName = null;
- int symbol = -1;
+ final int symbol;
+ final String methodName;
if (functor instanceof ASTIdentifier) {
ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
symbol = methodIdentifier.getSymbol();
@@ -1571,26 +1514,58 @@ protected Object call(final JexlNode node, Object target, Object functor, final
functor = null;
} else if (functor instanceof ASTIdentifierAccess) {
methodName = ((ASTIdentifierAccess) functor).getName();
+ symbol = -1;
functor = null;
+ } else if (functor != null) {
+ symbol = -1 - 1; // -2;
+ methodName = null;
+ } else {
+ return unsolvableMethod(node, "?");
}
+ // at this point, either the functor is a non null (hopefully) 'invocable' object or we do have the methodName
+ Object caller = target;
try {
boolean cacheable = cache;
- // attempt to reuse last funcall cached in volatile JexlNode.value
- if (cache) {
- Object cached = node.jjtGetValue();
- if (cached instanceof Funcall) {
- Object eval = ((Funcall) cached).tryInvoke(this, methodName, target, argv);
- if (JexlEngine.TRY_FAILED != eval) {
- return eval;
+ // do we have a method/function name ?
+ if (methodName != null) {
+ // is it a global or local variable ?
+ if (target == context) {
+ boolean isavar = true;
+ if (symbol >= 0) {
+ functor = frame.get(symbol);
+ } else if (context.has(methodName)) {
+ functor = context.get(methodName);
+ } else {
+ isavar = false;
+ }
+ // name is a variable, must be a functor, cant be cached
+ if (isavar) {
+ if (functor == null) {
+ return unsolvableMethod(node, methodName);
+ }
+ cacheable = false;
}
}
+ // attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
+ if (cacheable) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof Funcall) {
+ Object eval = ((Funcall) cached).tryInvoke(this, methodName, target, argv);
+ if (JexlEngine.TRY_FAILED != eval) {
+ return eval;
+ }
+ }
+ }
+ } else {
+ // if no name, we should not cache
+ cacheable = false;
}
boolean narrow = false;
JexlMethod vm = null;
Funcall funcall = null;
- // pseudo loop and a half
+ // pseudo loop and a half to try acquiring methods without and with argument narrowing
while (true) {
- if (methodName != null) {
+ if (functor == null) {
// try a method
vm = uberspect.getMethod(target, methodName, argv);
if (vm != null) {
@@ -1599,12 +1574,19 @@ protected Object call(final JexlNode node, Object target, Object functor, final
}
break;
}
- }
- // could not find a method, try as a var (local, global) or property (performed once)
- if (functor == null && !narrow) {
- if (symbol >= 0) {
- functor = frame.get(symbol);
- } else {
+ // solve 'null' namespace
+ if (target == context) {
+ Object namespace = resolveNamespace(null, node);
+ if (namespace == context) {
+ // we can not solve it
+ break;
+ } 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)
+ } else if (!narrow) {
// the method may be a functor stored in a property of the target
JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
if (get != null) {
@@ -1612,7 +1594,10 @@ protected Object call(final JexlNode node, Object target, Object functor, final
}
}
}
+ final Object[] nargv;
+ final String mname;
// this may happen without the above when we are chaining call like x(a)(b)
+ // or when a var/symbol or antish var is used as a "function" name
if (functor != null) {
// lambda, script or jexl method will do
if (functor instanceof JexlScript) {
@@ -1622,42 +1607,42 @@ protected Object call(final JexlNode node, Object target, Object functor, final
return ((JexlMethod) functor).invoke(target, argv);
}
// a generic callable
- vm = uberspect.getMethod(functor, "call", argv);
+ mname = "call";
+ vm = uberspect.getMethod(functor, mname, argv);
if (vm != null) {
return vm.invoke(functor, argv);
}
+ // prepend functor to arg to try JexlArithmetic or JexlContext function
+ nargv = functionArguments(functor, narrow, argv);
+ } else {
+ mname = methodName;
+ // no need to narrow since this has been performed in previous loop
+ nargv = functionArguments(caller, narrow, argv);
}
- // try JexlArithmetic or JexlContext function
- if (methodName != null) {
- // when target == context, we are dealing with a global namespace function call
- Object[] nargv = functionArguments(target, argv);
- vm = uberspect.getMethod(context, methodName, nargv);
- if (vm != null) {
- argv = nargv;
- target = context;
- if (cacheable && vm.isCacheable()) {
- funcall = new ContextFuncall(vm, narrow);
- }
- break;
- }
- vm = uberspect.getMethod(arithmetic, methodName, nargv);
- if (vm != null) {
- argv = nargv;
- target = arithmetic;
- if (cacheable && vm.isCacheable()) {
- funcall = new ArithmeticFuncall(vm, narrow);
- }
- break;
+ vm = uberspect.getMethod(context, mname, nargv);
+ if (vm != null) {
+ argv = nargv;
+ target = context;
+ if (cacheable && vm.isCacheable()) {
+ funcall = new ContextFuncall(vm, narrow);
}
- // if we did not find an exact method by name and we haven't tried yet,
- // attempt to narrow the parameters and if this succeeds, try again in next loop
- if (arithmetic.narrowArguments(argv)) {
- narrow = true;
- continue;
+ break;
+ }
+ vm = uberspect.getMethod(arithmetic, mname, nargv);
+ if (vm != null) {
+ argv = nargv;
+ target = arithmetic;
+ if (cacheable && vm.isCacheable()) {
+ funcall = new ArithmeticFuncall(vm, narrow);
}
+ break;
}
- // we are done trying
- break;
+ // if we did not find an exact method by name and we haven't tried yet,
+ // attempt to narrow the parameters and if this succeeds, try again in next loop
+ if (!arithmetic.narrowArguments(argv)) {
+ break;
+ }
+ narrow = true;
}
// we have either evaluated and returned or might have found a method
if (vm != null) {
@@ -1670,12 +1655,11 @@ protected Object call(final JexlNode node, Object target, Object functor, final
return eval;
}
return unsolvableMethod(node, methodName);
- } catch (JexlException.Method xmethod) {
- throw xmethod;
+ } catch (JexlException xthru) {
+ throw xthru;
} catch (Exception xany) {
- xjexl = new JexlException(node, methodName, xany);
+ throw invocationException(node, methodName, xany);
}
- return invocationFailed(xjexl);
}
@Override
@@ -1692,7 +1676,6 @@ protected Object visit(ASTConstructorNode node, Object data) {
argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, data);
}
- JexlException xjexl = null;
try {
// attempt to reuse last constructor cached in volatile JexlNode.value
if (cache) {
@@ -1722,13 +1705,12 @@ protected Object visit(ASTConstructorNode node, Object data) {
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 = new JexlException(node, dbgStr, xany);
+ throw invocationException(node, dbgStr, xany);
}
- return invocationFailed(xjexl);
}
/**
@@ -1758,37 +1740,37 @@ protected Object getAttribute(Object object, Object attribute, JexlNode node) {
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;
}
- // attempt to reuse last executor cached in volatile JexlNode.value
- if (node != null && cache) {
- Object cached = node.jjtGetValue();
- if (cached instanceof JexlPropertyGet) {
- JexlPropertyGet vg = (JexlPropertyGet) cached;
- Object value = vg.tryInvoke(object, attribute);
- if (!vg.tryFailed(value)) {
- return value;
+ Exception xcause = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertyGet) {
+ JexlPropertyGet vg = (JexlPropertyGet) cached;
+ Object value = vg.tryInvoke(object, attribute);
+ if (!vg.tryFailed(value)) {
+ return value;
+ }
}
}
- }
- // resolve that property
- Exception xcause = null;
- List resolvers = uberspect.getResolvers(operator, object);
- JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
- if (vg != null) {
- try {
+ // resolve that property
+ List resolvers = uberspect.getResolvers(operator, object);
+ JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
+ if (vg != null) {
Object value = vg.invoke(object);
// cache executor in volatile JexlNode.value
if (node != null && cache && vg.isCacheable()) {
node.jjtSetValue(vg);
}
return value;
- } catch (Exception xany) {
- xcause = xany;
}
+ } catch (Exception xany) {
+ xcause = xany;
}
// lets fail
if (node != null) {
@@ -1827,44 +1809,44 @@ protected void setAttribute(Object object, Object attribute, Object value, JexlN
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;
}
- // attempt to reuse last executor cached in volatile JexlNode.value
- if (node != null && cache) {
- Object cached = node.jjtGetValue();
- if (cached instanceof JexlPropertySet) {
- JexlPropertySet setter = (JexlPropertySet) cached;
- Object eval = setter.tryInvoke(object, attribute, value);
- if (!setter.tryFailed(eval)) {
- return;
+ Exception xcause = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertySet) {
+ JexlPropertySet setter = (JexlPropertySet) cached;
+ Object eval = setter.tryInvoke(object, attribute, value);
+ if (!setter.tryFailed(eval)) {
+ return;
+ }
}
}
- }
- Exception xcause = null;
- List resolvers = uberspect.getResolvers(operator, object);
- JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
- // if we can't find an exact match, narrow the value argument and try again
- if (vs == null) {
- // replace all numbers with the smallest type that will fit
- Object[] narrow = {value};
- if (arithmetic.narrowArguments(narrow)) {
- vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
+ List resolvers = uberspect.getResolvers(operator, object);
+ JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
+ // if we can't find an exact match, narrow the value argument and try again
+ if (vs == null) {
+ // replace all numbers with the smallest type that will fit
+ Object[] narrow = {value};
+ if (arithmetic.narrowArguments(narrow)) {
+ vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
+ }
}
- }
- if (vs != null) {
- try {
+ if (vs != null) {
// cache executor in volatile JexlNode.value
vs.invoke(object, value);
if (node != null && cache && vs.isCacheable()) {
node.jjtSetValue(vs);
}
return;
- } catch (Exception xany) {
- xcause = xany;
}
+ } catch (Exception xany) {
+ xcause = xany;
}
// lets fail
if (node != null) {
@@ -1884,14 +1866,98 @@ protected void setAttribute(Object object, Object attribute, Object value, JexlN
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);
+ // if the context has changed, might need a new interpreter
+ final JexlArithmetic jexla = arithmetic.options(context);
+ if (jexla != arithmetic) {
+ if (!arithmetic.getClass().equals(jexla.getClass())) {
+ logger.warn("expected arithmetic to be " + arithmetic.getClass().getSimpleName()
+ + ", got " + jexla.getClass().getSimpleName()
+ );
+ }
+ Interpreter ii = new Interpreter(Interpreter.this, jexla);
+ Object r = block.jjtAccept(ii, data);
+ if (ii.isCancelled()) {
+ Interpreter.this.cancel();
+ }
+ return r;
+ } else {
+ return block.jjtAccept(Interpreter.this, data);
+ }
+ }
+ // tracking whether we processed the annotation
+ final boolean[] processed = new boolean[]{false};
+ final Callable jstmt = new Callable() {
+ @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]) {
+ return annotationError(anode, aname, null);
+ } else {
+ return result;
+ }
+ } catch(JexlException xjexl) {
+ throw xjexl;
+ } catch(Exception xany) {
+ return annotationError(anode, aname, xany);
+ }
+ }
+
+ /**
+ * 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 stmt) throws Exception {
+ return context instanceof JexlContext.AnnotationProcessor
+ ? ((JexlContext.AnnotationProcessor) context).processAnnotation(annotation, args, stmt)
+ : stmt.call();
+ }
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
new file mode 100644
index 000000000..eaadf32e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -0,0 +1,301 @@
+/*
+ * 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;
+ JexlArithmetic jexla = jexl.arithmetic;
+ this.arithmetic = jexla.options(context);
+ if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) {
+ logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
+ + ", got " + arithmetic.getClass().getSimpleName()
+ );
+ }
+ }
+
+ /**
+ * Copy constructor.
+ * @param ii the base to copy
+ * @param jexla the arithmetic instance to use (or null)
+ */
+ protected InterpreterBase(InterpreterBase ii, JexlArithmetic jexla) {
+ jexl = ii.jexl;
+ logger = ii.logger;
+ uberspect = ii.uberspect;
+ context = ii.context;
+ arithmetic = ii.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.
+ * 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.isCancellable();
+ }
+
+ /**
+ * 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() && (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()) {
+ 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()) {
+ 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()) {
+ throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
+ }
+ return null;
+ }
+
+ /**
+ * 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()) {
+ throw new JexlException.Annotation(node, annotation, cause);
+ } else if (logger.isDebugEnabled()) {
+ logger.debug(JexlException.annotationError(node, annotation), cause);
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when method, function or constructor invocation fails with an exception.
+ * @param node the node triggering the exception
+ * @param methodName the method/function name
+ * @param xany the cause
+ * @return a JexlException that will be thrown
+ */
+ protected JexlException invocationException(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);
+ }
+
+ /**
+ * 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 cancelled due to thread interruption.
+ * @return true if cancelled, 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 036a7cbc8..d157445bc 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Operators.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Operators.java
@@ -71,17 +71,17 @@ protected Object tryOverload(JexlNode node, JexlOperator operator, Object... arg
if (operators != null && operators.overloads(operator)) {
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(operator.getMethodName(), arithmetic, args);
- if (!me.tryFailed(eval)) {
- return eval;
+ try {
+ if (cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlMethod) {
+ JexlMethod me = (JexlMethod) cached;
+ Object eval = me.tryInvoke(operator.getMethodName(), arithmetic, args);
+ if (!me.tryFailed(eval)) {
+ return eval;
+ }
}
}
- }
- try {
JexlMethod vm = operators.getOperator(operator, args);
if (vm != null) {
Object result = vm.invoke(arithmetic, args);
@@ -91,7 +91,7 @@ protected Object tryOverload(JexlNode node, JexlOperator operator, Object... arg
return result;
}
} catch (Exception xany) {
- interpreter.operatorError(node, operator, xany);
+ return interpreter.operatorError(node, operator, xany);
}
}
return JexlEngine.TRY_FAILED;
@@ -139,26 +139,32 @@ protected Object tryAssignOverload(JexlNode node, JexlOperator operator, Object.
}
}
// base eval
- switch (operator) {
- case SELF_ADD:
- return arithmetic.add(args[0], args[1]);
- case SELF_SUBTRACT:
- return arithmetic.subtract(args[0], args[1]);
- case SELF_MULTIPLY:
- return arithmetic.multiply(args[0], args[1]);
- case SELF_DIVIDE:
- return arithmetic.divide(args[0], args[1]);
- case SELF_MOD:
- return arithmetic.mod(args[0], args[1]);
- case SELF_AND:
- return arithmetic.and(args[0], args[1]);
- case SELF_OR:
- return arithmetic.or(args[0], args[1]);
- case SELF_XOR:
- return arithmetic.xor(args[0], args[1]);
- default:
- throw new JexlException.Operator(node, operator.getOperatorSymbol(), null);
+ try {
+ switch (operator) {
+ case SELF_ADD:
+ return arithmetic.add(args[0], args[1]);
+ case SELF_SUBTRACT:
+ return arithmetic.subtract(args[0], args[1]);
+ case SELF_MULTIPLY:
+ return arithmetic.multiply(args[0], args[1]);
+ case SELF_DIVIDE:
+ return arithmetic.divide(args[0], args[1]);
+ case SELF_MOD:
+ return arithmetic.mod(args[0], args[1]);
+ case SELF_AND:
+ return arithmetic.and(args[0], args[1]);
+ case SELF_OR:
+ return arithmetic.or(args[0], args[1]);
+ case SELF_XOR:
+ return arithmetic.xor(args[0], args[1]);
+ default:
+ // unexpected, new operator added?
+ throw new UnsupportedOperationException(operator.getOperatorSymbol());
+ }
+ } catch (Exception xany) {
+ interpreter.operatorError(node, base, xany);
}
+ return JexlEngine.TRY_FAILED;
}
/**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java b/src/main/java/org/apache/commons/jexl3/internal/Script.java
index ec897216d..eeb727f9c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
@@ -26,7 +26,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.Callable;
/**
*
A JexlScript implementation.
@@ -50,6 +49,13 @@ public class Script implements JexlScript, JexlExpression {
*/
protected int version;
+ /**
+ * @return the script AST
+ */
+ protected ASTJexlScript getScript() {
+ return script;
+ }
+
/**
* Do not let this be generally instantiated with a 'new'.
*
@@ -75,7 +81,10 @@ protected Script(Engine engine, String expr, ASTJexlScript ref) {
protected void checkCacheVersion() {
int uberVersion = jexl.getUberspect().getVersion();
if (version != uberVersion) {
- script.clearCache();
+ // version 0 of the uberSpect is an illusion due to order of construction; no need to clear cache
+ if (version > 0) {
+ script.clearCache();
+ }
version = uberVersion;
}
}
@@ -106,25 +115,16 @@ public JexlEngine getEngine() {
return jexl;
}
- /**
- * {@inheritDoc}
- */
@Override
public String getSourceText() {
return source;
}
- /**
- * {@inheritDoc}
- */
@Override
public String getParsedText() {
return getParsedText(2);
}
- /**
- * {@inheritDoc}
- */
@Override
public String getParsedText(int indent) {
Debugger debug = new Debugger();
@@ -169,37 +169,22 @@ public String toString() {
debug.debug(script);
src = debug.toString();
}
- return src == null ? "/*no source*/" : src.toString();
+ return src.toString();
}
- /**
- * {@inheritDoc}
- */
@Override
public Object evaluate(JexlContext context) {
- if (script.jjtGetNumChildren() < 1) {
- return null;
- }
- checkCacheVersion();
- Scope.Frame frame = createFrame((Object[]) null);
- Interpreter interpreter = createInterpreter(context, frame);
- return interpreter.interpret(script.jjtGetChild(0));
+ return execute(context);
}
- /**
- * {@inheritDoc}
- */
@Override
public Object execute(JexlContext context) {
checkCacheVersion();
- Scope.Frame frame = createFrame((Object[]) null);
+ Scope.Frame frame = createFrame(null);
Interpreter interpreter = createInterpreter(context, frame);
return interpreter.interpret(script);
}
- /**
- * {@inheritDoc}
- */
@Override
public Object execute(JexlContext context, Object... args) {
checkCacheVersion();
@@ -262,9 +247,6 @@ public Object execute(JexlContext context, Object... args) {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlScript curry(Object... args) {
String[] parms = script.getParameters();
@@ -322,7 +304,7 @@ public Map getPragmas() {
* @return the callable
*/
@Override
- public Callable callable(JexlContext context) {
+ public Callable callable(JexlContext context) {
return callable(context, (Object[]) null);
}
@@ -335,20 +317,67 @@ public Callable callable(JexlContext context) {
* @return the callable
*/
@Override
- public Callable callable(JexlContext context, Object... args) {
- final Interpreter interpreter = jexl.createInterpreter(context, script.createFrame(args));
- return new Callable() {
- /** Use interpreter as marker for not having run. */
- private Object result = interpreter;
-
- @Override
- public Object call() throws Exception {
+ public Callable callable(JexlContext context, Object... args) {
+ return new Callable(jexl.createInterpreter(context, script.createFrame(args)));
+ }
+
+ /**
+ * Implements the Future and Callable interfaces to help delegation.
+ */
+ public class Callable implements java.util.concurrent.Callable {
+ /** The actual interpreter. */
+ protected final Interpreter interpreter;
+ /** Use interpreter as marker for not having run. */
+ protected volatile Object result;
+
+ /**
+ * The base constructor.
+ * @param intrprtr the interpreter to use
+ */
+ protected Callable(Interpreter intrprtr) {
+ this.interpreter = intrprtr;
+ this.result = intrprtr;
+ }
+
+ /**
+ * Run the interpreter.
+ * @return the evaluation result
+ */
+ protected Object interpret() {
+ return interpreter.interpret(script);
+ }
+
+ @Override
+ public Object call() throws Exception {
+ synchronized(this) {
if (result == interpreter) {
checkCacheVersion();
- result = interpreter.interpret(script);
+ result = interpret();
}
return result;
}
- };
+ }
+
+ /**
+ * Soft cancel the execution.
+ * @return true if cancel was successful, false otherwise
+ */
+ public boolean cancel() {
+ return interpreter.cancel();
+ }
+
+ /**
+ * @return true if evaluation was cancelled, false otherwise
+ */
+ public boolean isCancelled() {
+ return interpreter.isCancelled();
+ }
+
+ /**
+ * @return true if interruption will throw a JexlException.Cancel, false otherwise
+ */
+ public boolean isCancellable() {
+ return interpreter.isCancellable();
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
new file mode 100644
index 000000000..8f728007c
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java
@@ -0,0 +1,496 @@
+/*
+ * 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.JexlExpression;
+import org.apache.commons.jexl3.JexlScript;
+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;
+import org.apache.commons.jexl3.parser.ASTAssignment;
+import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
+import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
+import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
+import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
+import org.apache.commons.jexl3.parser.ASTBlock;
+import org.apache.commons.jexl3.parser.ASTBreak;
+import org.apache.commons.jexl3.parser.ASTConstructorNode;
+import org.apache.commons.jexl3.parser.ASTContinue;
+import org.apache.commons.jexl3.parser.ASTDivNode;
+import org.apache.commons.jexl3.parser.ASTEQNode;
+import org.apache.commons.jexl3.parser.ASTERNode;
+import org.apache.commons.jexl3.parser.ASTEWNode;
+import org.apache.commons.jexl3.parser.ASTEmptyFunction;
+import org.apache.commons.jexl3.parser.ASTEmptyMethod;
+import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
+import org.apache.commons.jexl3.parser.ASTFalseNode;
+import org.apache.commons.jexl3.parser.ASTForeachStatement;
+import org.apache.commons.jexl3.parser.ASTFunctionNode;
+import org.apache.commons.jexl3.parser.ASTGENode;
+import org.apache.commons.jexl3.parser.ASTGTNode;
+import org.apache.commons.jexl3.parser.ASTIdentifier;
+import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
+import org.apache.commons.jexl3.parser.ASTIfStatement;
+import org.apache.commons.jexl3.parser.ASTJexlScript;
+import org.apache.commons.jexl3.parser.ASTJxltLiteral;
+import org.apache.commons.jexl3.parser.ASTLENode;
+import org.apache.commons.jexl3.parser.ASTLTNode;
+import org.apache.commons.jexl3.parser.ASTMapEntry;
+import org.apache.commons.jexl3.parser.ASTMapLiteral;
+import org.apache.commons.jexl3.parser.ASTMethodNode;
+import org.apache.commons.jexl3.parser.ASTModNode;
+import org.apache.commons.jexl3.parser.ASTMulNode;
+import org.apache.commons.jexl3.parser.ASTNENode;
+import org.apache.commons.jexl3.parser.ASTNEWNode;
+import org.apache.commons.jexl3.parser.ASTNRNode;
+import org.apache.commons.jexl3.parser.ASTNSWNode;
+import org.apache.commons.jexl3.parser.ASTNotNode;
+import org.apache.commons.jexl3.parser.ASTNullLiteral;
+import org.apache.commons.jexl3.parser.ASTNullpNode;
+import org.apache.commons.jexl3.parser.ASTNumberLiteral;
+import org.apache.commons.jexl3.parser.ASTOrNode;
+import org.apache.commons.jexl3.parser.ASTRangeNode;
+import org.apache.commons.jexl3.parser.ASTReference;
+import org.apache.commons.jexl3.parser.ASTReferenceExpression;
+import org.apache.commons.jexl3.parser.ASTReturnStatement;
+import org.apache.commons.jexl3.parser.ASTSWNode;
+import org.apache.commons.jexl3.parser.ASTSetAddNode;
+import org.apache.commons.jexl3.parser.ASTSetAndNode;
+import org.apache.commons.jexl3.parser.ASTSetDivNode;
+import org.apache.commons.jexl3.parser.ASTSetLiteral;
+import org.apache.commons.jexl3.parser.ASTSetModNode;
+import org.apache.commons.jexl3.parser.ASTSetMultNode;
+import org.apache.commons.jexl3.parser.ASTSetOrNode;
+import org.apache.commons.jexl3.parser.ASTSetSubNode;
+import org.apache.commons.jexl3.parser.ASTSetXorNode;
+import org.apache.commons.jexl3.parser.ASTSizeFunction;
+import org.apache.commons.jexl3.parser.ASTSizeMethod;
+import org.apache.commons.jexl3.parser.ASTStringLiteral;
+import org.apache.commons.jexl3.parser.ASTSubNode;
+import org.apache.commons.jexl3.parser.ASTTernaryNode;
+import org.apache.commons.jexl3.parser.ASTTrueNode;
+import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
+import org.apache.commons.jexl3.parser.ASTVar;
+import org.apache.commons.jexl3.parser.ASTWhileStatement;
+import org.apache.commons.jexl3.parser.JexlNode;
+import org.apache.commons.jexl3.parser.ParserVisitor;
+
+/**
+ * Fully abstract to avoid public interface exposition.
+ */
+public class ScriptVisitor extends ParserVisitor {
+ /**
+ * Visits all AST constituents of a JEXL expression.
+ * @param jscript the expression
+ * @param data some data context
+ * @return the visit result or null if jscript was not a Script implementation
+ */
+ public Object visitExpression (JexlExpression jscript, Object data) {
+ if (jscript instanceof Script) {
+ return ((Script) jscript).getScript().jjtAccept(this, data);
+ }
+ return null;
+ }
+
+ /**
+ * Visits all AST constituents of a JEXL script.
+ * @param jscript the expression
+ * @param data some data context
+ * @return the visit result or null if jscript was not a Script implementation
+ */
+ public Object visitScript(JexlScript jscript, Object data) {
+ if (jscript instanceof Script) {
+ return ((Script) jscript).getScript().jjtAccept(this, data);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a node.
+ * Default implementation visits all its children.
+ * @param node the node to visit
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ protected Object visitNode(JexlNode node, Object data) {
+ return node.childrenAccept(this, data);
+ }
+
+ @Override
+ protected Object visit(ASTJexlScript node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTBlock node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTIfStatement node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTWhileStatement node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTContinue node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTBreak node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTForeachStatement node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTReturnStatement node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTAssignment node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTVar node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTReference node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTTernaryNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNullpNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTOrNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTAndNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTBitwiseOrNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTBitwiseXorNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTBitwiseAndNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTEQNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNENode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTLTNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTGTNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTLENode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTGENode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTERNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNRNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSWNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNSWNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTEWNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNEWNode node, Object data) {
+
+ return visitNode(node, data); }
+
+ @Override
+ protected Object visit(ASTAddNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSubNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTMulNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTDivNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTModNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTUnaryMinusNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTBitwiseComplNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNotNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTIdentifier node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNullLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTTrueNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTFalseNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTNumberLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTStringLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTExtendedLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTArrayLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTRangeNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTMapLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTMapEntry node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTEmptyFunction node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTEmptyMethod node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSizeFunction node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTFunctionNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTMethodNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSizeMethod node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTConstructorNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTArrayAccess node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTIdentifierAccess node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTArguments node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTReferenceExpression node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetAddNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetSubNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetMultNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetDivNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetModNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetAndNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetOrNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetXorNode node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTJxltLiteral node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTAnnotation node, Object data) {
+ return visitNode(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTAnnotatedStatement node, Object data) {
+ return visitNode(node, data);
+ }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java b/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
new file mode 100644
index 000000000..5328cab52
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
@@ -0,0 +1,211 @@
+/*
+ * 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 java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * A soft referenced cache.
+ *
+ * The actual cache is held through a soft reference, allowing it to be GCed
+ * under memory pressure.
+ *
+ * @param the cache key entry type
+ * @param the cache key value type
+ */
+public class SoftCache {
+ /**
+ * The default cache load factor.
+ */
+ private static final float LOAD_FACTOR = 0.75f;
+ /**
+ * The cache size.
+ */
+ private final int size;
+ /**
+ * The soft reference to the cache map.
+ */
+ private SoftReference> ref = null;
+ /**
+ * The cache r/w lock.
+ */
+ private final ReadWriteLock lock;
+
+ /**
+ * Creates a new instance of a soft cache.
+ *
+ * @param theSize the cache size
+ */
+ SoftCache(int theSize) {
+ size = theSize;
+ lock = new ReentrantReadWriteLock();
+ }
+
+ /**
+ * Returns the cache size.
+ *
+ * @return the cache size
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public void clear() {
+ lock.writeLock().lock();
+ try {
+ ref = null;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Gets a value from cache.
+ *
+ * @param key the cache entry key
+ * @return the cache entry value
+ */
+ public V get(K key) {
+ lock.readLock().lock();
+ try {
+ final Map map = ref != null ? ref.get() : null;
+ return map != null ? map.get(key) : null;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Puts a value in cache.
+ *
+ * @param key the cache entry key
+ * @param script the cache entry value
+ */
+ public void put(K key, V script) {
+ lock.writeLock().lock();
+ try {
+ Map map = ref != null ? ref.get() : null;
+ if (map == null) {
+ map = createCache(size);
+ ref = new SoftReference>(map);
+ }
+ map.put(key, script);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Produces the cache entry set.
+ *
+ * For testing only, perform deep copy of cache entries
+ *
+ * @return the cache entry list
+ */
+ public List> entries() {
+ lock.readLock().lock();
+ try {
+ Map map = ref != null ? ref.get() : null;
+ if (map == null) {
+ return Collections.emptyList();
+ }
+ final Set> set = map.entrySet();
+ final List> entries = new ArrayList>(set.size());
+ for (Map.Entry e : set) {
+ entries.add(new SoftCacheEntry(e));
+ }
+ return entries;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Creates the cache store.
+ *
+ * @param the key type
+ * @param the value type
+ * @param cacheSize the cache size, must be > 0
+ * @return a Map usable as a cache bounded to the given size
+ */
+ public Map createCache(final int cacheSize) {
+ return new java.util.LinkedHashMap(cacheSize, LOAD_FACTOR, true) {
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return super.size() > cacheSize;
+ }
+ };
+ }
+}
+
+/**
+ * A soft cache entry.
+ *
+ * @param key type
+ * @param value type
+ */
+class SoftCacheEntry implements Map.Entry {
+ /**
+ * The entry key.
+ */
+ private final K key;
+ /**
+ * The entry value.
+ */
+ private final V value;
+
+ /**
+ * Creates an entry clone.
+ *
+ * @param e the entry to clone
+ */
+ SoftCacheEntry(Map.Entry e) {
+ key = e.getKey();
+ value = e.getValue();
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public V setValue(V v) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+}
+
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Source.java b/src/main/java/org/apache/commons/jexl3/internal/Source.java
new file mode 100644
index 000000000..f79b06c49
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/Source.java
@@ -0,0 +1,94 @@
+/*
+ * 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.JexlFeatures;
+
+/**
+ * Maintains the set of allowed features associated with a script/expression source.
+ * This is meant for caching scripts using their 'source' as key but still distinguishing
+ * scripts with different features and prevent false sharing.
+ */
+public final class Source {
+ /** The hash code, pre-computed for fast op. */
+ private final int hashCode;
+ /** The set of features. */
+ private final JexlFeatures features;
+ /** The actual source script/expression. */
+ private final String str;
+
+ /**
+ * Default constructor.
+ * @param theFeatures the features
+ * @param theStr the script source
+ */
+ Source(JexlFeatures theFeatures, String theStr) { // CSOFF: MagicNumber
+ this.features = theFeatures;
+ this.str = theStr;
+ int hash = 3;
+ hash = 37 * hash + features.hashCode();
+ hash = 37 * hash + str.hashCode() ;
+ this.hashCode = hash;
+ }
+
+ /**
+ * @return the length of the script source
+ */
+ int length() {
+ return str.length();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Source other = (Source) obj;
+ if (this.features != other.features
+ && (this.features == null || !this.features.equals(other.features))) {
+ return false;
+ }
+ if ((this.str == null) ? (other.str != null) : !this.str.equals(other.str)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return str;
+ }
+
+ /**
+ * @return the features associated with the source
+ */
+ public JexlFeatures getFeatures() {
+ return features;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
index b117bed47..da0516192 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -41,7 +41,7 @@
*/
public final class TemplateEngine extends JxltEngine {
/** The TemplateExpression cache. */
- private final Engine.SoftCache cache;
+ private final SoftCache cache;
/** The JEXL engine instance. */
private final Engine jexl;
/** The first character for immediate expressions. */
@@ -61,7 +61,7 @@ public final class TemplateEngine extends JxltEngine {
*/
public TemplateEngine(Engine aJexl, boolean noScript, int cacheSize, char immediate, char deferred) {
this.jexl = aJexl;
- this.cache = aJexl.new SoftCache(cacheSize);
+ this.cache = new SoftCache(cacheSize);
immediateChar = immediate;
deferredChar = deferred;
noscript = noScript;
@@ -86,7 +86,7 @@ char getDeferredChar() {
* Each instance carries a counter index per (composite sub-) template expression type.
* @see ExpressionBuilder
*/
- static enum ExpressionType {
+ enum ExpressionType {
/** Constant TemplateExpression, count index 0. */
CONSTANT(0),
/** Immediate TemplateExpression, count index 1. */
@@ -540,7 +540,7 @@ ExpressionType getType() {
@Override
protected TemplateExpression prepare(Interpreter interpreter) {
String value = interpreter.interpret(node).toString();
- JexlNode dnode = jexl.parse(jexl.isDebug() ? node.jexlInfo() : null, value, null, false, noscript);
+ JexlNode dnode = jexl.parse(node.jexlInfo(), noscript, value, null);
return new ImmediateExpression(value, dnode, this);
}
@@ -659,16 +659,10 @@ public JxltEngine.Expression createExpression(JexlInfo info, String expression)
Exception xuel = null;
TemplateExpression stmt = null;
try {
- if (cache == null) {
+ stmt = cache.get(expression);
+ if (stmt == null) {
stmt = parseExpression(info, expression, null);
- } else {
- synchronized (cache) {
- stmt = cache.get(expression);
- if (stmt == null) {
- stmt = parseExpression(info, expression, null);
- cache.put(expression, stmt);
- }
- }
+ cache.put(expression, stmt);
}
} catch (JexlException xjexl) {
xuel = new Exception(xjexl.getInfo(), "failed to parse '" + expression + "'", xjexl);
@@ -712,7 +706,7 @@ static Exception createException(JexlInfo info, String action, TemplateExpressio
}
/** The different parsing states. */
- private static enum ParseState {
+ private enum ParseState {
/** Parsing a constant. */
CONST,
/** Parsing after $ . */
@@ -806,7 +800,7 @@ TemplateExpression parseExpression(JexlInfo info, String expr, Scope scope) { /
String src = strb.toString();
TemplateExpression iexpr = new ImmediateExpression(
src,
- jexl.parse(info.at(lineno, column), src, scope, false, noscript),
+ jexl.parse(info.at(lineno, column), noscript, src, scope),
null);
builder.add(iexpr);
strb.delete(0, Integer.MAX_VALUE);
@@ -854,12 +848,12 @@ TemplateExpression parseExpression(JexlInfo info, String expr, Scope scope) { /
if (nested) {
dexpr = new NestedExpression(
expr.substring(inested, column + 1),
- jexl.parse(info.at(lineno, column), src, scope, false, noscript),
+ jexl.parse(info.at(lineno, column), noscript, src, scope),
null);
} else {
dexpr = new DeferredExpression(
strb.toString(),
- jexl.parse(info.at(lineno, column), src, scope, false, noscript),
+ jexl.parse(info.at(lineno, column), noscript, src, scope),
null);
}
builder.add(dexpr);
@@ -902,11 +896,11 @@ TemplateExpression parseExpression(JexlInfo info, String expr, Scope scope) { /
/**
* The enum capturing the difference between verbatim and code source fragments.
*/
- static enum BlockType {
+ enum BlockType {
/** Block is to be output "as is" but may be a unified expression. */
VERBATIM,
/** Block is a directive, ie a fragment of JEXL code. */
- DIRECTIVE;
+ DIRECTIVE
}
/**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
index 96e3c7ed8..321d2b8c1 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -27,6 +27,7 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -95,7 +96,7 @@ public TemplateScript(TemplateEngine engine, JexlInfo info, String directive, Re
info = jxlt.getEngine().createInfo();
}
// allow lambda defining params
- script = jxlt.getEngine().parse(info.at(0, 0), strb.toString(), scope, false, false).script();
+ script = jxlt.getEngine().parse(info.at(0, 0), false, strb.toString(), scope).script();
scope = script.getScope();
// create the exprs using the code frame for those appearing after the first block of code
for (int b = 0; b < blocks.size(); ++b) {
@@ -160,8 +161,7 @@ public String toString() {
public String asString() {
StringBuilder strb = new StringBuilder();
int e = 0;
- for (int b = 0; b < source.length; ++b) {
- Block block = source[b];
+ for (Block block : source) {
if (block.getType() == BlockType.DIRECTIVE) {
strb.append(prefix);
} else {
@@ -207,4 +207,8 @@ public String[] getParameters() {
return script.getParameters();
}
+ @Override
+ public Map getPragmas() {
+ return script.getPragmas();
+ }
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java
index 59895efee..fbe5c2b5f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/AbstractExecutor.java
@@ -89,13 +89,11 @@ protected AbstractExecutor(Class> theClass, java.lang.reflect.Method theMethod
method = theMethod;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
return this == obj || (obj instanceof AbstractExecutor && equals((AbstractExecutor) obj));
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return method.hashCode();
@@ -236,7 +234,6 @@ protected Method(Class> c, java.lang.reflect.Method m, MethodKey k) {
key = k;
}
- /** {@inheritDoc} */
@Override
public Object getTargetProperty() {
return key;
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java
index d762b4205..6bc516793 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ArrayListWrapper.java
@@ -43,13 +43,11 @@ public ArrayListWrapper(Object anArray) {
this.array = anArray;
}
- /** {@inheritDoc} */
@Override
public Object get(int index) {
return Array.get(array, index);
}
- /** {@inheritDoc} */
@Override
public Object set(int index, Object element) {
Object old = Array.get(array, index);
@@ -57,7 +55,6 @@ public Object set(int index, Object element) {
return old;
}
- /** {@inheritDoc} */
@Override
public int size() {
return Array.getLength(array);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java
index 370097b2b..4369e69ad 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/BooleanGetExecutor.java
@@ -35,12 +35,13 @@ public final class BooleanGetExecutor extends AbstractExecutor.Get {
* @return the executor if found, null otherwise
*/
public static BooleanGetExecutor discover(Introspector is, final Class> clazz, String property) {
- java.lang.reflect.Method m = PropertyGetExecutor.discoverGet(is, "is", clazz, property);
- if (m != null && (m.getReturnType() == Boolean.TYPE || m.getReturnType() == Boolean.class)) {
- return new BooleanGetExecutor(clazz, m, property);
- } else {
- return null;
+ if (property != null && !property.isEmpty()) {
+ java.lang.reflect.Method m = PropertyGetExecutor.discoverGet(is, "is", clazz, property);
+ if (m != null && (m.getReturnType() == Boolean.TYPE || m.getReturnType() == Boolean.class)) {
+ return new BooleanGetExecutor(clazz, m, property);
+ }
}
+ return null;
}
/**
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
index 5a3de9a88..dd91c891f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
@@ -63,12 +63,12 @@ public static Method cacheMiss() {
* This is the cache to store and look up the method information.
*
* It stores the association between:
- * - a key made of a method name and an array of argument types.
- * - a method.
+ * - a key made of a method name and an array of argument types.
+ * - a method.
*
*
* Since the invocation of the associated method is dynamic, there is no need (nor way) to differentiate between
- * foo(int,int) and foo(Integer,Integer) since in practise, only the latter form will be used through a call.
+ * foo(int,int) and foo(Integer,Integer) since in practice only the latter form will be used through a call.
* This of course, applies to all 8 primitive types.
*
* Uses ConcurrentMap since 3.0, marginally faster than 2.1 under contention.
@@ -87,18 +87,19 @@ public static Method cacheMiss() {
* Standard constructor.
*
* @param aClass the class to deconstruct.
- * @param log the logger.
+ * @param permissions the permissions to apply during introspection
+ * @param log the logger.
*/
@SuppressWarnings("LeakingThisInConstructor")
- ClassMap(Class> aClass, Log log) {
+ ClassMap(Class> aClass, Permissions permissions, Log log) {
// eagerly cache methods
- create(this, aClass, log);
+ create(this, permissions, aClass, log);
// eagerly cache public fields
Field[] fields = aClass.getFields();
if (fields.length > 0) {
Map cache = new HashMap();
for (Field field : fields) {
- if (Modifier.isPublic(field.getModifiers()) && Permissions.allow(field)) {
+ if (permissions.allow(field)) {
cache.put(field.getName(), field);
}
}
@@ -149,16 +150,16 @@ Method[] getMethods(final String methodName) {
/**
* Find a Method using the method name and parameter objects.
- *
- * Look in the methodMap for an entry. If found,
+ *
+ * Look in the methodMap for an entry. If found,
* it'll either be a CACHE_MISS, in which case we
* simply give up, or it'll be a Method, in which
* case, we return it.
- *
+ *
*
* If nothing is found, then we must actually go
* and introspect the method from the MethodMap.
- *
+ *
* @param methodKey the method key
* @return A Method object representing the method to invoke or null.
* @throws MethodKey.AmbiguousException When more than one method is a match for the parameters.
@@ -195,11 +196,12 @@ Method getMethod(final MethodKey methodKey) throws MethodKey.AmbiguousException
/**
* Populate the Map of direct hits. These are taken from all the public methods
* that our class, its parents and their implemented interfaces provide.
- * @param cache the ClassMap instance we create
+ * @param cache the ClassMap instance we create
+ * @param permissions the permissions to apply during introspection
* @param classToReflect the class to cache
- * @param log the Log
+ * @param log the Log
*/
- private static void create(ClassMap cache, Class> classToReflect, Log log) {
+ private static void create(ClassMap cache, Permissions permissions, Class> classToReflect, Log log) {
//
// Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
// with the actual declaring class and its interfaces and then move up (superclass etc.) until we
@@ -210,11 +212,11 @@ private static void create(ClassMap cache, Class> classToReflect, Log log) {
//
for (; classToReflect != null; classToReflect = classToReflect.getSuperclass()) {
if (Modifier.isPublic(classToReflect.getModifiers())) {
- populateWithClass(cache, classToReflect, log);
+ populateWithClass(cache, permissions, classToReflect, log);
}
Class>[] interfaces = classToReflect.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
- populateWithInterface(cache, interfaces[i], log);
+ populateWithInterface(cache, permissions, interfaces[i], log);
}
}
// now that we've got all methods keyed in, lets organize them by name
@@ -253,33 +255,40 @@ public int compare(Method o1, Method o2) {
/**
* Recurses up interface hierarchy to get all super interfaces.
* @param cache the cache to fill
+ * @param permissions the permissions to apply during introspection
* @param iface the interface to populate the cache from
- * @param log the Log
+ * @param log the Log
*/
- private static void populateWithInterface(ClassMap cache, Class> iface, Log log) {
+ private static void populateWithInterface(ClassMap cache, Permissions permissions, Class> iface, Log log) {
if (Modifier.isPublic(iface.getModifiers())) {
- populateWithClass(cache, iface, log);
- }
- Class>[] supers = iface.getInterfaces();
- for (int i = 0; i < supers.length; i++) {
- populateWithInterface(cache, supers[i], log);
+ populateWithClass(cache, permissions, iface, log);
+ Class>[] supers = iface.getInterfaces();
+ for (int i = 0; i < supers.length; i++) {
+ populateWithInterface(cache, permissions, supers[i], log);
+ }
}
}
/**
* Recurses up class hierarchy to get all super classes.
* @param cache the cache to fill
+ * @param permissions the permissions to apply during introspection
* @param clazz the class to populate the cache from
- * @param log the Log
+ * @param log the Log
*/
- private static void populateWithClass(ClassMap cache, Class> clazz, Log log) {
+ private static void populateWithClass(ClassMap cache, Permissions permissions, Class> clazz, Log log) {
try {
Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method mi = methods[i];
- if (Modifier.isPublic(mi.getModifiers()) && Permissions.allow(mi)) {
+ if (permissions.allow(mi)) {
// add method to byKey cache; do not override
- cache.byKey.putIfAbsent(new MethodKey(mi), mi);
+ MethodKey key = new MethodKey(mi);
+ Method pmi = cache.byKey.putIfAbsent(key, mi);
+ if (pmi != null && log.isDebugEnabled() && !key.equals(new MethodKey(pmi))) {
+ // foo(int) and foo(Integer) have the same signature for JEXL
+ log.debug("Method "+ pmi + " is already registered, key: " + key.debugString());
+ }
}
}
} catch (SecurityException se) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java
index 19fb5d990..04b6c06ce 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckGetExecutor.java
@@ -30,7 +30,7 @@
* @since 2.0
*/
public final class DuckGetExecutor extends AbstractExecutor.Get {
- /** The property. */
+ /** The property, may be null. */
private final Object property;
/**
@@ -69,10 +69,12 @@ public Object invoke(Object obj) throws IllegalAccessException, InvocationTarget
@Override
public Object tryInvoke(Object obj, Object key) {
- if (obj != null && method != null
- // ensure method name matches the property name
- && property.equals(key)
- && objectClass.equals(obj.getClass())) {
+ if (obj != null
+ && objectClass.equals(obj.getClass())
+ // ensure method name matches the property name
+ && method != null
+ && ((property == null && key == null)
+ || (property != null && property.equals(key)))) {
try {
Object[] args = {property};
return method.invoke(obj, args);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java
index af898f73f..c1da6f5e4 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/DuckSetExecutor.java
@@ -36,7 +36,7 @@
* @since 2.0
*/
public final class DuckSetExecutor extends AbstractExecutor.Set {
- /** The property. */
+ /** The property, may be null. */
private final Object property;
/**
@@ -83,10 +83,11 @@ public Object invoke(Object obj, Object value) throws IllegalAccessException, In
@Override
public Object tryInvoke(Object obj, Object key, Object value) {
- if (obj != null && method != null
- // ensure method name matches the property name
- && property.equals(key)
- && objectClass.equals(obj.getClass())) {
+ if (obj != null
+ && objectClass.equals(obj.getClass())
+ && method != null
+ && ((property != null && property.equals(key))
+ || (property == null && key == null))) {
try {
Object[] args = {property, value};
method.invoke(obj, args);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java
index a5a0fe5df..5efda2838 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java
@@ -24,18 +24,23 @@
/**
* Abstract an indexed property container.
- * This stores the container name and owning class as well as the list of available getter and setter methods.
+ * This allows getting properties from expressions like var.container.property
.
+ * This stores the container name and class as well as the list of available getter and setter methods.
* It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set).
*/
public final class IndexedType implements JexlPropertyGet {
- /** The container name. */
+ /** The container name. */
private final String container;
- /** The owning class. */
+ /** The container class. */
private final Class> clazz;
/** The array of getter methods. */
private final Method[] getters;
+ /** Last get method used. */
+ private volatile Method get = null;
/** The array of setter methods. */
private final Method[] setters;
+ /** Last set method used. */
+ private volatile Method set = null;
/**
* Attempts to find an indexed-property getter in an object.
@@ -46,10 +51,10 @@ public final class IndexedType implements JexlPropertyGet {
* @param is the introspector
* @param object the object
* @param name the container name
- * @return a JexlPropertyGet is successfull, null otherwise
+ * @return a JexlPropertyGet is successful, null otherwise
*/
public static JexlPropertyGet discover(Introspector is, Object object, String name) {
- if (object != null && name != null) {
+ if (object != null && name != null && !name.isEmpty()) {
String base = name.substring(0, 1).toUpperCase() + name.substring(1);
final String container = name;
final Class> clazz = object.getClass();
@@ -63,50 +68,66 @@ public static JexlPropertyGet discover(Introspector is, Object object, String na
}
/**
- * A generic indexed property container, exposes get(key) and set(key, value) and solves method call dynamically
- * based on arguments.
+ * A generic indexed property container, exposes get(key) and set(key, value)
+ * and solves method call dynamically based on arguments.
*
Must remain public for introspection purpose.
*/
public static final class IndexedContainer {
- /** The instance owning the container. */
- private final Object object;
+ /** The container instance. */
+ private final Object container;
/** The container type instance. */
private final IndexedType type;
/**
* Creates a new duck container.
* @param theType the container type
- * @param theObject the instance owning the container
+ * @param theContainer the container instance
*/
- private IndexedContainer(IndexedType theType, Object theObject) {
+ private IndexedContainer(IndexedType theType, Object theContainer) {
this.type = theType;
- this.object = theObject;
+ this.container = theContainer;
+ }
+
+ /**
+ * Gets the property container name.
+ * @return the container name
+ */
+ public String getContainerName() {
+ return type.container;
}
/**
- * Gets a property from a container.
+ * Gets the property container class.
+ * @return the container class
+ */
+ public Class> getContainerClass() {
+ return type.clazz;
+ }
+
+ /**
+ * Gets a property from this indexed container.
* @param key the property key
* @return the property value
* @throws Exception if inner invocation fails
*/
public Object get(Object key) throws Exception {
- return type.invokeGet(object, key);
+ return type.invokeGet(container, key);
}
/**
- * Sets a property in a container.
+ * Sets a property in this indexed container.
* @param key the property key
* @param value the property value
* @return the invocation result (frequently null)
* @throws Exception if inner invocation fails
*/
public Object set(Object key, Object value) throws Exception {
- return type.invokeSet(object, key, value);
+ return type.invokeSet(container, key, value);
}
}
/**
- * Creates a new indexed type.
+ * Creates a new indexed property container type.
* @param name the container name
* @param c the owning class
* @param gets the array of getter methods
@@ -130,7 +151,9 @@ public Object invoke(Object obj) throws Exception {
@Override
public Object tryInvoke(Object obj, Object key) {
- if (obj != null && key != null && clazz.equals(obj.getClass()) && container.equals(key.toString())) {
+ if (obj != null && key != null
+ && clazz.equals(obj.getClass())
+ && container.equals(key.toString())) {
return new IndexedContainer(this, obj);
} else {
return Uberspect.TRY_FAILED;
@@ -149,49 +172,69 @@ public boolean isCacheable() {
/**
* Gets the value of a property from a container.
- * @param object the instance owning the container (not null)
+ * @param object the container instance (not null)
* @param key the property key (not null)
* @return the property value
- * @throws Exception if invocation failed; IntrospectionException if a property getter could not be found
+ * @throws Exception if invocation failed;
+ * IntrospectionException if a property getter could not be found
*/
private Object invokeGet(Object object, Object key) throws Exception {
- if (getters != null) {
- final Object[] args = {key};
- final Method jm;
- if (getters.length == 1) {
- jm = getters[0];
- } else {
- jm = new MethodKey(getters[0].getName(), args).getMostSpecificMethod(getters);
+ if (getters != null && getters.length > 0) {
+ Method jm = get;
+ if (jm != null) {
+ final Class>[] ptypes = jm.getParameterTypes();
+ if (ptypes[0].isAssignableFrom(key.getClass())) {
+ return jm.invoke(object, key);
+ }
}
+ final Object[] args = {key};
+ final String mname = getters[0].getName();
+ final MethodKey km = new MethodKey(mname, args);
+ jm = km.getMostSpecificMethod(getters);
if (jm != null) {
- return jm.invoke(object, args);
+ Object invoked = jm.invoke(object, args);
+ get = jm;
+ return invoked;
}
}
- throw new IntrospectionException("property get error: " + object.getClass().toString() + "@" + key.toString());
+ throw new IntrospectionException("property get error: "
+ + object.getClass().toString()
+ + "@" + key.toString());
}
/**
* Sets the value of a property in a container.
- * @param object the instance owning the container (not null)
+ * @param object the container instance (not null)
* @param key the property key (not null)
* @param value the property value (not null)
* @return the result of the method invocation (frequently null)
- * @throws Exception if invocation failed; IntrospectionException if a property setter could not be found
+ * @throws Exception if invocation failed;
+ * IntrospectionException if a property setter could not be found
*/
private Object invokeSet(Object object, Object key, Object value) throws Exception {
- if (setters != null) {
- final Object[] args = {key, value};
- final Method jm;
- if (setters.length == 1) {
- jm = setters[0];
- } else {
- jm = new MethodKey(setters[0].getName(), args).getMostSpecificMethod(setters);
+ if (setters != null && setters.length > 0) {
+ Method jm = set;
+ if (jm != null) {
+ final Class>[] ptypes = jm.getParameterTypes();
+ if (ptypes[0].isAssignableFrom(key.getClass())
+ && (value == null
+ || ptypes[1].isAssignableFrom(value.getClass()))) {
+ return jm.invoke(object, key, value);
+ }
}
+ final Object[] args = {key, value};
+ final String mname = setters[0].getName();
+ final MethodKey km = new MethodKey(mname, args);
+ jm = km.getMostSpecificMethod(setters);
if (jm != null) {
- return jm.invoke(object, args);
+ Object invoked = jm.invoke(object, args);
+ set = jm;
+ return invoked;
}
}
- throw new IntrospectionException("property set error: " + object.getClass().toString() + "@" + key.toString());
+ throw new IntrospectionException("property set error: "
+ + object.getClass().toString()
+ + "@" + key.toString());
}
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index d9a1e8356..e89ef95ed 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -21,7 +21,6 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
@@ -67,6 +66,10 @@ public CacheMiss() {
* The class loader used to solve constructors if needed.
*/
private ClassLoader loader;
+ /**
+ * The permissions.
+ */
+ private final Permissions permissions;
/**
* The read/write lock.
*/
@@ -90,8 +93,19 @@ public CacheMiss() {
* @param cloader the class loader
*/
public Introspector(Log log, ClassLoader cloader) {
+ this(log, cloader, null);
+ }
+
+ /**
+ * Create the introspector.
+ * @param log the logger to use
+ * @param cloader the class loader
+ * @param perms the permissions
+ */
+ public Introspector(Log log, ClassLoader cloader, Permissions perms) {
this.rlog = log;
- loader = cloader;
+ this.loader = cloader;
+ this.permissions = perms != null? perms : Permissions.DEFAULT;
}
/**
@@ -131,8 +145,8 @@ public Method getMethod(Class> c, MethodKey key) {
try {
return getMap(c).getMethod(key);
} catch (MethodKey.AmbiguousException xambiguous) {
- // whoops. Ambiguous. Make a nice log message and return null...
- if (rlog != null && rlog.isInfoEnabled()) {
+ // whoops. Ambiguous and not benign. Make a nice log message and return null...
+ if (rlog != null && xambiguous.isSevere() && rlog.isInfoEnabled()) {
rlog.info("ambiguous method invocation: "
+ c.getName() + "."
+ key.debugString(), xambiguous);
@@ -247,7 +261,7 @@ public Constructor> getConstructor(final Class> c, final MethodKey key) {
}
List> l = new ArrayList>();
for (Constructor> ictor : clazz.getConstructors()) {
- if (Modifier.isPublic(ictor.getModifiers()) && Permissions.allow(ictor)) {
+ if (permissions.allow(ictor)) {
l.add(ictor);
}
}
@@ -298,7 +312,7 @@ private ClassMap getMap(Class> c) {
// try again
classMap = classMethodMaps.get(c);
if (classMap == null) {
- classMap = new ClassMap(c, rlog);
+ classMap = new ClassMap(c, permissions, rlog);
classMethodMaps.put(c, classMap);
}
} finally {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java
index fd2c558ca..956b65c6e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ListGetExecutor.java
@@ -72,7 +72,7 @@ public Object getTargetProperty() {
@Override
public Object invoke(final Object obj) {
if (method == ARRAY_GET) {
- return java.lang.reflect.Array.get(obj, property.intValue());
+ return Array.get(obj, property.intValue());
} else {
return ((List>) obj).get(property.intValue());
}
@@ -85,7 +85,7 @@ public Object tryInvoke(final Object obj, Object identifier) {
&& objectClass.equals(obj.getClass())
&& index != null) {
if (method == ARRAY_GET) {
- return java.lang.reflect.Array.get(obj, index.intValue());
+ return Array.get(obj, index.intValue());
} else {
return ((List>) obj).get(index.intValue());
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java
index 18b1b837d..e41e88777 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ListSetExecutor.java
@@ -81,7 +81,7 @@ public Object getTargetProperty() {
@Override
public Object invoke(final Object obj, Object value) {
if (method == ARRAY_SET) {
- java.lang.reflect.Array.set(obj, property.intValue(), value);
+ Array.set(obj, property.intValue(), value);
} else {
@SuppressWarnings("unchecked") // LSE should only be created for array or list types
final List list = (List) obj;
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java
index 4cd955531..1799fe5a4 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/MapGetExecutor.java
@@ -62,7 +62,6 @@ public Object getTargetProperty() {
return property;
}
-
@Override
public Object invoke(final Object obj) {
@SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
@@ -72,9 +71,11 @@ public Object invoke(final Object obj) {
@Override
public Object tryInvoke(final Object obj, Object key) {
- if (obj != null && method != null
- && objectClass.equals(obj.getClass())
- && (key == null || property.getClass().equals(key.getClass()))) {
+ if (obj != null
+ && method != null
+ && objectClass.equals(obj.getClass())
+ && ((property == null && key == null)
+ || (property != null && key != null && property.getClass().equals(key.getClass())))) {
@SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
final Map map = (Map) obj;
return map.get(key);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java
index d1d25648d..ce439dc0c 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/MapSetExecutor.java
@@ -72,9 +72,11 @@ public Object invoke(final Object obj, Object value) throws IllegalAccessExcepti
@Override
public Object tryInvoke(final Object obj, Object key, Object value) {
- if (obj != null && method != null
+ if (obj != null
+ && method != null
&& objectClass.equals(obj.getClass())
- && (key == null || property.getClass().equals(key.getClass()))) {
+ && ((property == null && key == null)
+ || (property != null && key != null && property.getClass().equals(key.getClass())))) {
@SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
final Map map = ((Map) obj);
map.put(key, value);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
index a08b40290..b0c3cae77 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
@@ -23,6 +23,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
/**
@@ -46,38 +47,7 @@
*/
public final class MethodKey {
/** The initial size of the primitive conversion map. */
- private static final int PRIMITIVE_SIZE = 13;
- /** The primitive type to class conversion map. */
- private static final Map, Class>> PRIMITIVE_TYPES;
-
- static {
- PRIMITIVE_TYPES = new HashMap, Class>>(PRIMITIVE_SIZE);
- PRIMITIVE_TYPES.put(Boolean.TYPE, Boolean.class);
- PRIMITIVE_TYPES.put(Byte.TYPE, Byte.class);
- PRIMITIVE_TYPES.put(Character.TYPE, Character.class);
- PRIMITIVE_TYPES.put(Double.TYPE, Double.class);
- PRIMITIVE_TYPES.put(Float.TYPE, Float.class);
- PRIMITIVE_TYPES.put(Integer.TYPE, Integer.class);
- PRIMITIVE_TYPES.put(Long.TYPE, Long.class);
- PRIMITIVE_TYPES.put(Short.TYPE, Short.class);
- }
-
- /** Converts a primitive type to its corresponding class.
- *
- * If the argument type is primitive then we want to convert our
- * primitive type signature to the corresponding Object type so
- * introspection for methods with primitive types will work
- * correctly.
- *
- * @param parm a may-be primitive type class
- * @return the equivalent object class
- */
- static Class> primitiveClass(Class> parm) {
- // it is marginally faster to get from the map than call isPrimitive...
- //if (!parm.isPrimitive()) return parm;
- Class> prim = PRIMITIVE_TYPES.get(parm);
- return prim == null ? parm : prim;
- }
+ private static final int PRIMITIVE_SIZE = 11;
/** The hash code. */
private final int hashCode;
/** The method name. */
@@ -173,13 +143,11 @@ Class>[] getParameters() {
return params;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return hashCode;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (obj instanceof MethodKey) {
@@ -189,7 +157,6 @@ public boolean equals(Object obj) {
return false;
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder builder = new StringBuilder(method);
@@ -261,7 +228,7 @@ public static boolean isVarArgs(final Method method) {
* @throws MethodKey.AmbiguousException if there is more than one.
*/
public Method getMostSpecificMethod(Method[] methods) {
- return METHODS.getMostSpecific(methods, params);
+ return METHODS.getMostSpecific(this, methods);
}
/**
@@ -271,7 +238,7 @@ public Method getMostSpecificMethod(Method[] methods) {
* @throws MethodKey.AmbiguousException if there is more than one.
*/
public Constructor> getMostSpecificConstructor(Constructor>[] methods) {
- return CONSTRUCTORS.getMostSpecific(methods, params);
+ return CONSTRUCTORS.getMostSpecific(this, methods);
}
/**
@@ -295,69 +262,7 @@ public Constructor> getMostSpecificConstructor(Constructor>[] methods) {
* the formal type.
*/
public static boolean isInvocationConvertible(Class> formal, Class> actual, boolean possibleVarArg) {
- /* if it's a null, it means the arg was null */
- if (actual == null && !formal.isPrimitive()) {
- return true;
- }
-
- /* Check for identity or widening reference conversion */
- if (actual != null && formal.isAssignableFrom(actual)) {
- return true;
- }
-
- /** Catch all... */
- if (formal == Object.class) {
- return true;
- }
-
- /* Check for boxing with widening primitive conversion. Note that
- * actual parameters are never primitives. */
- if (formal.isPrimitive()) {
- if (formal == Boolean.TYPE && actual == Boolean.class) {
- return true;
- }
- if (formal == Character.TYPE && actual == Character.class) {
- return true;
- }
- if (formal == Byte.TYPE && actual == Byte.class) {
- return true;
- }
- if (formal == Short.TYPE
- && (actual == Short.class || actual == Byte.class)) {
- return true;
- }
- if (formal == Integer.TYPE
- && (actual == Integer.class || actual == Short.class
- || actual == Byte.class)) {
- return true;
- }
- if (formal == Long.TYPE
- && (actual == Long.class || actual == Integer.class
- || actual == Short.class || actual == Byte.class)) {
- return true;
- }
- if (formal == Float.TYPE
- && (actual == Float.class || actual == Long.class
- || actual == Integer.class || actual == Short.class
- || actual == Byte.class)) {
- return true;
- }
- if (formal == Double.TYPE
- && (actual == Double.class || actual == Float.class
- || actual == Long.class || actual == Integer.class
- || actual == Short.class || actual == Byte.class)) {
- return true;
- }
- }
-
- /* Check for vararg conversion. */
- if (possibleVarArg && formal.isArray()) {
- if (actual != null && actual.isArray()) {
- actual = actual.getComponentType();
- }
- return isInvocationConvertible(formal.getComponentType(), actual, false);
- }
- return false;
+ return isInvocationConvertible(formal, actual, false, possibleVarArg);
}
/**
@@ -377,52 +282,124 @@ public static boolean isInvocationConvertible(Class> formal, Class> actual,
* subject to widening conversion to formal.
*/
public static boolean isStrictInvocationConvertible(Class> formal, Class> actual, boolean possibleVarArg) {
- /* we shouldn't get a null into, but if so */
+ return isInvocationConvertible(formal, actual, true, possibleVarArg);
+ }
+
+ /** Converts a primitive type to its corresponding class.
+ *
+ * If the argument type is primitive then we want to convert our
+ * primitive type signature to the corresponding Object type so
+ * introspection for methods with primitive types will work
+ * correctly.
+ *
+ * @param parm a may-be primitive type class
+ * @return the equivalent object class
+ */
+ static Class> primitiveClass(Class> parm) {
+ // it was marginally faster to get from the map than call isPrimitive...
+ //if (!parm.isPrimitive()) return parm;
+ Class>[] prim = CONVERTIBLES.get(parm);
+ return prim == null ? parm : prim[0];
+ }
+
+ /**
+ * Helper to build class arrays.
+ * @param args the classes
+ * @return the array
+ */
+ private static Class>[] asArray(Class>... args) {
+ return args;
+ }
+
+ /**
+ * Maps from primitive types to invocation compatible classes.
+ * Considering the key as a parameter type, the value is the list of argument classes that are invocation
+ * compatible with the parameter. Example is Long is invocation convertible to long.
+ */
+ private static final Map, Class>[]> CONVERTIBLES;
+ static {
+ CONVERTIBLES = new HashMap, Class>[]>(PRIMITIVE_SIZE);
+ CONVERTIBLES.put(Boolean.TYPE,
+ asArray(Boolean.class));
+ CONVERTIBLES.put(Character.TYPE,
+ asArray(Character.class));
+ CONVERTIBLES.put(Byte.TYPE,
+ asArray(Byte.class));
+ CONVERTIBLES.put(Short.TYPE,
+ asArray(Short.class, Byte.class));
+ CONVERTIBLES.put(Integer.TYPE,
+ asArray(Integer.class, Short.class, Byte.class));
+ CONVERTIBLES.put(Long.TYPE,
+ asArray(Long.class, Integer.class, Short.class, Byte.class));
+ CONVERTIBLES.put(Float.TYPE,
+ asArray(Float.class, Long.class, Integer.class, Short.class, Byte.class));
+ CONVERTIBLES.put(Double.TYPE,
+ asArray(Double.class, Float.class, Long.class, Integer.class, Short.class, Byte.class));
+ }
+
+ /**
+ * Maps from primitive types to invocation compatible primitive types.
+ * Considering the key as a parameter type, the value is the list of argument types that are invocation
+ * compatible with the parameter. Example is 'int' is invocation convertible to 'long'.
+ */
+ private static final Map, Class>[]> STRICT_CONVERTIBLES;
+ static {
+ STRICT_CONVERTIBLES = new HashMap, Class>[]>(PRIMITIVE_SIZE);
+ STRICT_CONVERTIBLES.put(Short.TYPE,
+ asArray(Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Integer.TYPE,
+ asArray(Short.TYPE, Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Long.TYPE,
+ asArray(Integer.TYPE, Short.TYPE, Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Float.TYPE,
+ asArray(Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE));
+ STRICT_CONVERTIBLES.put(Double.TYPE,
+ asArray(Float.TYPE, Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE));
+ }
+
+ /**
+ * Determines parameter-argument invocation compatibility.
+ *
+ * @param formal the formal parameter type
+ * @param actual the argument type
+ * @param strict whether the check is strict or not
+ * @param possibleVarArg whether or not we're dealing with the last parameter in the method declaration
+ * @return true if compatible, false otherwise
+ */
+ private static boolean isInvocationConvertible(
+ Class> formal, Class> actual, boolean strict, boolean possibleVarArg) {
+ /* if it's a null, it means the arg was null */
if (actual == null && !formal.isPrimitive()) {
return true;
}
-
- /* Check for identity or widening reference conversion */
- if (formal.isAssignableFrom(actual) && (actual != null && formal.isArray() == actual.isArray())) {
+ /* system asssignable, both sides must be array or not */
+ if (actual != null && formal.isAssignableFrom(actual) && actual.isArray() == formal.isArray()) {
return true;
}
-
- /* Check for widening primitive conversion. */
+ /** catch all... */
+ if (!strict && formal == Object.class) {
+ return true;
+ }
+ /* Primitive conversion check. */
if (formal.isPrimitive()) {
- if (formal == Short.TYPE && (actual == Byte.TYPE)) {
- return true;
- }
- if (formal == Integer.TYPE
- && (actual == Short.TYPE || actual == Byte.TYPE)) {
- return true;
- }
- if (formal == Long.TYPE
- && (actual == Integer.TYPE || actual == Short.TYPE
- || actual == Byte.TYPE)) {
- return true;
- }
- if (formal == Float.TYPE
- && (actual == Long.TYPE || actual == Integer.TYPE
- || actual == Short.TYPE || actual == Byte.TYPE)) {
- return true;
- }
- if (formal == Double.TYPE
- && (actual == Float.TYPE || actual == Long.TYPE
- || actual == Integer.TYPE || actual == Short.TYPE
- || actual == Byte.TYPE)) {
- return true;
+ Class>[] clist = strict ? STRICT_CONVERTIBLES.get(formal) : CONVERTIBLES.get(formal);
+ for(int c = 0; c < clist.length; ++c) {
+ if (actual == clist[c]) {
+ return true;
+ }
}
+ return false;
}
-
/* Check for vararg conversion. */
if (possibleVarArg && formal.isArray()) {
if (actual != null && actual.isArray()) {
actual = actual.getComponentType();
}
- return isStrictInvocationConvertible(formal.getComponentType(), actual, false);
+ return isInvocationConvertible(formal.getComponentType(), actual, strict, false);
}
return false;
}
+
/**
* whether a method/ctor is more specific than a previously compared one.
*/
@@ -442,10 +419,28 @@ public static boolean isStrictInvocationConvertible(Class> formal, Class> ac
* by the introspector.
*/
public static class AmbiguousException extends RuntimeException {
+ /** Version Id for serializable. */
+ private static final long serialVersionUID = -201801091655L;
+ /** Whether this exception should be considered severe. */
+ private final boolean severe;
+
/**
- * Version Id for serializable.
+ * A severe or not ambiguous exception.
+ * @param flag logging flag
*/
- private static final long serialVersionUID = -2314636505414551664L;
+ AmbiguousException(boolean flag) {
+ this.severe = flag;
+ }
+
+ /**
+ * Whether this exception is considered severe or benign.
+ * Note that this is meant in the context of an ambiguous exception; benign cases can only be triggered
+ * by null arguments often related to runtime problems (not simply on overload signatures).
+ * @return true if severe, false if benign.
+ */
+ public boolean isSevere() {
+ return severe;
+ }
}
/**
@@ -482,14 +477,14 @@ private abstract static class Parameters {
* like this is needed.
*
*
- * @param methods a list of methods.
- * @param classes list of argument types.
+ * @param key a method key, esp its parameters
+ * @param methods a list of methods
* @return the most specific method.
* @throws MethodKey.AmbiguousException if there is more than one.
*/
- private T getMostSpecific(T[] methods, Class>[] classes) {
- LinkedList applicables = getApplicables(methods, classes);
-
+ private T getMostSpecific(MethodKey key, T[] methods) {
+ final Class>[] args = key.params;
+ LinkedList applicables = getApplicables(methods, args);
if (applicables.isEmpty()) {
return null;
}
@@ -505,12 +500,12 @@ private T getMostSpecific(T[] methods, Class>[] classes) {
*/
LinkedList maximals = new LinkedList();
for (T app : applicables) {
- Class>[] appArgs = getParameterTypes(app);
+ final Class>[] parms = getParameterTypes(app);
boolean lessSpecific = false;
Iterator maximal = maximals.iterator();
while(!lessSpecific && maximal.hasNext()) {
T max = maximal.next();
- switch (moreSpecific(appArgs, getParameterTypes(max))) {
+ switch (moreSpecific(args, parms, getParameterTypes(max))) {
case MORE_SPECIFIC:
/*
* This method is more specific than the previously
@@ -538,23 +533,69 @@ private T getMostSpecific(T[] methods, Class>[] classes) {
maximals.addLast(app);
}
}
+ // if we have more than one maximally specific method, this call is ambiguous...
if (maximals.size() > 1) {
- // We have more than one maximally specific method
- throw new AmbiguousException();
+ throw ambiguousException(args, applicables);
}
return maximals.getFirst();
} // CSON: RedundantThrows
+ /**
+ * Creates an ambiguous exception.
+ *
+ * This method computes the severity of the ambiguity. The only non-severe case is when there is
+ * at least one null argument and at most one applicable method or constructor has a corresponding 'Object'
+ * parameter.
+ * We thus consider that ambiguity is benign in presence of null arguments but in the case where
+ * the corresponding parameter is of type Object in more than one applicable overloads.
+ *
+ * Rephrasing:
+ *
+ * If all arguments are valid instances - no null argument -, ambiguity is severe.
+ * If there is at least one null argument, the ambiguity is severe if more than one method has a
+ * corresponding parameter of class 'Object'.
+ *
+ *
+ * @param classes the argument args
+ * @param applicables the list of applicable methods or constructors
+ * @return an ambiguous exception
+ */
+ private AmbiguousException ambiguousException (Class>[] classes, List applicables) {
+ boolean severe = false;
+ int instanceArgCount = 0; // count the number of valid instances, aka not null
+ for(int c = 0; c < classes.length; ++c) {
+ Class> argClazz = classes[c];
+ if (Void.class.equals(argClazz)) {
+ // count the number of methods for which the current arg maps to an Object parameter
+ int objectParmCount = 0;
+ for (T app : applicables) {
+ Class>[] parmClasses = getParameterTypes(app);
+ Class> parmClass = parmClasses[c];
+ if (Object.class.equals(parmClass)) {
+ if (objectParmCount++ == 2) {
+ severe = true;
+ break;
+ }
+ }
+ }
+ } else {
+ instanceArgCount += 1;
+ }
+ }
+ return new AmbiguousException(severe || instanceArgCount == classes.length);
+ }
+
/**
* Determines which method signature (represented by a class array) is more
* specific. This defines a partial ordering on the method signatures.
*
- * @param c1 first signature to compare
- * @param c2 second signature to compare
+ * @param a the arguments signature
+ * @param c1 first method signature to compare
+ * @param c2 second method signature to compare
* @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
* c1 is less specific than c2, INCOMPARABLE if they are incomparable.
*/
- private int moreSpecific(Class>[] c1, Class>[] c2) {
+ private int moreSpecific(final Class>[] a, final Class>[] c1, final Class>[] c2) {
boolean c1MoreSpecific = false;
boolean c2MoreSpecific = false;
@@ -575,6 +616,16 @@ private int moreSpecific(Class>[] c1, Class>[] c2) {
for (int i = 0; i < length; ++i) {
if (c1[i] != c2[i]) {
boolean last = (i == ultimate);
+ if (a[i] == Void.class) {
+ if (c1[i] == Object.class && c2[i] != Object.class) {
+ c1MoreSpecific = true;
+ continue;
+ }
+ if (c1[i] != Object.class && c2[i] == Object.class) {
+ c2MoreSpecific = true;
+ continue;
+ }
+ }
c1MoreSpecific = c1MoreSpecific || isStrictConvertible(c2[i], c1[i], last);
c2MoreSpecific = c2MoreSpecific || isStrictConvertible(c1[i], c2[i], last);
}
@@ -604,7 +655,8 @@ private int moreSpecific(Class>[] c1, Class>[] c2) {
}
if (primDiff > 0) {
return MORE_SPECIFIC;
- } else if (primDiff < 0) {
+ }
+ if (primDiff < 0) {
return LESS_SPECIFIC;
}
/*
@@ -753,6 +805,7 @@ private boolean isStrictConvertible(Class> formal, Class> actual,
return isStrictInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg);
}
}
+
/**
* The parameter matching service for methods.
*/
@@ -766,7 +819,9 @@ protected Class>[] getParameterTypes(Method app) {
public boolean isVarArgs(Method app) {
return MethodKey.isVarArgs(app);
}
+
};
+
/**
* The parameter matching service for constructors.
*/
@@ -780,5 +835,6 @@ protected Class>[] getParameterTypes(Constructor> app) {
public boolean isVarArgs(Constructor> app) {
return app.isVarArgs();
}
+
};
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
index 19073a3ec..c79e7e0f1 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
@@ -20,25 +20,42 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import org.apache.commons.jexl3.annotations.NoJexl;
/**
- * Checks whether an element (ctor, field or method) is visible by JEXL introspection
- * by checking if has been annotated with NoJexl.
+ * Checks whether an element (ctor, field or method) is visible by JEXL introspection.
+ * Default implementation does this by checking if element has been annotated with NoJexl.
*/
public class Permissions {
/** Make non instantiable. */
private Permissions() {
}
+ /**
+ * The default singleton.
+ */
+ public static final Permissions DEFAULT = new Permissions();
/**
- * Checks whether a class or one of its superclasses or implemented interfaces
+ * Checks whether a package explicitly disallows JEXL introspection.
+ * @param pack the package
+ * @return true if JEXL is allowed to introspect, false otherwise
+ */
+ public boolean allow(Package pack) {
+ if (pack != null && pack.getAnnotation(NoJexl.class) != null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether a class or one of its super-classes or implemented interfaces
* explicitly disallows JEXL introspection.
* @param clazz the class to check
* @return true if JEXL is allowed to introspect, false otherwise
*/
- public static boolean allow(Class> clazz) {
- return allow(clazz, true);
+ public boolean allow(Class> clazz) {
+ return clazz != null && allow(clazz.getPackage()) && allow(clazz, true);
}
/**
@@ -46,10 +63,13 @@ public static boolean allow(Class> clazz) {
* @param ctor the constructor to check
* @return true if JEXL is allowed to introspect, false otherwise
*/
- public static boolean allow(Constructor> ctor) {
+ public boolean allow(Constructor> ctor) {
if (ctor == null) {
return false;
}
+ if (!Modifier.isPublic(ctor.getModifiers())) {
+ return false;
+ }
Class> clazz = ctor.getDeclaringClass();
if (!allow(clazz, false)) {
return false;
@@ -67,10 +87,13 @@ public static boolean allow(Constructor> ctor) {
* @param field the field to check
* @return true if JEXL is allowed to introspect, false otherwise
*/
- public static boolean allow(Field field) {
+ public boolean allow(Field field) {
if (field == null) {
return false;
}
+ if (!Modifier.isPublic(field.getModifiers())) {
+ return false;
+ }
Class> clazz = field.getDeclaringClass();
if (!allow(clazz, false)) {
return false;
@@ -85,15 +108,18 @@ public static boolean allow(Field field) {
/**
* Checks whether a method explicitly disallows JEXL introspection.
- * Since methods can be overriden, this also checks that no superclass or interface
+ *
Since methods can be overridden, this also checks that no superclass or interface
* explictly disallows this methods.
* @param method the method to check
* @return true if JEXL is allowed to introspect, false otherwise
*/
- public static boolean allow(Method method) {
+ public boolean allow(Method method) {
if (method == null) {
return false;
}
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return false;
+ }
// is method annotated with nojexl ?
NoJexl nojexl = method.getAnnotation(NoJexl.class);
if (nojexl != null) {
@@ -134,9 +160,7 @@ private static boolean allow(Class> clazz, boolean interf) {
if (clazz == null) {
return false;
}
- // is package annotated with nojexl ?
- Package pack = clazz.getPackage();
- if (pack != null && pack.getAnnotation(NoJexl.class) != null) {
+ if (!Modifier.isPublic(clazz.getModifiers())) {
return false;
}
// lets walk all interfaces
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
index 3d5483eeb..583a9d400 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
@@ -53,25 +53,16 @@ public SandboxUberspect(final JexlUberspect theUberspect, final JexlSandbox theS
this.sandbox = theSandbox.copy();
}
- /**
- * {@inheritDoc}
- */
@Override
public void setClassLoader(ClassLoader loader) {
uberspect.setClassLoader(loader);
}
- /**
- * {@inheritDoc}
- */
@Override
public int getVersion() {
return uberspect.getVersion();
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlMethod getConstructor(final Object ctorHandle, final Object... args) {
final String className;
@@ -89,9 +80,6 @@ public JexlMethod getConstructor(final Object ctorHandle, final Object... args)
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlMethod getMethod(final Object obj, final String method, final Object... args) {
if (obj != null && method != null) {
@@ -104,25 +92,16 @@ public JexlMethod getMethod(final Object obj, final String method, final Object.
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public List getResolvers(JexlOperator op, Object obj) {
return uberspect.getResolvers(op, obj);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) {
return getPropertyGet(null, obj, identifier);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertyGet getPropertyGet(final List resolvers,
final Object obj,
@@ -136,17 +115,11 @@ public JexlPropertyGet getPropertyGet(final List resolvers,
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertySet getPropertySet(final Object obj,final Object identifier,final Object arg) {
return getPropertySet(null, obj, identifier, arg);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertySet getPropertySet(final List resolvers,
final Object obj,
@@ -161,17 +134,11 @@ public JexlPropertySet getPropertySet(final List resolvers,
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public Iterator> getIterator(final Object obj) {
return uberspect.getIterator(obj);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) {
return uberspect.getArithmetic(arithmetic);
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index cab15dce8..8ebdd082f 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -109,9 +109,6 @@ protected final Introspector base() {
}
// CSON: DoubleCheckedLocking
- /**
- * {@inheritDoc}
- */
@Override
public void setClassLoader(ClassLoader nloader) {
synchronized (this) {
@@ -128,9 +125,6 @@ public void setClassLoader(ClassLoader nloader) {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public int getVersion() {
return version.intValue();
@@ -219,33 +213,21 @@ public final Method[] getMethods(Class> c, final String methodName) {
return base().getMethods(c, methodName);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlMethod getMethod(Object obj, String method, Object... args) {
return MethodExecutor.discover(base(), obj, method, args);
}
- /**
- * {@inheritDoc}
- */
@Override
public List getResolvers(JexlOperator op, Object obj) {
return strategy.apply(op, obj);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertyGet getPropertyGet(Object obj, Object identifier) {
return getPropertyGet(null, obj, identifier);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertyGet getPropertyGet(
final List resolvers, final Object obj, final Object identifier
@@ -287,6 +269,10 @@ public JexlPropertyGet getPropertyGet(
case FIELD:
// a field may be? (can not be a number)
executor = FieldGetExecutor.discover(is, claz, property);
+ // static class fields (enums included)
+ if (obj instanceof Class>) {
+ executor = FieldGetExecutor.discover(is, (Class>) obj, property);
+ }
break;
case CONTAINER:
// or an indexed property?
@@ -304,17 +290,12 @@ public JexlPropertyGet getPropertyGet(
}
return null;
}
- /**
- * {@inheritDoc}
- */
+
@Override
public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) {
return getPropertySet(null, obj, identifier, arg);
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlPropertySet getPropertySet(
final List resolvers, final Object obj, final Object identifier, final Object arg
@@ -368,9 +349,6 @@ public JexlPropertySet getPropertySet(
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
@SuppressWarnings("unchecked")
public Iterator> getIterator(Object obj) {
@@ -405,9 +383,6 @@ public Iterator> getIterator(Object obj) {
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlMethod getConstructor(Object ctorHandle, Object... args) {
return ConstructorMethod.discover(base(), ctorHandle, args);
@@ -420,7 +395,7 @@ protected class ArithmeticUberspect implements JexlArithmetic.Uberspect {
/** The arithmetic instance being analyzed. */
private final JexlArithmetic arithmetic;
/** The set of overloaded operators. */
- private final EnumSet overloads;
+ private final Set overloads;
/**
* Creates an instance.
@@ -429,9 +404,7 @@ protected class ArithmeticUberspect implements JexlArithmetic.Uberspect {
*/
private ArithmeticUberspect(JexlArithmetic theArithmetic, Set theOverloads) {
this.arithmetic = theArithmetic;
- this.overloads = EnumSet.copyOf(theOverloads);
- // register this arithmetic class in the operator map
- operatorMap.put(arithmetic.getClass(), overloads);
+ this.overloads = theOverloads;
}
@Override
@@ -447,41 +420,40 @@ public boolean overloads(JexlOperator operator) {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {
JexlArithmetic.Uberspect jau = null;
if (arithmetic != null) {
- Set ops = operatorMap.get(arithmetic.getClass());
+ final Class extends JexlArithmetic> aclass = arithmetic.getClass();
+ Set ops = operatorMap.get(aclass);
if (ops == null) {
ops = EnumSet.noneOf(JexlOperator.class);
- for (JexlOperator op : JexlOperator.values()) {
- Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName());
- if (methods != null) {
- for (Method method : methods) {
- Class>[] parms = method.getParameterTypes();
- if (parms.length != op.getArity()) {
- continue;
- }
- // eliminate method(Object) and method(Object, Object)
- boolean root = true;
- for (int p = 0; root && p < parms.length; ++p) {
- if (!Object.class.equals(parms[p])) {
- root = false;
+ // deal only with derived classes
+ if (!JexlArithmetic.class.equals(aclass)) {
+ for (JexlOperator op : JexlOperator.values()) {
+ Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName());
+ if (methods != null) {
+ mloop:
+ for (Method method : methods) {
+ Class>[] parms = method.getParameterTypes();
+ if (parms.length != op.getArity()) {
+ continue;
+ }
+ // keep only methods that are not overrides
+ try {
+ JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes());
+ } catch (NoSuchMethodException xmethod) {
+ // method was not found in JexlArithmetic; this is an operator definition
+ ops.add(op);
}
- }
- if (!root) {
- ops.add(op);
}
}
}
}
+ // register this arithmetic class in the operator map
+ operatorMap.put(aclass, ops);
}
- if (!ops.isEmpty()) {
- jau = new ArithmeticUberspect(arithmetic, ops);
- }
+ jau = new ArithmeticUberspect(arithmetic, ops);
}
return jau;
}
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
index cebbb1832..1b4e29920 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
@@ -25,56 +25,83 @@
/**
* A sandbox describes permissions on a class by explicitly allowing or forbidding access to methods and properties
* through "whitelists" and "blacklists".
- *
+ *
* A whitelist explicitly allows methods/properties for a class;
- *
+ *
*
- * If a whitelist is empty and thus does not contain any names,
+ * If a whitelist is empty and thus does not contain any names,
* all properties/methods are allowed for its class.
* If it is not empty, the only allowed properties/methods are the ones contained.
*
- *
+ *
* A blacklist explicitly forbids methods/properties for a class;
- *
+ *
*
* If a blacklist is empty and thus does not contain any names,
* all properties/methods are forbidden for its class.
* If it is not empty, the only forbidden properties/methods are the ones contained.
*
- *
+ *
* Permissions are composed of three lists, read, write, execute, each being "white" or "black":
- *
+ *
*
* read controls readable properties
- * write controls writeable properties
+ * write controls writable properties
* execute controls executable methods and constructor
*
- *
- * Note that a JexlUberspect allways uses a copy of the JexlSandbox used to built it to avoid synchronization and/or
+ *
+ *
Note that a JexlUberspect always uses a copy of the JexlSandbox used to built it to avoid synchronization and/or
* concurrent modifications at runtime.
- *
+ *
* @since 3.0
*/
public final class JexlSandbox {
-
/**
* The map from class names to permissions.
*/
private final Map sandbox;
+ /**
+ * Default behavior, black or white.
+ */
+ private final boolean white;
/**
* Creates a new default sandbox.
+ * In the absence of explicit permissions on a class, the
+ * sandbox is a white-box, white-listing that class for all permissions (read, write and execute).
*/
public JexlSandbox() {
- this(new HashMap());
+ this(true, new HashMap());
+ }
+
+ /**
+ * Creates a new default sandbox.
+ * A white-box considers no permissions as "everything is allowed" when
+ * a black-box considers no permissions as "nothing is allowed".
+ * @param wb whether this sandbox is white (true) or black (false)
+ * if no permission is explicitly defined for a class.
+ * @since 3.1
+ */
+ public JexlSandbox(boolean wb) {
+ this(wb, new HashMap());
}
/**
* Creates a sandbox based on an existing permissions map.
- *
* @param map the permissions map
*/
protected JexlSandbox(Map map) {
+ this(true, map);
+ }
+
+ /**
+ * Creates a sandbox based on an existing permissions map.
+ * @param wb whether this sandbox is white (true) or black (false)
+ * @param map the permissions map
+ * @since 3.1
+ */
+ protected JexlSandbox(boolean wb, Map map) {
+ white = wb;
sandbox = map;
}
@@ -86,12 +113,12 @@ public JexlSandbox copy() {
for (Map.Entry entry : sandbox.entrySet()) {
map.put(entry.getKey(), entry.getValue().copy());
}
- return new JexlSandbox(map);
+ return new JexlSandbox(white, map);
}
/**
* Gets the read permission value for a given property of a class.
- *
+ *
* @param clazz the class
* @param name the property name
* @return null if not allowed, the name of the property to use otherwise
@@ -102,7 +129,7 @@ public String read(Class> clazz, String name) {
/**
* Gets the read permission value for a given property of a class.
- *
+ *
* @param clazz the class name
* @param name the property name
* @return null if not allowed, the name of the property to use otherwise
@@ -110,7 +137,7 @@ public String read(Class> clazz, String name) {
public String read(String clazz, String name) {
Permissions permissions = sandbox.get(clazz);
if (permissions == null) {
- return name;
+ return white? name : null;
} else {
return permissions.read().get(name);
}
@@ -118,7 +145,7 @@ public String read(String clazz, String name) {
/**
* Gets the write permission value for a given property of a class.
- *
+ *
* @param clazz the class
* @param name the property name
* @return null if not allowed, the name of the property to use otherwise
@@ -129,7 +156,7 @@ public String write(Class> clazz, String name) {
/**
* Gets the write permission value for a given property of a class.
- *
+ *
* @param clazz the class name
* @param name the property name
* @return null if not allowed, the name of the property to use otherwise
@@ -137,7 +164,7 @@ public String write(Class> clazz, String name) {
public String write(String clazz, String name) {
Permissions permissions = sandbox.get(clazz);
if (permissions == null) {
- return name;
+ return white ? name : null;
} else {
return permissions.write().get(name);
}
@@ -145,7 +172,7 @@ public String write(String clazz, String name) {
/**
* Gets the execute permission value for a given method of a class.
- *
+ *
* @param clazz the class
* @param name the method name
* @return null if not allowed, the name of the method to use otherwise
@@ -156,7 +183,7 @@ public String execute(Class> clazz, String name) {
/**
* Gets the execute permission value for a given method of a class.
- *
+ *
* @param clazz the class name
* @param name the method name
* @return null if not allowed, the name of the method to use otherwise
@@ -164,7 +191,7 @@ public String execute(Class> clazz, String name) {
public String execute(String clazz, String name) {
Permissions permissions = sandbox.get(clazz);
if (permissions == null) {
- return name;
+ return white ? name : null;
} else {
return permissions.execute().get(name);
}
@@ -177,7 +204,7 @@ public abstract static class Names {
/**
* Adds a name to this set.
- *
+ *
* @param name the name to add
* @return true if the name was really added, false if not
*/
@@ -186,7 +213,7 @@ public abstract static class Names {
/**
* Adds an alias to a name to this set.
* This only has an effect on white lists.
- *
+ *
* @param name the name to alias
* @param alias the alias
* @return true if the alias was added, false if it was already present
@@ -197,7 +224,7 @@ public boolean alias(String name, String alias) {
/**
* Whether a given name is allowed or not.
- *
+ *
* @param name the method/property name to check
* @return null if not allowed, the actual name to use otherwise
*/
@@ -304,7 +331,7 @@ public static final class Permissions {
/** The controlled readable properties. */
private final Names read;
- /** The controlled writeable properties. */
+ /** The controlled writable properties. */
private final Names write;
/** The controlled methods. */
@@ -312,7 +339,7 @@ public static final class Permissions {
/**
* Creates a new permissions instance.
- *
+ *
* @param readFlag whether the read property list is white or black
* @param writeFlag whether the write property list is white or black
* @param executeFlag whether the method list is white of black
@@ -325,7 +352,7 @@ public static final class Permissions {
/**
* Creates a new permissions instance.
- *
+ *
* @param nread the read set
* @param nwrite the write set
* @param nexecute the method set
@@ -345,7 +372,7 @@ Permissions copy() {
/**
* Adds a list of readable property names to these permissions.
- *
+ *
* @param pnames the property names
* @return this instance of permissions
*/
@@ -357,8 +384,8 @@ public Permissions read(String... pnames) {
}
/**
- * Adds a list of writeable property names to these permissions.
- *
+ * Adds a list of writable property names to these permissions.
+ *
* @param pnames the property names
* @return this instance of permissions
*/
@@ -372,7 +399,7 @@ public Permissions write(String... pnames) {
/**
* Adds a list of executable methods names to these permissions.
* The constructor is denoted as the empty-string, all other methods by their names.
- *
+ *
* @param mnames the method names
* @return this instance of permissions
*/
@@ -385,7 +412,7 @@ public Permissions execute(String... mnames) {
/**
* Gets the set of readable property names in these permissions.
- *
+ *
* @return the set of property names
*/
public Names read() {
@@ -393,8 +420,8 @@ public Names read() {
}
/**
- * Gets the set of writeable property names in these permissions.
- *
+ * Gets the set of writable property names in these permissions.
+ *
* @return the set of property names
*/
public Names write() {
@@ -403,7 +430,7 @@ public Names write() {
/**
* Gets the set of method names in these permissions.
- *
+ *
* @return the set of method names
*/
public Names execute() {
@@ -417,10 +444,10 @@ public Names execute() {
/**
* Creates the set of permissions for a given class.
- *
+ *
* @param clazz the class for which these permissions apply
* @param readFlag whether the readable property list is white - true - or black - false -
- * @param writeFlag whether the writeable property list is white - true - or black - false -
+ * @param writeFlag whether the writable property list is white - true - or black - false -
* @param executeFlag whether the executable method list is white white - true - or black - false -
* @return the set of permissions
*/
@@ -432,7 +459,7 @@ public Permissions permissions(String clazz, boolean readFlag, boolean writeFlag
/**
* Creates a new set of permissions based on white lists for methods and properties for a given class.
- *
+ *
* @param clazz the whitened class name
* @return the permissions instance
*/
@@ -442,7 +469,7 @@ public Permissions white(String clazz) {
/**
* Creates a new set of permissions based on black lists for methods and properties for a given class.
- *
+ *
* @param clazz the blackened class name
* @return the permissions instance
*/
@@ -452,7 +479,7 @@ public Permissions black(String clazz) {
/**
* Gets the set of permissions associated to a class.
- *
+ *
* @param clazz the class name
* @return the defined permissions or an all-white permission instance if none were defined
*/
diff --git a/src/main/java/org/apache/commons/jexl3/package.html b/src/main/java/org/apache/commons/jexl3/package.html
index d6d026b90..92ea77441 100644
--- a/src/main/java/org/apache/commons/jexl3/package.html
+++ b/src/main/java/org/apache/commons/jexl3/package.html
@@ -80,11 +80,13 @@
org.apache.commons.jexl3
org.apache.commons.jexl3.introspection
+
The following packages follow a "use at your own maintenance cost" policy; these are only intended to be used
for extending JEXL.
Their classes and methods are not guaranteed to remain compatible in subsequent versions.
If you think you need to use directly some of their features or methods, it might be a good idea to check with
the community through the mailing list first.
+
org.apache.commons.jexl3.parser
org.apache.commons.jexl3.scripting
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java b/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java
new file mode 100644
index 000000000..be7954475
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTAnnotation.java
@@ -0,0 +1,54 @@
+/*
+ * 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.parser;
+
+/**
+ * Annotation.
+ */
+public class ASTAnnotation extends JexlNode {
+ private String name = null;
+
+ ASTAnnotation(int id) {
+ super(id);
+ }
+
+ ASTAnnotation(Parser p, int id) {
+ super(p, id);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ void setName(String identifier) {
+ if (identifier.charAt(0) == '@') {
+ name = identifier.substring(1);
+ } else {
+ name = identifier;
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data) {
+ return visitor.visit(this, data);
+ }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
index 5a90cb244..e0d550e1d 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
@@ -44,7 +44,6 @@ protected boolean isConstant(boolean literal) {
return constant;
}
- /** {@inheritDoc} */
@Override
public void jjtClose() {
constant = true;
@@ -58,7 +57,6 @@ public void jjtClose() {
}
}
- /** {@inheritDoc} */
@Override
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
index 2f8884e24..8dd894734 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.jexl3.parser;
+import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.internal.Scope;
import java.util.Map;
@@ -26,7 +27,9 @@ public class ASTJexlScript extends JexlNode {
/** The script scope. */
private Scope scope = null;
/** The pragmas. */
- Map pragmas = null;
+ private Map pragmas = null;
+ /** Features. */
+ private JexlFeatures features = null;
public ASTJexlScript(int id) {
super(id);
@@ -54,18 +57,34 @@ public ASTJexlScript script() {
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
+ /**
+ * Sets this script pragmas.
+ * @param pragmas the pragmas
+ */
+ public void setPragmas(Map thePragmas) {
+ this.pragmas = thePragmas;
+ }
/**
- * Coerce this script as an expression (ie only one child) if necessary.
- * @return true if the script was coerced, false otherwise
+ * @return this script pragmas.
*/
- public boolean toExpression() {
- if (jjtGetNumChildren() > 1) {
- jjtSetChildren(new JexlNode[]{jjtGetChild(0)});
- return true;
- } else {
- return false;
- }
+ public Map getPragmas() {
+ return pragmas;
+ }
+
+ /**
+ * Sets this script features.
+ * @param theFeatures the features
+ */
+ public void setFeatures(JexlFeatures theFeatures) {
+ this.features = theFeatures;
+ }
+
+ /**
+ * @return this script scope
+ */
+ public JexlFeatures getFeatures() {
+ return features;
}
/**
@@ -83,13 +102,6 @@ public Scope getScope() {
return scope;
}
- /**
- * @return this script pragmas
- */
- public Map getPragmas() {
- return pragmas;
- }
-
/**
* Creates an array of arguments by copying values up to the number of parameters.
* @param values the argument values
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
index e90bcf356..93a14b6fd 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJxltLiteral.java
@@ -45,7 +45,6 @@ public String toString() {
return this.literal;
}
- /** {@inheritDoc} */
@Override
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java
index cae63f801..5650dbf63 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTMapLiteral.java
@@ -41,7 +41,6 @@ protected boolean isConstant(boolean literal) {
return constant;
}
- /** {@inheritDoc} */
@Override
public void jjtClose() {
constant = true;
@@ -55,7 +54,6 @@ public void jjtClose() {
}
}
- /** {@inheritDoc} */
@Override
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTNumberLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTNumberLiteral.java
index 163e6193b..d5b614dd3 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTNumberLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTNumberLiteral.java
@@ -44,7 +44,7 @@ protected boolean isConstant(boolean literal) {
return true;
}
- public Class> getLiteralClass() {
+ public Class extends Number> getLiteralClass() {
return nlp.getLiteralClass();
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java
index 8531593fa..4db2395f4 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSetLiteral.java
@@ -41,7 +41,6 @@ protected boolean isConstant(boolean literal) {
return constant;
}
- /** {@inheritDoc} */
@Override
public void jjtClose() {
constant = true;
@@ -53,7 +52,6 @@ public void jjtClose() {
}
}
- /** {@inheritDoc} */
@Override
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTStringLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTStringLiteral.java
index 383e6e31e..4898dbe0f 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTStringLiteral.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTStringLiteral.java
@@ -42,8 +42,6 @@ public String getLiteral() {
return this.literal;
}
-
- /** {@inheritDoc} */
@Override
protected boolean isConstant(boolean literal) {
return true;
@@ -53,8 +51,6 @@ void setLiteral(String literal) {
this.literal = literal;
}
-
- /** {@inheritDoc} */
@Override
public Object jjtAccept(ParserVisitor visitor, Object data) {
return visitor.visit(this, data);
diff --git a/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java b/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
new file mode 100644
index 000000000..92d0e7217
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/FeatureController.java
@@ -0,0 +1,229 @@
+/*
+ * 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.parser;
+
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlFeatures;
+import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.internal.ScriptVisitor;
+/**
+ * Controls that a script only uses enabled features.
+ */
+public class FeatureController extends ScriptVisitor {
+ /** The set of features. */
+ private JexlFeatures features = null;
+
+ /**
+ * Creates a features controller .
+ */
+ public FeatureController(JexlFeatures features) {
+ this.features = features;
+ }
+
+ /**
+ * Sets the features to controlNode.
+ * @param fdesc the features
+ */
+ public void setFeatures(JexlFeatures fdesc) {
+ this.features = fdesc;
+ }
+
+ /**
+ * @return the controlled features
+ */
+ public JexlFeatures getFeatures() {
+ return features;
+ }
+
+ /**
+ * Perform the control on a node.
+ * Note that controlNode() does *not* visit node children in this class.
+ * @param node the node to controlNode
+ * @throws JexlException.Feature if required feature is disabled
+ */
+ public void controlNode(JexlNode node) {
+ node.jjtAccept(this, null);
+ }
+
+ @Override
+ protected Object visitNode(JexlNode node, Object data) {
+ // no need to visit them since we close them one by one
+ return data;
+ }
+
+ /**
+ * Throws a feature exception.
+ * @param feature the feature code
+ * @param node the node that caused it
+ */
+ public void throwFeatureException(int feature, JexlNode node) {
+ JexlInfo dbgInfo = node.jexlInfo();
+ throw new JexlException.Feature(dbgInfo, feature, "");
+ }
+
+ /**
+ * Checks whether a node is a string or an integer.
+ * @param child the child node
+ * @return true if string / integer, false otherwise
+ */
+ private boolean isArrayReferenceLiteral(JexlNode child) {
+ if (child instanceof ASTStringLiteral) {
+ return true;
+ }
+ if (child instanceof ASTNumberLiteral && ((ASTNumberLiteral) child).isInteger()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected Object visit(ASTArrayAccess node, Object data) {
+ if (!features.supportsArrayReferenceExpr()) {
+ for (int i = 0; i < node.jjtGetNumChildren(); ++i) {
+ JexlNode child = node.jjtGetChild(i);
+ if (!isArrayReferenceLiteral(child)) {
+ throwFeatureException(JexlFeatures.ARRAY_REF_EXPR, child);
+ }
+ }
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTWhileStatement node, Object data) {
+ if (!features.supportsLoops()) {
+ throwFeatureException(JexlFeatures.LOOP, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTForeachStatement node, Object data) {
+ if (!features.supportsLoops()) {
+ throwFeatureException(JexlFeatures.LOOP, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTConstructorNode node, Object data) {
+ if (!features.supportsNewInstance()) {
+ throwFeatureException(JexlFeatures.NEW_INSTANCE, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTMethodNode node, Object data) {
+ if (!features.supportsMethodCall()) {
+ throwFeatureException(JexlFeatures.METHOD_CALL, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTAnnotation node, Object data) {
+ if (!features.supportsAnnotation()) {
+ throwFeatureException(JexlFeatures.ANNOTATION, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTArrayLiteral node, Object data) {
+ if (!features.supportsStructuredLiteral()) {
+ throwFeatureException(JexlFeatures.STRUCTURED_LITERAL, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTMapLiteral node, Object data) {
+ if (!features.supportsStructuredLiteral()) {
+ throwFeatureException(JexlFeatures.STRUCTURED_LITERAL, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTSetLiteral node, Object data) {
+ if (!features.supportsStructuredLiteral()) {
+ throwFeatureException(JexlFeatures.STRUCTURED_LITERAL, node);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTRangeNode node, Object data) {
+ if (!features.supportsStructuredLiteral()) {
+ throwFeatureException(JexlFeatures.STRUCTURED_LITERAL, node);
+ }
+ return data;
+ }
+
+ private Object controlSideEffect(JexlNode node, Object data) {
+ JexlNode lv = node.jjtGetChild(0);
+ if (!features.supportsSideEffectGlobal() && lv.isGlobalVar()) {
+ throwFeatureException(JexlFeatures.SIDE_EFFECT_GLOBAL, lv);
+ }
+ if (!features.supportsSideEffect()) {
+ throwFeatureException(JexlFeatures.SIDE_EFFECT, lv);
+ }
+ return data;
+ }
+
+ @Override
+ protected Object visit(ASTAssignment node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetAddNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetMultNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetDivNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetAndNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetOrNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetXorNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+ @Override
+ protected Object visit(ASTSetSubNode node, Object data) {
+ return controlSideEffect(node, data);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
index e50774f70..4772d5bf8 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -62,22 +62,24 @@ public void jjtSetLastToken(Token t) {
* @return the info
*/
public JexlInfo jexlInfo() {
+ JexlInfo info = null;
JexlNode node = this;
while (node != null) {
if (node.jjtGetValue() instanceof JexlInfo) {
- JexlInfo info = (JexlInfo) node.jjtGetValue();
- if (lc >= 0) {
- int c = lc & 0xfff;
- int l = lc >> 0xc;
- return info.at(l, c);
- } else {
- // weird though; no jjSetFirstToken(...) ever called?
- return info;
- }
+ info = (JexlInfo) node.jjtGetValue();
+ break;
}
node = node.jjtGetParent();
}
- return null;
+ if (lc >= 0) {
+ int c = lc & 0xfff;
+ int l = lc >> 0xc;
+ // at least an info with line/column number
+ return info != null? info.at(l, c) : new JexlInfo(null, l, c);
+ } else {
+ // weird though; no jjSetFirstToken(...) ever called?
+ return info;
+ }
}
/**
@@ -136,17 +138,47 @@ protected boolean isConstant(boolean literal) {
* @return true if node is assignable, false otherwise
*/
public boolean isLeftValue() {
- if (this instanceof ASTIdentifier || this instanceof ASTIdentifierAccess) {
+ JexlNode walk = this;
+ do {
+ if (walk instanceof ASTIdentifier
+ || walk instanceof ASTIdentifierAccess
+ || walk instanceof ASTArrayAccess) {
+ return true;
+ }
+ int nc = walk.jjtGetNumChildren() - 1;
+ if (nc >= 0) {
+ walk = walk.jjtGetChild(nc);
+ } else if (walk.jjtGetParent() instanceof ASTReference) {
+ return true;
+ } else {
+ return false;
+ }
+ } while (walk != null);
+ return false;
+ }
+
+ public boolean isGlobalVar() {
+ if (this instanceof ASTVar) {
+ return false;
+ }
+ if (this instanceof ASTIdentifier) {
+ return ((ASTIdentifier) this).getSymbol() < 0;
+ }
+ if (this instanceof ASTIdentifierAccess) {
return true;
}
int nc = this.jjtGetNumChildren() - 1;
if (nc >= 0) {
- JexlNode last = this.jjtGetChild(this.jjtGetNumChildren() - 1);
- return last.isLeftValue();
+ JexlNode first = this.jjtGetChild(0);
+ return first.isGlobalVar();
}
- if (jjtGetParent() instanceof ASTReference || jjtGetParent() instanceof ASTArrayAccess) {
+ if (jjtGetParent() instanceof ASTReference) {
return true;
}
return false;
}
+
+ public boolean isLocalVar() {
+ return this instanceof ASTIdentifier && ((ASTIdentifier) this).getSymbol() >= 0;
+ }
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index c6a605cc9..188626e5a 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -16,44 +16,112 @@
*/
package org.apache.commons.jexl3.parser;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
+import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.internal.Scope;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
+import java.util.Set;
import java.util.Stack;
+import java.util.TreeMap;
+
/**
* The base class for parsing, manages the parameter/local variable frame.
*/
public abstract class JexlParser extends StringParser {
- /** Whether the parser will allow user-named registers (aka #0 syntax). */
- boolean ALLOW_REGISTERS = false;
- /** The source being processed. */
- String source = null;
+ /**
+ * The associated controller.
+ */
+ protected final FeatureController featureController = new FeatureController(JexlEngine.DEFAULT_FEATURES);
+ /**
+ * The source being processed.
+ */
+ protected String source = null;
/**
* The map of named registers aka script parameters.
*
Each parameter is associated to a register and is materialized
* as an offset in the registers array used during evaluation.
*/
- Scope frame = null;
- Stack frames = new Stack();
+ protected Scope frame = null;
+ /**
+ * When parsing inner functions/lambda, need to stack the scope (sic).
+ */
+ protected Stack frames = new Stack();
/**
* The list of pragma declarations.
*/
- Map pragmas = null;
+ protected Map pragmas = null;
+
+ /**
+ * Utility function to create '.' separated string from a list of string.
+ * @param lstr the list of strings
+ * @return the dotted version
+ */
+ protected static String stringify(List lstr) {
+ StringBuilder strb = new StringBuilder();
+ boolean dot = false;
+ for(String str : lstr) {
+ if (!dot) {
+ dot = true;
+ } else {
+ strb.append('.');
+ }
+ strb.append(str);
+ }
+ return strb.toString();
+ }
+ /**
+ * Reading a given source line
+ * @parma src the source
+ * @param lineno the line number
+ * @return the line
+ */
+ protected static String readSourceLine(String src, int lineno) {
+ String msg = "";
+ if (src != null && lineno >= 0) {
+ try {
+ BufferedReader reader = new BufferedReader(new StringReader(src));
+ for (int l = 0; l < lineno; ++l) {
+ msg = reader.readLine();
+ }
+ } catch (IOException xio) {
+ // ignore, very unlikely but then again...
+ }
+ }
+ return msg;
+ }
/**
* Internal, for debug purpose only.
*/
public void allowRegisters(boolean registers) {
- ALLOW_REGISTERS = registers;
+ featureController.setFeatures(new JexlFeatures(featureController.getFeatures()).register(registers));
+ }
+
+ /**
+ * Sets a new set of options.
+ * @param features
+ */
+ protected void setFeatures(JexlFeatures features) {
+ this.featureController.setFeatures(features);
+ }
+
+ /**
+ * @return the current set of features active during parsing
+ */
+ protected JexlFeatures getFeatures() {
+ return featureController.getFeatures();
}
/**
@@ -61,7 +129,7 @@ public void allowRegisters(boolean registers) {
* This is used to allow parameters to be declared before parsing.
* @param theFrame the register map
*/
- public void setFrame(Scope theFrame) {
+ protected void setFrame(Scope theFrame) {
frame = theFrame;
}
@@ -71,14 +139,14 @@ public void setFrame(Scope theFrame) {
* regain access after parsing to known which / how-many registers are needed.
* @return the named register map
*/
- public Scope getFrame() {
+ protected Scope getFrame() {
return frame;
}
/**
* Create a new local variable frame and push it as current scope.
*/
- public void pushFrame() {
+ protected void pushFrame() {
if (frame != null) {
frames.push(frame);
}
@@ -88,7 +156,7 @@ public void pushFrame() {
/**
* Pops back to previous local variable frame.
*/
- public void popFrame() {
+ protected void popFrame() {
if (!frames.isEmpty()) {
frame = frames.pop();
} else {
@@ -102,7 +170,7 @@ public void popFrame() {
* @param image the identifier image
* @return the image
*/
- public String checkVariable(ASTIdentifier identifier, String image) {
+ protected String checkVariable(ASTIdentifier identifier, String image) {
if (frame != null) {
Integer register = frame.getSymbol(image);
if (register != null) {
@@ -112,18 +180,33 @@ public String checkVariable(ASTIdentifier identifier, String image) {
return image;
}
+ protected boolean allowVariable(String image) {
+ JexlFeatures features = getFeatures();
+ if (!features.supportsLocalVar()) {
+ return false;
+ }
+ if (features.isReservedName(image)) {
+ return false;
+ }
+ return true;
+ }
+
/**
* Declares a local variable.
* This method creates an new entry in the symbol map.
- * @param identifier the identifier used to declare
- * @param image the variable name
+ * @param var the identifier used to declare
+ * @param token the variable name toekn
*/
- public void declareVariable(ASTVar identifier, String image) {
+ protected void declareVariable(ASTVar var, Token token) {
+ String identifier = token.image;
+ if (!allowVariable(identifier)) {
+ throwFeatureException(JexlFeatures.LOCAL_VAR, token);
+ }
if (frame == null) {
frame = new Scope(null, (String[]) null);
}
- Integer register = frame.declareVariable(image);
- identifier.setSymbol(register.intValue(), image);
+ Integer register = frame.declareVariable(identifier);
+ var.setSymbol(register.intValue(), identifier);
}
/**
@@ -131,7 +214,10 @@ public void declareVariable(ASTVar identifier, String image) {
* @param key the pragma key
* @param value the pragma value
*/
- public void declarePragma(String key, Object value) {
+ protected void declarePragma(String key, Object value) {
+ if (!getFeatures().supportsPragma()) {
+ throwFeatureException(JexlFeatures.PRAGMA, getToken(0));
+ }
if (pragmas == null) {
pragmas = new TreeMap();
}
@@ -141,9 +227,13 @@ public void declarePragma(String key, Object value) {
/**
* Declares a local parameter.
* This method creates an new entry in the symbol map.
- * @param identifier the parameter name
+ * @param token the parameter name toekn
*/
- public void declareParameter(String identifier) {
+ protected void declareParameter(Token token) {
+ String identifier = token.image;
+ if (!allowVariable(identifier)) {
+ throwFeatureException(JexlFeatures.LOCAL_VAR, token);
+ }
if (frame == null) {
frame = new Scope(null, (String[]) null);
}
@@ -155,62 +245,97 @@ public void declareParameter(String identifier) {
* @param top whether the identifier is beginning an l/r value
* @throws ParseException subclasses may throw this
*/
- public void Identifier(boolean top) throws ParseException {
+ protected void Identifier(boolean top) throws ParseException {
// Overriden by generated code
}
- final public void Identifier() throws ParseException {
+ final protected void Identifier() throws ParseException {
Identifier(false);
}
- public Token getToken(int index) {
- return null;
- }
+ /**
+ * Overridden in actual parser to access tokens stack.
+ * @param index 0 to get current token
+ * @return the token on the stack
+ */
+ protected abstract Token getToken(int index);
- void jjtreeOpenNodeScope(JexlNode node) {
+ protected void jjtreeOpenNodeScope(JexlNode node) {
+ if (node instanceof ASTAmbiguous) {
+ throwParsingException(JexlException.Ambiguous.class, node);
+ }
}
+ /**
+ * The set of assignment operators as classes.
+ */
+ @SuppressWarnings("unchecked")
+ private static final Set> ASSIGN_NODES = new HashSet>(
+ Arrays.asList(
+ ASTAssignment.class,
+ ASTSetAddNode.class,
+ ASTSetMultNode.class,
+ ASTSetDivNode.class,
+ ASTSetAndNode.class,
+ ASTSetOrNode.class,
+ ASTSetXorNode.class,
+ ASTSetSubNode.class
+ )
+ );
+
/**
* Called by parser at end of node construction.
- * Detects "Ambiguous statement" and 'non-leaft value assignment'.
+ *
+ * Detects "Ambiguous statement" and 'non-left value assignment'.
* @param node the node
* @throws ParseException
*/
- void jjtreeCloseNodeScope(JexlNode node) throws ParseException {
+ protected void jjtreeCloseNodeScope(JexlNode node) throws ParseException {
if (node instanceof ASTJexlScript) {
+ if (node instanceof ASTJexlLambda && !getFeatures().supportsLambda()) {
+ throwFeatureException(JexlFeatures.LAMBDA, node.jexlInfo());
+ }
ASTJexlScript script = (ASTJexlScript) node;
// reaccess in case local variables have been declared
if (script.getScope() != frame) {
script.setScope(frame);
}
popFrame();
- } else if (node instanceof ASTAmbiguous) {
- throwParsingException(JexlException.Ambiguous.class, node);
- } else if (node instanceof ASTAssignment) {
+ } else if (ASSIGN_NODES.contains(node.getClass())) {
JexlNode lv = node.jjtGetChild(0);
if (!lv.isLeftValue()) {
throwParsingException(JexlException.Assignment.class, lv);
}
}
+ // heavy check
+ featureController.controlNode(node);
+ }
+
+
+ /**
+ * Throws a feature exception.
+ * @param feature the feature code
+ * @param info the exception surroundings
+ */
+ protected void throwFeatureException(int feature, JexlInfo info) {
+ String msg = info != null? readSourceLine(source, info.getLine()) : null;
+ throw new JexlException.Feature(info, feature, msg);
}
/**
- * Utility function to create '.' separated string from a list of string.
- * @param lstr the list of strings
- * @return the dotted version
+ * Throws a feature exception.
+ * @param feature the feature code
+ * @param token the token that triggered it
*/
- String stringify(List lstr) {
- StringBuilder strb = new StringBuilder();
- boolean dot = false;
- for(String str : lstr) {
- if (!dot) {
- dot = true;
- } else {
- strb.append('.');
+ protected void throwFeatureException(int feature, Token token) {
+ if (token == null) {
+ token = this.getToken(0);
+ if (token == null) {
+ throw new JexlException.Parsing(null, JexlFeatures.stringify(feature));
}
- strb.append(str);
}
- return strb.toString();
+ JexlInfo info = new JexlInfo(token.image, token.beginLine, token.beginColumn);
+ throwFeatureException(feature, info);
}
/**
@@ -226,33 +351,22 @@ protected void throwParsingException(JexlNode node) {
* @param xclazz the class of exception
* @param node the node that caused it
*/
- private void throwParsingException(Class extends JexlException> xclazz, JexlNode node) {
- final JexlInfo dbgInfo;
+ protected void throwParsingException(Class extends JexlException> xclazz, JexlNode node) {
Token tok = this.getToken(0);
- if (tok != null) {
- dbgInfo = new JexlInfo(tok.image, tok.beginLine, tok.beginColumn);
- } else {
- dbgInfo = node.jexlInfo();
+ if (tok == null) {
+ throw new JexlException.Parsing(null, "unrecoverable state");
}
- String msg = null;
- try {
- if (source != null) {
- BufferedReader reader = new BufferedReader(new StringReader(source));
- for (int l = 0; l < dbgInfo.getLine(); ++l) {
- msg = reader.readLine();
- }
- } else {
- msg = "";
+ JexlInfo dbgInfo = new JexlInfo(tok.image, tok.beginLine, tok.beginColumn);
+ String msg = readSourceLine(source, tok.beginLine);
+ JexlException xjexl = null;
+ if (xclazz != null) {
+ try {
+ Constructor extends JexlException> ctor = xclazz.getConstructor(JexlInfo.class, String.class);
+ xjexl = ctor.newInstance(dbgInfo, msg);
+ } catch (Exception xany) {
+ // ignore, very unlikely but then again..
}
- } catch (IOException xio) {
- // ignore
- }
- if (JexlException.Ambiguous.class.equals(xclazz)) {
- throw new JexlException.Ambiguous(dbgInfo, msg);
- }
- if (JexlException.Assignment.class.equals(xclazz)) {
- throw new JexlException.Assignment(dbgInfo, msg);
}
- throw new JexlException.Parsing(dbgInfo, msg);
+ throw xjexl != null ? xjexl : new JexlException.Parsing(dbgInfo, msg);
}
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java b/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java
index ad3e08526..265a3070d 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java
@@ -26,7 +26,7 @@ public final class NumberParser {
/** The type literal value. */
private Number literal = null;
/** The expected class. */
- private Class> clazz = null;
+ private Class extends Number> clazz = null;
/** JEXL locale-neutral big decimal format. */
static final DecimalFormat BIGDF = new DecimalFormat("0.0b", new DecimalFormatSymbols(Locale.ENGLISH));
@@ -36,27 +36,25 @@ public String toString() {
return "NaN";
}
if (BigDecimal.class.equals(clazz)) {
- return BIGDF.format(literal);
+ return BIGDF.format(literal);
}
StringBuilder strb = new StringBuilder(literal.toString());
- if (clazz != null) {
- if (Float.class.equals(clazz)) {
- strb.append('f');
- } else if (Double.class.equals(clazz)) {
- strb.append('d');
- } else if (BigDecimal.class.equals(clazz)) {
- strb.append('b');
- } else if (BigInteger.class.equals(clazz)) {
- strb.append('h');
- } else if (Long.class.equals(clazz)) {
- strb.append('l');
- }
+ if (Float.class.equals(clazz)) {
+ strb.append('f');
+ } else if (Double.class.equals(clazz)) {
+ strb.append('d');
+ } else if (BigDecimal.class.equals(clazz)) {
+ strb.append('b');
+ } else if (BigInteger.class.equals(clazz)) {
+ strb.append('h');
+ } else if (Long.class.equals(clazz)) {
+ strb.append('l');
}
return strb.toString();
}
- Class> getLiteralClass() {
+ Class extends Number> getLiteralClass() {
return clazz;
}
@@ -87,7 +85,7 @@ static Number parseDouble(String s) {
*/
void setNatural(String s) {
Number result;
- Class> rclass;
+ Class extends Number> rclass;
// determine the base
final int base;
if (s.charAt(0) == '0') {
@@ -138,7 +136,7 @@ void setNatural(String s) {
*/
void setReal(String s) {
Number result;
- Class> rclass;
+ Class extends Number> rclass;
if ("#NaN".equals(s) || "NaN".equals(s)) {
result = Double.NaN;
rclass = Double.class;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index d320fc539..f1942f559 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -35,11 +35,10 @@ PARSER_BEGIN(Parser)
package org.apache.commons.jexl3.parser;
import java.util.Collections;
-import java.util.List;
import java.util.LinkedList;
-import java.io.Reader;
import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.internal.Scope;
@@ -47,23 +46,25 @@ public final class Parser extends JexlParser
{
private int loopCount = 0;
- public ASTJexlScript parse(JexlInfo info, String jexlSrc, Scope scope, boolean registers, boolean expr) {
+ public ASTJexlScript parse(JexlInfo info, JexlFeatures jexlFeatures, String jexlSrc, Scope scope) {
+ JexlFeatures previous = getFeatures();
try {
+ setFeatures(jexlFeatures);
// If registers are allowed, the default parser state has to be REGISTERS.
- if (registers || ALLOW_REGISTERS) {
+ if (jexlFeatures.supportsRegister()) {
token_source.defaultLexState = REGISTERS;
}
// lets do the 'Unique Init' in here to be safe - it's a pain to remember
source = jexlSrc;
pragmas = null;
- ReInit(new java.io.StringReader(jexlSrc));
frame = scope;
- ASTJexlScript script = expr? JexlExpression(scope) : JexlScript(scope) ;
- script.pragmas = pragmas != null
+ ReInit(new java.io.StringReader(jexlSrc));
+ ASTJexlScript script = jexlFeatures.supportsScript()? JexlScript(scope) : JexlExpression(scope);
+ script.jjtSetValue(info);
+ script.setPragmas(pragmas != null
? Collections.unmodifiableMap(pragmas)
- : Collections.emptyMap();
+ : Collections.emptyMap());
pragmas = null;
- script.jjtSetValue(info);
return script;
} catch (TokenMgrError xtme) {
throw new JexlException.Tokenization(info, xtme).clean();
@@ -73,6 +74,7 @@ public final class Parser extends JexlParser
source = null;
frame = null;
token_source.defaultLexState = DEFAULT;
+ setFeatures(previous);
}
}
}
@@ -151,6 +153,7 @@ TOKEN_MGR_DECLS : {
<*> TOKEN : { /* CONDITIONALS */
< QMARK : "?" >
| < ELVIS : "?:" >
+ | < NULLP : "??" >
| < AND : "&&" | "and" >
| < OR : "||" | "or" >
}
@@ -203,6 +206,11 @@ TOKEN_MGR_DECLS : {
< NAN_LITERAL : "NaN" >
}
+<*> TOKEN : /* ANNOTATION */
+{
+ < ANNOTATION: "@" ( [ "0"-"9", "a"-"z", "A"-"Z", "_", "$" ])+ >
+}
+
TOKEN : /* IDENTIFIERS */
{
< DOT_IDENTIFIER: ( [ "0"-"9", "a"-"z", "A"-"Z", "_", "$", "@" ])+ > { popDot(); } /* Revert state to default. */
@@ -278,11 +286,25 @@ ASTJexlScript JexlExpression(Scope frame) #JexlScript : {
}
}
+void Annotation() #Annotation :
+{
+ Token t;
+}
+{
+ t= (LOOKAHEAD() Arguments() )? { jjtThis.setName(t.image); }
+}
+
+void AnnotatedStatement() #AnnotatedStatement() : {}
+{
+ (LOOKAHEAD() Annotation())+ (LOOKAHEAD() Var() | LOOKAHEAD(1) Block() | Expression())
+}
+
void Statement() #void : {}
{
- | LOOKAHEAD( Expression() ) Block() // to diasmbiguate the set literals
- | LOOKAHEAD( Statement() ) Block() // to diasmbiguate the set literals
+ | LOOKAHEAD() AnnotatedStatement()
+ | LOOKAHEAD( Expression() ) Block() // to disambiguate the set literals
+ | LOOKAHEAD( Statement() ) Block() // to disambiguate the set literals
| IfStatement()
| ForeachStatement()
| WhileStatement()
@@ -302,13 +324,15 @@ void Block() #Block : {}
void ExpressionStatement() #void : {}
{
- Expression() (LOOKAHEAD(1) Expression() #Ambiguous())* (LOOKAHEAD(2) )?
+ Expression() (LOOKAHEAD(1) Expression() #Ambiguous())* (LOOKAHEAD(1) )?
}
void IfStatement() : {}
{
- Expression() (LOOKAHEAD(1) Block() | Statement()) ( LOOKAHEAD(1) (LOOKAHEAD(1) Block() | Statement()) )?
+ Expression() (LOOKAHEAD(1) Block() | Statement())
+ ( LOOKAHEAD(2) Expression() (LOOKAHEAD(1) Block() | Statement()) )*
+ ( LOOKAHEAD(1) (LOOKAHEAD(1) Block() | Statement()) )?
}
@@ -319,7 +343,7 @@ void WhileStatement() : {}
void ReturnStatement() : {}
{
- Expression()
+ ExpressionStatement()
}
void Continue() #Continue : {}
@@ -354,7 +378,7 @@ void DeclareVar() #Var :
Token t;
}
{
- t= { declareVariable(jjtThis, t.image); }
+ t= { declareVariable(jjtThis, t); }
}
void Pragma() #void :
@@ -435,6 +459,8 @@ void ConditionalExpression() #void : {}
Expression() Expression() #TernaryNode(3)
|
Expression() #TernaryNode(2)
+ |
+ Expression() #NullpNode(2)
)?
}
@@ -731,7 +757,7 @@ void Parameter() #void :
Token t;
}
{
- t= { declareParameter(t.image); }
+ t= { declareParameter(t); }
}
void Parameters() #void : {}
@@ -841,7 +867,3 @@ void ValueExpression() #void : {}
( PrimaryExpression() ( LOOKAHEAD(2) MemberExpression() )*) #Reference(>1)
}
-
-
-
-
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
index a84329c98..03047382c 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
@@ -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.");
}
/**
@@ -64,6 +64,8 @@ protected final Object visit(ASTAmbiguous node, Object data) {
protected abstract Object visit(ASTTernaryNode node, Object data);
+ protected abstract Object visit(ASTNullpNode node, Object data);
+
protected abstract Object visit(ASTOrNode node, Object data);
protected abstract Object visit(ASTAndNode node, Object data);
@@ -177,4 +179,8 @@ protected final Object visit(ASTAmbiguous node, Object data) {
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);
}
diff --git a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java
index e8f760b68..c64c5732b 100644
--- a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java
+++ b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngineFactory.java
@@ -37,7 +37,7 @@
* See
* Java Scripting API
* Javadoc.
- *
+ *
* @since 2.0
*/
public class JexlScriptEngineFactory implements ScriptEngineFactory {
@@ -49,7 +49,7 @@ public String getEngineName() {
@Override
public String getEngineVersion() {
- return "3.0"; // ensure this is updated if function changes are made to this class
+ return "3.2"; // ensure this is updated if function changes are made to this class
}
@Override
@@ -59,7 +59,7 @@ public String getLanguageName() {
@Override
public String getLanguageVersion() {
- return "3.0"; // TODO this should be derived from the actual version
+ return "3.2"; // TODO this should be derived from the actual version
}
@Override
diff --git a/src/site/site.xml b/src/site/site.xml
index c9c77e944..79e3609a0 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -25,9 +25,9 @@
-
-
-
+
+
+
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 69b1d4fcb..f530ffef4 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -25,7 +25,161 @@
Commons Developers
-
+
+
+ Allow range subexpression as an array property assignment identifier
+
+
+ Intermittent ambiguous method invocation when processing assignOverload
+
+
+ Engine in strict mode fails to fail on unsolvable variables or properties
+
+
+ Webapp classloader memory leaks
+
+
+ Allow restricting available features in Script/Expressions
+
+
+ NPE when script containing string interpolation executed in multiple threads
+
+
+ Unable to invoke a call operator using antish style variable resoltion
+
+
+ Restrict getLiteralClass to a Number for NumberLiterals
+
+
+ Ability to restrict usage of certain names when declaring local variables
+
+
+ Support CharSequence in size(), empty() and contains() operators
+
+
+ Extend application of operators startsWith and endsWith from String to CharSequence types
+
+
+ Syntax for accessing List elements is not mentioned in docs
+
+
+ List literal is not mentioned in docs
+
+
+ JexlScriptEngineFactory.getEngineVersion() should return actual version
+
+
+ add ?? operator support
+
+
+ Incorrect invoking methods with ObjectContext
+
+
+ The ability to overload call() operator in customized JexlArithmetic implementation
+
+
+ Restrict usage of assignment statements in JexlExpression
+
+
+
+
+ The ability to declare indexed property getter/setter in customised JexlArithmetic implementation
+
+
+ Sporadic undefined property error caused by NPE at MapGetExecutor.tryInvoke()
+
+
+ Blacklist by default in sandbox
+
+
+ Interpreter.getAttribute() raises exception in non-strict mode when cached property resolver is used
+
+
+ Improve parsing concurrency in multithreaded environment
+
+
+ JexlEngine.createInfo() is redundantly called when debug and caching is enabled leading to sub-optimal performance
+
+
+ Redundant call of fillInStackTrace() in JexlEngine.createInfo() ?
+
+
+ rename JexlBuilder.loader(Charset arg) to JexlBuilder.charset(Charset arg)
+
+
+ Add callable method to JexlExpression interface
+
+
+ The way to cancel script execution with an error
+
+
+ Unsolvable function/method '<?>.<null>(...)'
+
+
+ Documentation typos/inconsistencies
+
+
+ Inconsistent error handling
+
+
+ testCallableCancel() test hangs sporadically
+
+
+ testCancelForever() is not terminated properly
+
+
+ Script is not interrupted by a method call throwing Exception
+
+
+ JexlArithmetic.options() diverts Interpreter to use default implementation of JexlArithmetic instead of custom one
+
+
+ Detect invalid assignment operator usage with non-assignable l-value during script parsing
+
+
+ Allow Interpreter to use live values from JexlEngine.Option interface implemented by JexlContext
+
+
+ JxltEngine Template does not expose pragmas
+
+
+ Add annotations
+
+
+ Script execution hangs while calling method with one argument without parameter
+
+
+ Support for AtomicBoolean in logical expressions
+
+
+ allow synchronization on iterableValue in foreach statement
+
+
+ InterruptedException is swallowed in function call in silent and non-strict mode
+
+
+ Invalid return type when expected result is null
+
+
+ Jexl3 unsolvable property exception when using enum
+
+
+ local function within context is not resolved if function resolver class without namespace is specified
+
+
+ Possible bug in Interpreter.isCancelled()
+
+
+ Possible bug in JexlArithmetic.isFloatingPointNumber()
+
+
+ Jexl Syntax doc does not mention 'continue' and 'break' operators
+
+
+ Performance regression in arithmetic operations compared to JEXL 2.1
+
+
+
dot-ed identifiers parsing failure
@@ -174,7 +328,7 @@
Syntactically enforce that expressions do not contain statements: POTENTIAL EXPRESSION BREAK!
(ie an expression is not a script and can NOT use 'if','for'... and blocks)
-
+
Added syntactic shortcut to create parametric scripts (script source creates an anonymous function)
diff --git a/src/site/xdoc/download_jexl.xml b/src/site/xdoc/download_jexl.xml
index 5cb37d899..e4899c1ac 100644
--- a/src/site/xdoc/download_jexl.xml
+++ b/src/site/xdoc/download_jexl.xml
@@ -32,14 +32,16 @@ limitations under the License.
| - commons.componentid (required, alphabetic, lower case) |
| - commons.release.version (required) |
| - commons.release.name (required) |
- | - commons.binary.suffix (optional) |
+ | - commons.binary.suffix (optional) |
| (defaults to "-bin", set to "" for pre-maven2 releases) |
| - commons.release.desc (optional) |
+ | - commons.release.subdir (optional) |
| |
| - commons.release.2/3.version (conditional) |
| - commons.release.2/3.name (conditional) |
| - commons.release.2/3.binary.suffix (optional) |
| - commons.release.2/3.desc (optional) |
+ | - commons.release.2/3.subdir (optional) |
| |
| 3) Example Properties |
| (commons.release.name inherited by parent: |
@@ -109,32 +111,32 @@ limitations under the License.
-
+
diff --git a/src/site/xdoc/reference/examples.xml b/src/site/xdoc/reference/examples.xml
index 644d9b95c..16303a3f9 100644
--- a/src/site/xdoc/reference/examples.xml
+++ b/src/site/xdoc/reference/examples.xml
@@ -32,7 +32,7 @@
- You can find two sample programs in JEXL's CVS repository:
+ You can find two sample programs in JEXL's source repository:
Using arrays
Accessing Properties and invoking methods
diff --git a/src/site/xdoc/reference/jsr223.xml b/src/site/xdoc/reference/jsr223.xml
index 1a4c72d72..2dd31d745 100644
--- a/src/site/xdoc/reference/jsr223.xml
+++ b/src/site/xdoc/reference/jsr223.xml
@@ -54,7 +54,7 @@
The binary release includes a command-line application which can be used to exercise the JSR-223 script engine.
For example:
- java -cp commons-jexl-3.0.jar;commons-logging-1.2.jar
+ java -cp commons-jexl-3.1.jar;commons-logging-1.2.jar
org.apache.commons.jexl3.scripting.Main script.jexl
If a single argument is provided, then that is assumed to be the name of a script file;
otherwise, the application prompts for script input to be evaluated.
diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml
index 5fee85e92..1a8b695d6 100644
--- a/src/site/xdoc/reference/syntax.xml
+++ b/src/site/xdoc/reference/syntax.xml
@@ -17,668 +17,896 @@
-->
-
- Apache Commons JEXL Syntax
-
+
+ Apache Commons JEXL Syntax
+
-
-
-
- For more technical information about the JEXL Grammar, you can find the
- JavaCC grammar for JEXL
- here: Parser.jjt
-
-
-
-
- Item Description
-
- Comments
-
- Specified using ##
or //
and extend to the end of line, e.g.
- ## This is a comment
- Also specified using //
, e.g.
- // This is a comment
- Multiple lines comments are specified using /*...*/
, e.g.
- /* This is a
- multi-line comment */
-
-
-
- Identifiers / variables
-
- Must start with a-z
, A-Z
, _
or $
.
- Can then be followed by 0-9
, a-z
, A-Z
, _
or $
.
- e.g.
-
- Valid: var1
,_a99
,$1
- Invalid: 9v
,!a99
,1$
-
+
+
- Variable names are case-sensitive , e.g. var1
and Var1
are different variables.
+ This reference is split up into the following sections:
+
+
+ Language Elements
+
+
+ Literals
+
+
+ Functions
+
+
+ Operators
+
+
+ Access
+
+
+ Conditional Statements
+
+
- NOTE: JEXL does not support variables with hyphens in them, e.g.
- commons-logging // invalid variable name (hyphenated) is not a valid variable, but instead is treated as a
- subtraction of the variable logging
from the variable commons
+ For more technical information about the JEXL Grammar, you can find the
+ JavaCC grammar for JEXL
+ here: Parser.jjt
-
- JEXL also supports ant-style
variables, the following is a valid variable name:
- my.dotted.var
-
-
- N.B. the following keywords are reserved, and cannot be used as a variable name or property when using the dot operator:
- or and eq ne lt gt le ge div mod not null true false new var return
- For example, the following is invalid:
- my.new.dotted.var // invalid ('new' is keyword)
- In such cases, quoted identifiers or the [ ] operator can be used, for example:
- my.'new'.dotted.var
- my['new'].dotted.var
-
-
-
-
- Scripts
-
-
- A script in JEXL is made up of zero or more statements. Scripts can be read from a String, File or URL.
-
-
- They can be created with named parameters which allow a later evaluation to be performed with arguments.
-
-
- A script returns the last expression evaluated by default.
-
-
- Using the return
keyword, a script will return the expression that follows (or null).
-
-
-
-
- Local variables
- Can be defined using the var
keyword; their identifying rules are the same as contextual variables.
-
- Basic declaration: var x;
- Declaration with assignment: var theAnswer = 42;
- Invalid declaration: var x.y;
-
- Their scope is the entire script scope and they take precedence in resolution over contextual variables.
- When scripts are created with named parameters, those behave as local variables.
- Local variables can not use ant-style
naming, only one identifier.
-
-
-
- Statements
-
- A statement can be the empty statement, the semicolon (;
) , block, assignment or an expression.
- Statements are optionally terminated with a semicolon.
-
-
-
- Block
-
- A block is simply multiple statements inside curly braces ({, }
).
-
-
-
- Assignment
-
- Assigns the value of a variable (my.var = 'a value'
) using a
- JexlContext
as initial resolver. Both beans and ant-ish
- variables assignment are supported.
-
-
-
- Method calls
-
- Calls a method of an object, e.g.
- "hello world".hashCode() will call the hashCode
method
- of the "hello world"
String.
- In case of multiple arguments and overloading, JEXL will make the best effort to find
- the most appropriate non ambiguous method to call.
-
-
-
- #pragma
-
- Declares a pragma, a method to communicate information from a script to its execution environment, e.g.
- #pragma execution.option 42 will declare a pragma named execution.option
with
- a value of 42
.
- Pragma keys can be identifiers or antish names, pragma values can be literals (boolean, integer,
- real, string, null, NaN) and antish names
-
-
-
-
-
-
- Item Description
-
- Integer Literals
- 1 or more digits from 0
to 9
, eg 42
.
-
-
-
- Float Literals
-
- 1 or more digits from 0
to 9
, followed
- by a decimal point and then one or more digits from
- 0
to 9
,
- optionally followed by f
or F
,
- eg 42.0
or 42.0f
.
-
-
-
- Long Literals
- 1 or more digits from 0
to 9
suffixed with l
or L
- , eg 42l
.
-
-
-
- Double Literals
-
- 1 or more digits from 0
to 9
, followed
- by a decimal point and then one or more digits from
- 0
to 9
suffixed with d
or D
- , eg 42.0d
.
-
-
-
- Big Integer Literals
- 1 or more digits from 0
to 9
suffixed with h
or H
- (for Huge ala OGNL, "does not interfere with hexa-decimal digits"), eg 42h
.
-
-
-
- Big Decimal Literals
-
- 1 or more digits from 0
to 9
, followed
- by a decimal point and then one or more digits from
- 0
to 9
suffixed with b
or B
)
- , eg 42.0b
.
-
-
-
- Natural literals - octal and hex support
-
- Natural numbers (i.e. Integer, Long, BigInteger) can also be expressed as octal or hexadecimal using the same format as Java.
- i.e. prefix the number with 0
for octal, and prefix with 0x
or 0X
for hexadecimal.
- For example 010
or 0x10
.
-
-
-
- Real literals - exponent support
-
- Real numbers (i.e. Float, Double, BigDecimal) can also be expressed using standard Java exponent notation.
- i.e. suffix the number with e
or E
followed by the sign +
or -
- followed by one or more decimal digits.
- For example 42.0E-1D
or 42.0E+3B
.
-
-
-
- String literals
-
- Can start and end with either '
or "
delimiters, e.g.
- "Hello world" and
- 'Hello world' are equivalent.
- The escape character is \
(backslash); it only escapes the string delimiter
-
-
-
- Multiline format literals
-
- Start and end with `
delimiter - back-quote -, e.g. `Hello world`
- The escape character is \
(backslash); it only escapes the string delimiter.
- These format literals can span multiple lines and allow Unified JEXL expressions (JSTL like expressions)
- to be interpolated. If a variable user
valued JEXL
is present in the environment - whether
- as a local or global variable -, the format `Hello ${user}` will evaluate as Hello JEXL .
-
-
-
- Boolean literals
-
- The literals true
and false
can be used, e.g.
- val1 == true
-
-
-
- Null literal
-
- The null value is represented as in java using the literal null
, e.g.
- val1 == null
-
-
-
- Array literal
-
- A [
followed by one or more expressions separated by ,
and ending
- with ]
, e.g.
- [ 1, 2, "three" ]
- This syntax creates an Object[]
.
-
- JEXL will attempt to strongly type the array; if all entries are of the same class or if all
- entries are Number instance, the array literal will be an MyClass[]
in the former
- case, a Number[]
in the latter case.
- Furthermore, if all entries in the array literal are of the same class
- and that class has an equivalent primitive type, the array returned will be a primitive array. e.g.
- [1, 2, 3]
will be interpreted as int[]
.
-
-
-
- Set literal
-
- A {
followed by one or more expressions separated by ,
and ending
- with }
, e.g.
- { "one" , 2, "more"}
- This syntax creates a HashSet<Object>
.
-
-
-
- Map literal
-
- A {
followed by one or more sets of key : value
pairs separated by ,
and ending
- with }
, e.g.
- { "one" : 1, "two" : 2, "three" : 3, "more": "many more" }
- This syntax creates a HashMap<Object,Object>
.
-
-
-
-
-
-
- Function Description
-
- empty
-
- Evaluates whether an expression if 'empty'.
- This is true when the argument is:
-
- null
- An instance of class C and the derived JexlArithmetic overloads a method 'public boolean empty(C arg)'
- that returns true when the argument is considered empty
- An empty string
- An array of length zero
- A collection of size zero
- An empty map
- Defining a method 'public boolean isEmpty()'
- that returns true when the instance is considered empty
-
- This is false in other cases (besides errors).
- empty(arg)
-
-
-
- size
-
- Evaluates the 'size' of an expression.
- This returns:
-
- 0 if the argument is null
- The result of calling a method from a derived JexlArithmetic overload 'public int size(C arg)',
- C being the class of the argument
- Length of an array
- Length of a string
- Size of a Collection
- Size of a Map
- The result of calling a method 'public int size()' defined by the argument class
-
- This returns 0 in other cases (besides errors).
- size("Hello") returns 5.
-
-
-
- new
-
- Creates a new instance using a fully-qualified class name or Class:
- new("java.lang.Double", 10) returns 10.0.
- Note that the first argument of new
can be a variable or any
- expression evaluating as a String or Class; the rest of the arguments are used
- as arguments to the constructor for the class considered.
- In case of multiple constructors, JEXL will make the best effort to find
- the most appropriate non ambiguous constructor to call.
-
-
-
- ns:function
-
- A JexlEngine
can register objects or classes used as function namespaces.
- This can allow expressions like:
- math:cosinus(23.0)
-
-
-
- function
-
- Defines a function within the script, usually associated with a local variable assignment.
- var fun = function(x, y) { x + y }
- Calling a function follows the usual convention:
- fun(17, 25)
- Note that functions can use local variables and parameters from their declaring script.
- Those variables values are bound in the function environment at definition time.
- var t = 20; var s = function(x, y) {x + y + t}; t = 54; s(15, 7)
- The function closure hoists 't' when defined; the result of the evaluation will
- lead to 15 +7 + 20 = 42
.
-
-
-
-
-
-
- Operator Description
-
- Boolean and
-
- The usual &&
operator can be used as well as the word and
, e.g.
- cond1 and cond2 and
- cond1 && cond2 are equivalent
-
-
-
- Boolean or
-
- The usual ||
operator can be used as well as the word or
, e.g.
- cond1 or cond2 and
- cond1 || cond2 are equivalent
-
-
-
- Boolean not
-
- The usual !
operator can be used as well as the word not
, e.g.
- !cond1 and
- not cond1 are equivalent
-
-
-
- Bitwise and
-
- The usual &
operator is used, e.g.
- 33 & 4 , 0010 0001 & 0000 0100 = 0.
-
-
-
- Bitwise or
-
- The usual |
operator is used, e.g.
- 33 | 4 , 0010 0001 | 0000 0100 = 0010 0101 = 37.
-
-
-
- Bitwise xor
-
- The usual ^
operator is used, e.g.
- 33 ^ 4 , 0010 0001 ^ 0000 0100 = 0010 0100 = 37.
-
-
-
- Bitwise complement
-
- The usual ~
operator is used, e.g.
- ~33 , ~0010 0001 = 1101 1110 = -34.
-
-
-
- Ternary conditional ?:
-
- The usual ternary conditional operator condition ? if_true : if_false
operator can be
- used as well as the abbreviation value ?: if_false
which returns the value
if
- its evaluation is defined, non-null and non-false, e.g.
- val1 ? val1 : val2 and
- val1 ?: val2 are equivalent.
-
- NOTE: The condition will evaluate to false
when it
- refers to an undefined variable or null
for all JexlEngine
- flag combinations. This allows explicit syntactic leniency and treats the condition
- 'if undefined or null or false' the same way in all cases.
-
-
-
-
- Equality
-
- The usual ==
operator can be used as well as the abbreviation eq
.
- For example
- val1 == val2 and
- val1 eq val2 are equivalent.
-
-
- null
is only ever equal to null, that is if you compare null
- to any non-null value, the result is false.
-
- Equality uses the java equals
method
-
-
-
-
- Inequality
-
- The usual !=
operator can be used as well as the abbreviation ne
.
- For example
- val1 != val2 and
- val1 ne val2 are equivalent.
-
-
-
- Less Than
-
- The usual <
operator can be used as well as the abbreviation lt
.
- For example
- val1 < val2 and
- val1 lt val2 are equivalent.
-
-
-
- Less Than Or Equal To
-
- The usual <=
operator can be used as well as the abbreviation le
.
- For example
- val1 <= val2 and
- val1 le val2 are equivalent.
-
-
-
- Greater Than
-
- The usual >
operator can be used as well as the abbreviation gt
.
- For example
- val1 > val2 and
- val1 gt val2 are equivalent.
-
-
-
- Greater Than Or Equal To
-
- The usual >=
operator can be used as well as the abbreviation ge
.
- For example
- val1 >= val2 and
- val1 ge val2 are equivalent.
-
-
-
- In or Match=~
-
- The syntactically Perl inspired =~
operator can be used to check that a string
matches
- a regular expression (expressed either a Java String or a java.util.regex.Pattern).
- For example
- "abcdef" =~ "abc.*
returns true
.
- It also checks whether any collection, set or map (on keys) contains a value or not; in that case, it behaves
- as an "in" operator. Note that it also applies to arrays as well as "duck-typed" collection, ie classes exposing a "contains"
- method.
- "a" =~ ["a","b","c","d","e",f"]
returns true
.
-
-
-
- Not-In or Not-Match!~
-
- The syntactically Perl inspired !~
operator can be used to check that a string
does not
- match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
- For example
- "abcdef" !~ "abc.*
returns false
.
- It also checks whether any collection, set or map (on keys) does not contain a value; in that case, it behaves
- as "not in" operator.
- "a" !~ ["a","b","c","d","e",f"]
returns true
.
- Note that through duck-typing, user classes exposing a public 'contains' method will allow their instances
- to behave has right-hand-size operands of this operator.
-
-
-
- Starts With=^
-
- The syntactically CSS3 inspired =^
operator is a short-hand for the 'startsWith' method.
- For example,
- "abcdef" ^= "abc"
returns true
.
- Note that through duck-typing, user classes exposing a public 'startsWith' method will allow their instances
- to behave has left-hand-size operands of this operator.
-
-
-
- Not Starts With!^
-
- This is the negation of the 'starts with' operator.
- a ^! "abc"
is equivalent to !(a ^= "abc")
-
-
-
- Ends With=$
- The syntactically CSS3 inspired =$
operator is a short-hand for the 'endsWith' method.
- For example,
- "abcdef" $= "def"
returns true
.
- Note that through duck-typing, user classes exposing an 'endsWith' method will allow their instances
- to behave has left-hand-size operands of this operator.
-
-
-
- Not Ends With!^
-
- This is the negation of the 'ends with' operator.
- a $! "abc"
is equivalent to !(a $= "abc")
-
-
-
- Range..
-
- This operator creates a 'range' of values (in the form of a java iterable).
- For example,
- for(var x: 1 .. 3) {}
will loop 3 times with the value of 'x' being 1, 2 and 3.
-
-
-
- Addition
-
- The usual +
operator is used.
- For example
- val1 + val2
-
-
-
- Subtraction
-
- The usual -
operator is used.
- For example
- val1 - val2
-
-
-
- Multiplication
-
- The usual *
operator is used.
- For example
- val1 * val2
-
-
-
- Division
-
- The usual /
operator is used, or one can use the div
operator.
- For example
- val1 / val2
- or
- val1 div val2
-
-
-
- Modulus (or remainder)
-
- The %
operator is used. An alternative is the mod
- operator.
- For example
- 5 mod 2 gives 1 and is equivalent to 5 % 2
-
-
-
- Negation
-
- The unary -
operator is used.
- For example
- -12
-
-
-
- Array access
-
- Array elements may be accessed using either square brackets or a dotted numeral, e.g.
- arr1[0] and arr1.0 are equivalent
-
-
-
- HashMap access
-
- Map elements are accessed using square brackets, e.g.
- map[0]; map['name']; map[var];
- Note that map['7'] and map[7] refer to different elements.
- Map elements with a numeric key may also be accessed using a dotted numeral, e.g.
- map[0] and map.0 are equivalent.
-
-
-
-
-
-
- Operator Description
-
- if
-
- Classic, if/else statement, e.g.
- if ((x * 2) == 5) {
- y = 1;
-} else {
- y = 2;
-}
-
-
-
- for
-
- Loop through items of an Array, Collection, Map, Iterator or Enumeration, e.g.
- for(item : list) {
- x = x + item;
-}
- Where item
and list
are variables.
- The JEXL 1.1 syntax using foreach(item in list)
is now unsupported .
-
-
-
- while
-
- Loop until a condition is satisfied, e.g.
- while (x lt 10) {
- x = x + 2;
-}
-
-
-
-
+
+
+
+
+ Item
+ Description
+
+
+ Comments
+
+ Specified using ##
or //
and extend to the end of line, e.g.
+ ## This is a comment
+ Also specified using //
, e.g.
+ // This is a comment
+ Multiple lines comments are specified using /*...*/
, e.g.
+ /* This is a
+ multi-line comment */
+
+
+
+ Identifiers / variables
+
+ Must start with a-z
, A-Z
, _
or $
.
+ Can then be followed by 0-9
, a-z
, A-Z
, _
or $
.
+ e.g.
+
+ Valid: var1
,_a99
,$1
+ Invalid: 9v
,!a99
,1$
+
+
+ Variable names are case-sensitive , e.g. var1
and Var1
are different variables.
+
+
+ NOTE: JEXL does not support variables with hyphens in them, e.g.
+ commons-logging // invalid variable name (hyphenated)
is not a valid variable, but instead is treated as a
+ subtraction of the variable logging
from the variable commons
+
+
+ JEXL also supports ant-style
variables, the following is a valid variable name:
+ my.dotted.var
+
+
+ N.B. the following keywords are reserved, and cannot be used as a variable name or property when using the dot operator:
+ or and eq ne lt gt le ge div mod not null true false new var break continue return
+ For example, the following is invalid:
+ my.new.dotted.var // invalid ('new' is keyword)
+ In such cases, quoted identifiers or the [ ] operator can be used, for example:
+ my.'new'.dotted.var
+ my['new'].dotted.var
+
+
+
+
+ Scripts
+
+
+ A script in JEXL is made up of zero or more statements. Scripts can include one or more pragmas.
+
+
+ Scripts can be read from a String, File or URL.
+
+
+ They can be created with named parameters which allow a later evaluation to be performed with arguments.
+
+
+ By default a script returns the value of the last evaluated statement.
+
+
+ Using the return
keyword, a script will return the expression that follows (or null).
+
+
+
+
+ Local variables
+ Can be defined using the var
keyword; their identifying rules are the same as contextual variables.
+
+ Basic declaration: var x;
+ Declaration with assignment: var theAnswer = 42;
+ Invalid declaration: var x.y;
+
+ Their scope is the entire script scope and they take precedence in resolution over contextual variables.
+ When scripts are created with named parameters, those behave as local variables.
+ Local variables can not use ant-style
naming, only one identifier.
+
+
+
+ Statements
+
+ A statement can be the empty statement, the semicolon (;
) , block, conditional, assignment or an expression.
+ Statements are optionally terminated with a semicolon.
+ A single statement or a statement block can be annotated.
+
+
+
+ Block
+
+ A block is simply multiple statements inside curly braces ({, }
).
+
+
+
+ Assignment
+
+ Assigns the value of a variable (my.var = 'a value'
) using a
+ JexlContext
as initial resolver. Both beans and ant-ish
+ variables assignment are supported.
+
+
+
+ Expression
+
+ An expression can be the literal, variable, access operator, function definition, function call, method call or
+ an evaluation operator.
+
+
+
+ Function definition
+
+ Defines a function within the script, usually associated with a local variable assignment.
+ var fun = function(x, y) { x + y }
+ The following syntax is also supported
+ var fun = (x, y) -> { x + y }
+ Calling a function follows the usual convention:
+ fun(17, 25)
+ Note that functions can use local variables and parameters from their declaring script.
+ Those variables values are bound in the function environment at definition time.
+ var t = 20; var s = function(x, y) {x + y + t}; t = 54; s(15, 7)
+ The function closure hoists 't' when defined; the result of the evaluation will
+ lead to 15 +7 + 20 = 42
.
+
+
+
+ Method call
+
+ Calls a method of an object, e.g.
+ "hello world".hashCode()
will call the hashCode
method
+ of the "hello world"
String.
+ In case of multiple arguments and overloading, JEXL will make the best effort to find
+ the most appropriate non ambiguous method to call.
+
+
+
+ Access Operator
+
+ Allows to evaluate a property of an object, a value of the collection or an array
+ by using either square brackets or a dotted numeral, e.g.
+ foo.bar
will access the bar
property
+ of the foo
Object.
+ arr1[0]
will access the first element of the
+ of the arr1
array.
+ Access operators can be overloaded in JexlArithmetic
, so that
+ the operator behaviour will differ depending on the type of the operator arguments
+
+
+
+ Evaluation Operator
+
+ Performs computational, logical or comparative action between one, two or three arguments
+ whose values are expressions, e.g.
+ 40 + 2
will call the add
operator
+ between two integer literals.
+ All operators, except when stated otherwise, can be overloaded in JexlArithmetic
, so that the action taken
+ will differ depending on the type of the operator arguments
+
+
+
+ #pragma
+
+ Declares a pragma, a method to communicate information from a script to its execution environment, e.g.
+ #pragma execution.option 42
will declare a pragma named execution.option
with
+ a value of 42
.
+ Pragma keys can be identifiers or antish names, pragma values can be literals (boolean, integer,
+ real, string, null, NaN) and antish names
+
+
+
+ @annotation
+
+ Annotations in JEXL are 'meta-statements'; they allow to wrap the execution of the JEXL statement in a user provided
+ caller; typical example would be: @synchronized(x) x.someMethod();
+
+ Annotations may be declared with zero or more parameters;
+ @lenient x.someMethod();
+ @synchronized(x) x.someMethod();
+ @parallel(pool, 8) x.someMethod();
+
+
+ They also can be chained as in:
+ @lenient @silent x.someMethod();
+
+
+ Annotation processing is implemented by providing a JexlContext.AnnotationProcessor; its processAnnotation
+ method will call the annotated statement encapsulated in a Callable. Annotation arguments are evaluated
+ and passed as arguments to processAnnotation.
+
+
+
+
+
+
+
+
+ Item
+ Description
+
+
+ Integer Literals
+ 1 or more digits from 0
to 9
, eg 42
.
+
+
+
+ Float Literals
+
+ 1 or more digits from 0
to 9
, followed
+ by a decimal point and then one or more digits from
+ 0
to 9
,
+ optionally followed by f
or F
,
+ eg 42.0
or 42.0f
.
+
+
+
+ Long Literals
+ 1 or more digits from 0
to 9
suffixed with l
or L
+ , eg 42l
.
+
+
+
+ Double Literals
+
+ 1 or more digits from 0
to 9
, followed
+ by a decimal point and then one or more digits from
+ 0
to 9
suffixed with d
or D
+ , eg 42.0d
.
+
+
+
+ Big Integer Literals
+ 1 or more digits from 0
to 9
suffixed with h
or H
+ (for Huge ala OGNL, "does not interfere with hexa-decimal digits"), eg 42h
.
+
+
+
+ Big Decimal Literals
+
+ 1 or more digits from 0
to 9
, followed
+ by a decimal point and then one or more digits from
+ 0
to 9
suffixed with b
or B
)
+ , eg 42.0b
.
+
+
+
+ Natural literals - octal and hex support
+
+ Natural numbers (i.e. Integer, Long, BigInteger) can also be expressed as octal or hexadecimal using the same format as Java.
+ i.e. prefix the number with 0
for octal, and prefix with 0x
or 0X
for hexadecimal.
+ For example 010
or 0x10
.
+
+
+
+ Real literals - exponent support
+
+ Real numbers (i.e. Float, Double, BigDecimal) can also be expressed using standard Java exponent notation.
+ i.e. suffix the number with e
or E
followed by the sign +
or -
+ followed by one or more decimal digits.
+ For example 42.0E-1D
or 42.0E+3B
.
+
+
+
+ String literals
+
+ Can start and end with either '
or "
delimiters, e.g.
+ "Hello world"
and
+ 'Hello world'
are equivalent.
+ The escape character is \
(backslash); it only escapes the string delimiter
+
+
+
+ Multiline format literals
+
+ Start and end with `
delimiter - back-quote -, e.g. `Hello world`
+ The escape character is \
(backslash); it only escapes the string delimiter.
+ These format literals can span multiple lines and allow Unified JEXL expressions (JSTL like expressions)
+ to be interpolated. If a variable user
valued JEXL
is present in the environment - whether
+ as a local or global variable -, the format `Hello ${user}`
will evaluate as Hello JEXL
.
+
+
+
+ Boolean literals
+
+ The literals true
and false
can be used, e.g.
+ val1 == true
+
+
+
+ Null literal
+
+ The null value is represented as in java using the literal null
, e.g.
+ val1 == null
+
+
+
+ Array literal
+
+ A [
followed by one or more expressions separated by ,
and ending
+ with ]
, e.g.
+ [ 1, 2, "three" ]
+ This syntax creates an Object[]
.
+
+ JEXL will attempt to strongly type the array; if all entries are of the same class or if all
+ entries are Number instance, the array literal will be an MyClass[]
in the former
+ case, a Number[]
in the latter case.
+ Furthermore, if all entries in the array literal are of the same class
+ and that class has an equivalent primitive type, the array returned will be a primitive array. e.g.
+ [1, 2, 3]
will be interpreted as int[]
.
+
+
+
+ List literal
+
+ A [
followed by one or more expressions separated by ,
and ending
+ with ,...]
, e.g.
+ [ 1, 2, "three",...]
+ This syntax creates an ArrayList<Object>
.
+
+
+
+ Set literal
+
+ A {
followed by one or more expressions separated by ,
and ending
+ with }
, e.g.
+ { "one" , 2, "more"}
+ This syntax creates a HashSet<Object>
.
+
+
+
+ Map literal
+
+ A {
followed by one or more sets of key : value
pairs separated by ,
and ending
+ with }
, e.g.
+ { "one" : 1, "two" : 2, "three" : 3, "more": "many more" }
+ This syntax creates a HashMap<Object,Object>
.
+
+
+
+
+ Range literal
+
+ A value followed by ..
and ending with other value, e.g.
+ 1 .. 42
+ This syntax creates a 'range' object in the form of a java iterable which can be used in for statement, e.g.
+ for (i : 1..42) a = a + b[i]
+
+
+
+
+
+
+
+
+ Function
+ Description
+
+
+ empty
+
+ Evaluates whether an expression if 'empty'.
+ This is true when the argument is:
+
+
+ null
+
+ An instance of class C and the derived JexlArithmetic overloads a method 'public boolean empty(C arg)'
+ that returns true when the argument is considered empty
+ An empty string
+ An array of length zero
+ A collection of size zero
+ An empty map
+ Defining a method 'public boolean isEmpty()'
+ that returns true when the instance is considered empty
+
+ This is false in other cases (besides errors).
+ empty(arg)
+
+
+
+ size
+
+ Evaluates the 'size' of an expression.
+ This returns:
+
+ 0 if the argument is null
+ The result of calling a method from a derived JexlArithmetic overload 'public int size(C arg)',
+ C being the class of the argument
+ Length of an array
+ Length of a string
+ Size of a Collection
+ Size of a Map
+ The result of calling a method 'public int size()' defined by the argument class
+
+ This returns 0 in other cases (besides errors).
+ size("Hello")
returns 5.
+
+
+
+ new
+
+ Creates a new instance using a fully-qualified class name or Class:
+ new("java.lang.Double", 10)
returns 10.0.
+ Note that the first argument of new
can be a variable or any
+ expression evaluating as a String or Class; the rest of the arguments are used
+ as arguments to the constructor for the class considered.
+ In case of multiple constructors, JEXL will make the best effort to find
+ the most appropriate non ambiguous constructor to call.
+
+
+
+ Top level function
+
+ Top level function is a function which can be invoked without specifying a namespace.
+ Top level function can be defined by the function definition method inside the script
+ A JexlContext
can define methods which can be invoked as top level functions.
+ This can allow expressions like:
+ string(23.0)
+ Another way to define top level function is to register to JexlEngine
objects or classes
+ with null namespace.
+
+
+
+
+ ns:function
+
+ A JexlEngine
can register objects or classes used as function namespaces.
+ This can allow expressions like:
+ math:cosinus(23.0)
+
+
+
+
+
+
+
+ Operator
+ Description
+
+
+ Boolean and
+
+ The usual &&
operator can be used as well as the word and
, e.g.
+ cond1 and cond2
and
+ cond1 && cond2
are equivalent.
+ Note that this operator can not be overloaded
+
+
+
+ Boolean or
+
+ The usual ||
operator can be used as well as the word or
, e.g.
+ cond1 or cond2
and
+ cond1 || cond2
are equivalent.
+ Note that this operator can not be overloaded
+
+
+
+ Boolean not
+
+ The usual !
operator can be used as well as the word not
, e.g.
+ !cond1
and
+ not cond1
are equivalent.
+ Note that this operator can not be overloaded
+
+
+
+ Bitwise and
+
+ The usual &
operator is used, e.g.
+ 33 & 4
, 0010 0001 & 0000 0100 = 0.
+
+
+
+ Bitwise or
+
+ The usual |
operator is used, e.g.
+ 33 | 4
, 0010 0001 | 0000 0100 = 0010 0101 = 37.
+
+
+
+ Bitwise xor
+
+ The usual ^
operator is used, e.g.
+ 33 ^ 4
, 0010 0001 ^ 0000 0100 = 0010 0100 = 37.
+
+
+
+ Bitwise complement
+
+ The usual ~
operator is used, e.g.
+ ~33
, ~0010 0001 = 1101 1110 = -34.
+
+
+
+ Ternary conditional ?:
+
+ The usual ternary conditional operator condition ? if_true : if_false
operator can be
+ used as well as the abbreviation value ?: if_false
which returns the value
if
+ its evaluation is defined, non-null and non-false, e.g.
+ val1 ? val1 : val2
and
+ val1 ?: val2
are equivalent.
+
+ NOTE: The condition will evaluate to false
when it
+ refers to an undefined variable or null
for all JexlEngine
+ flag combinations. This allows explicit syntactic leniency and treats the condition
+ 'if undefined or null or false' the same way in all cases.
+
+ Note that this operator can not be overloaded
+
+
+
+ Null coalescing operator ??
+
+ The null coalescing operator returns the result of its first operand if it is defined and is not null.
+ When x
andy
are null or undefined,
+ x ?? 'unknown or null x'
evaluates as 'unknown or null x'
+ y ?? "default"
evaluates as "default"
.
+
+
+ When var x = 42
and var y = "forty-two"
,x??"other"
+ evaluates as 42
and y??"other"
evaluates as "forty-two"
.
+
+
+ NOTE: this operator does not behave like the ternary conditional since it
+ does not coerce the first argument to a boolean to evaluate the condition.
+ When var x = false
and var y = 0
,x??true
+ evaluates as false
and y??1
evaluates as 0
.
+
+ Note that this operator can not be overloaded
+
+
+
+ Equality
+
+ The usual ==
operator can be used as well as the abbreviation eq
.
+ For example
+ val1 == val2
and
+ val1 eq val2
are equivalent.
+
+
+ null
is only ever equal to null, that is if you compare null
+ to any non-null value, the result is false.
+
+ Equality uses the java equals
method
+
+
+
+
+ Inequality
+
+ The usual !=
operator can be used as well as the abbreviation ne
.
+ For example
+ val1 != val2
and
+ val1 ne val2
are equivalent.
+
+
+
+ Less Than
+
+ The usual <
operator can be used as well as the abbreviation lt
.
+ For example
+ val1 < val2
and
+ val1 lt val2
are equivalent.
+
+
+
+ Less Than Or Equal To
+
+ The usual <=
operator can be used as well as the abbreviation le
.
+ For example
+ val1 <= val2
and
+ val1 le val2
are equivalent.
+
+
+
+ Greater Than
+
+ The usual >
operator can be used as well as the abbreviation gt
.
+ For example
+ val1 > val2
and
+ val1 gt val2
are equivalent.
+
+
+
+ Greater Than Or Equal To
+
+ The usual >=
operator can be used as well as the abbreviation ge
.
+ For example
+ val1 >= val2
and
+ val1 ge val2
are equivalent.
+
+
+
+ In or Match=~
+
+ The syntactically Perl inspired =~
operator can be used to check that a string
matches
+ a regular expression (expressed either a Java String or a java.util.regex.Pattern).
+ For example
+ "abcdef" =~ "abc.*
returns true
.
+ It also checks whether any collection, set or map (on keys) contains a value or not; in that case, it behaves
+ as an "in" operator.
+ Note that arrays and user classes exposing a public 'contains' method will allow their instances
+ to behave as right-hand side operands of this operator.
+ "a" =~ ["a","b","c","d","e",f"]
returns true
.
+
+
+
+ Not-In or Not-Match!~
+
+ The syntactically Perl inspired !~
operator can be used to check that a string
does not
+ match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
+ For example
+ "abcdef" !~ "abc.*
returns false
.
+ It also checks whether any collection, set or map (on keys) does not contain a value; in that case, it behaves
+ as "not in" operator.
+ Note that arrays and user classes exposing a public 'contains' method will allow their instances
+ to behave as right-hand side operands of this operator.
+ "a" !~ ["a","b","c","d","e",f"]
returns true
.
+
+
+
+ Starts With=^
+
+ The =^
operator is a short-hand for the 'startsWith' method.
+ For example, "abcdef" =^ "abc"
returns true
.
+ Note that through duck-typing, user classes exposing a public 'startsWith' method will allow their instances
+ to behave as left-hand side operands of this operator.
+
+
+
+ Not Starts With!^
+
+ This is the negation of the 'starts with' operator.
+ a !^ "abc"
is equivalent to !(a =^ "abc")
+
+
+
+ Ends With=$
+ The =$
operator is a short-hand for the 'endsWith' method.
+ For example, "abcdef" =$ "def"
returns true
.
+ Note that through duck-typing, user classes exposing an 'endsWith' method will allow their instances
+ to behave as left-hand side operands of this operator.
+
+
+
+ Not Ends With!$
+
+ This is the negation of the 'ends with' operator.
+ a !$ "abc"
is equivalent to !(a =$ "abc")
+
+
+
+ Addition
+
+ The usual +
operator is used.
+ For example
+ val1 + val2
+
+
+
+ Subtraction
+
+ The usual -
operator is used.
+ For example
+ val1 - val2
+
+
+
+ Multiplication
+
+ The usual *
operator is used.
+ For example
+ val1 * val2
+
+
+
+ Division
+
+ The usual /
operator is used, or one can use the div
operator.
+ For example
+ val1 / val2
+ or
+ val1 div val2
+
+
+
+ Modulus (or remainder)
+
+ The %
operator is used. An alternative is the mod
+ operator.
+ For example
+ 5 mod 2
gives 1 and is equivalent to 5 % 2
+
+
+
+ Side-effect operators
+
+ Some operators exist in side-effect forms.
+ Their default behavior is to execute the operator and assign the left-hand side with the result.
+ For instance a += 2
is equivalent to a = a + 2
+ The list of operators is:
+
+ +=
+ -=
+ *=
+ /=
+ %=
+ &=
+ |=
+ ^=
+
+
+
+
+ Negation
+
+ The unary -
operator is used.
+ For example
+ -12
+
+
+
+
+
+
+
+ Operator
+ Description
+
+
+ Array access
+
+ Array elements may be accessed using either square brackets or a dotted numeral, e.g.
+ arr1[0]
and arr1.0
are equivalent
+
+
+
+ List access
+
+ List elements may be accessed using either square brackets or a dotted numeral, e.g.
+ list[0]
and list.0
are equivalent
+
+
+
+ Map access
+
+ Map elements are accessed using square brackets, e.g.
+ map[0]; map['name']; map[var];
+ Note that map['7']
and map[7]
refer to different elements.
+ Map elements with a numeric key may also be accessed using a dotted numeral, e.g.
+ map[0]
and map.0
are equivalent.
+ Note that map.1
and map.01
refer to different elements,
+ while map.1
and map[01]
are equivalent.
+
+
+
+ JavaBean property access
+
+ Properties of JavaBean objects that define appropriate getter methods can be accessed
+ using either square brackets or a dotted numeral, e.g.
+ foo['bar']
and foo.bar
are equivalent.
+ The appropriate Foo.getBar()
method will be called.
+ Note that both foo.Bar
and foo.bar
can be used
+
+
+
+ Indexed JavaBean property access
+
+ Indexed properties of JavaBean objects that define appropriate getter methods can be accessed
+ using either square brackets or a dotted numeral, e.g.
+ x.attribute['name']
and x.attribute.name
are equivalent.
+ The appropriate Foo.getAttribute(String index)
method will be called
+
+
+
+ Public field access
+
+ Public fields of java objects can be accessed using either square brackets or a dotted numeral, e.g.
+ foo['bar']
and foo.bar
are equivalent.
+
+
+
+ Duck-typed collection property access
+
+ Properties of Java classes that define public Object get(String name)
method can be accessed
+ using either square brackets or a dotted numeral, e.g.
+ foo['bar']
and foo.bar
are equivalent.
+ The appropriate Foo.get(String index)
method will be called with the argument of "bar"
String
+
+
+
+
+
+
+
+ Statement
+ Description
+
+
+ if
+
+ Classic, if/else statement, e.g.
+ if ((x * 2) == 5) {
+ y = 1;
+ } else {
+ y = 2;
+ }
+
+
+
+ for
+
+ Loop through items of an Array, Collection, Map, Iterator or Enumeration, e.g.
+ for (item : list) {
+ x = x + item;
+ }
+ Where item
is a context variable.
+ The following syntax is also supported:
+ for (var item : list) {
+ x = x + item;
+ }
+
+ Where item
is a local variable.
+ Note that item
variable is accessible after loop evaluation
+ The JEXL 1.1 syntax using foreach(item in list)
is now unsupported .
+
+
+
+ while
+
+ Loop until a condition is satisfied, e.g.
+ while (x lt 10) {
+ x = x + 2;
+ }
+
+
+
+ continue
+
+ Within loops (while/for), allows to skip to the next iteration.
+
+
+
+ break
+
+ Allows to break from a loop (while/for) inconditionally.
+
+
+
+
-
+
diff --git a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
new file mode 100644
index 000000000..fc03571be
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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 names = new TreeSet();
+
+ @Override
+ public Object processAnnotation(String name, Object[] args, Callable 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 getNames() {
+ return names;
+ }
+ }
+
+ public class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
+ @Override
+ public Object processAnnotation(String name, Object[] args, Callable statement) throws Exception {
+ // transient side effect for strict
+ if ("strict".equals(name)) {
+ boolean s = (Boolean) args[0];
+ boolean b = this.isStrict();
+ setStrict(s);
+ Object r = statement.call();
+ setStrict(b);
+ return r;
+ }
+ // transient side effect for silent
+ if ("silent".equals(name)) {
+ if (args == null || args.length == 0) {
+ boolean b = this.isSilent();
+ try {
+ return statement.call();
+ } catch(JexlException xjexl) {
+ return null;
+ } finally {
+ setSilent(b);
+ }
+ } else {
+ boolean s = (Boolean) args[0];
+ boolean b = this.isSilent();
+ setSilent(s);
+ Object r = statement.call();
+ setSilent(b);
+ return r;
+ }
+ }
+ // durable side effect for scale
+ if ("scale".equals(name)) {
+ this.setMathScale((Integer) args[0]);
+ return statement.call();
+ }
+ return statement.call();
+ }
+ }
+
+ @Test
+ public void testVarStmt() throws Exception {
+ OptAnnotationContext jc = new OptAnnotationContext();
+ JexlEngine jexl = new JexlBuilder().strict(true).silent(false).create();
+ jc.setOptions(jexl);
+ JexlScript e;
+ Object r;
+ e = jexl.createScript("(s, v)->{ @strict(s) @silent(v) var x = y ; 42; }");
+
+ // wont make an error
+ try {
+ r = e.execute(jc, false, true);
+ Assert.assertEquals(42, r);
+ } catch (JexlException.Variable xjexl) {
+ Assert.fail("should not have thrown");
+ }
+
+ r = null;
+ // will make an error and throw
+ try {
+ r = e.execute(jc, true, false);
+ Assert.fail("should have thrown");
+ } catch (JexlException.Variable xjexl) {
+ Assert.assertNull(r);
+ }
+
+ r = null;
+ // will make an error and will not throw but result is null
+ try {
+ r = e.execute(jc, true, true);
+ Assert.assertEquals(null, r);
+ } catch (JexlException.Variable xjexl) {
+ Assert.fail("should not have thrown");
+ }
+
+ r = null;
+ // will not make an error and will not throw
+ try {
+ r = e.execute(jc, false, false);
+ Assert.assertEquals(42, r);
+ } catch (JexlException.Variable xjexl) {
+ Assert.fail("should not have thrown");
+ }
+ //Assert.assertEquals(42, r);
+ Assert.assertTrue(jc.isStrict());
+ e = jexl.createScript("@scale(5) 42;");
+ r = e.execute(jc);
+ Assert.assertEquals(42, r);
+ Assert.assertTrue(jc.isStrict());
+ Assert.assertEquals(5, jc.getArithmeticMathScale());
+ }
+
+ @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 {
+ testError(true);
+ testError(false);
+ }
+
+ private void testError(boolean silent) throws Exception {
+ CaptureLog log = new CaptureLog();
+ AnnotationContext jc = new AnnotationContext();
+ 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);
+ 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().logger(log).strict(true).silent(silent).create();
+ JexlScript e = jexl.createScript("@unknown('42') { return 42; }");
+ try {
+ Object r = e.execute(jc);
+ 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/AntishCallTest.java b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
new file mode 100644
index 000000000..5e9814933
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.Map;
+import java.util.TreeMap;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test cases for calling antish variables as method names (JEXL-240);
+ * Also tests that a class instance is a functor that invokes the constructor when called.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class AntishCallTest extends JexlTestCase {
+
+ public AntishCallTest() {
+ super("AntishCallTest");
+ }
+
+ /**
+ * Wraps a class.
+ */
+ public class ClassReference {
+ final Class> clazz;
+ ClassReference(Class> c) {
+ this.clazz = c;
+ }
+ }
+
+ /**
+ * Considers any call using a class reference as functor as a call to its constructor.
+ * Note that before 3.2, a class was not considered a functor.
+ * @param clazz the class we seek to instantiate
+ * @param args the constructor arguments
+ * @return an instance if that was possible
+ */
+ public static Object callConstructor(JexlEngine engine, ClassReference ref, Object... args) {
+ return callConstructor(engine, ref.clazz, args);
+ }
+ public static Object callConstructor(JexlEngine engine, Class> clazz, Object... args) {
+ if (clazz == null || clazz.isPrimitive() || clazz.isInterface()
+ || clazz.isMemberClass() || clazz.isAnnotation() || clazz.isArray()) {
+ throw new ArithmeticException("not a constructible object");
+ }
+ JexlEngine jexl = engine;
+ if (jexl == null) {
+ jexl = JexlEngine.getThreadEngine();
+ if (jexl == null) {
+ throw new ArithmeticException("no engine to solve constructor");
+ }
+ }
+ return jexl.newInstance(clazz, args);
+ }
+
+ /**
+ * An arithmetic that considers class objects as callable.
+ */
+ public class CallSupportArithmetic extends JexlArithmetic {
+ public CallSupportArithmetic(boolean strict) {
+ super(strict);
+ }
+
+ public Object call(ClassReference clazz, Object... args) {
+ return callConstructor(null, clazz, args);
+ }
+
+ public Object call(Class> clazz, Object... args) {
+ return callConstructor(null, clazz, args);
+ }
+ }
+
+ /**
+ * A context that considers class references as callable.
+ */
+ public static class CallSupportContext extends MapContext {
+ CallSupportContext(Map map) {
+ super(map);
+ }
+ private JexlEngine engine;
+
+ @Override public Object get(String str) {
+ if (!super.has(str)) {
+ try {
+ return CallSupportContext.class.getClassLoader().loadClass(str);
+ } catch(Exception xany) {
+ return null;
+ }
+ }
+ return super.get(str);
+ }
+
+ @Override public boolean has(String str) {
+ if (!super.has(str)){
+ try {
+ return CallSupportContext.class.getClassLoader().loadClass(str) != null;
+ } catch(Exception xany) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ CallSupportContext engine(JexlEngine j) {
+ engine = j;
+ return this;
+ }
+
+ public Object call(ClassReference clazz, Object... args) {
+ return callConstructor(engine, clazz, args);
+ }
+
+ public Object call(Class> clazz, Object... args) {
+ return callConstructor(engine, clazz, args);
+ }
+ }
+
+ @Test
+ public void testAntishContextVar() throws Exception {
+ JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).create();
+ Map lmap = new TreeMap();
+ JexlContext jc = new CallSupportContext(lmap).engine(jexl);
+ runTestCall(jexl, jc);
+ lmap.put("java.math.BigInteger", new ClassReference(java.math.BigInteger.class));
+ runTestCall(jexl, jc);
+ lmap.remove("java.math.BigInteger");
+ runTestCall(jexl, jc);
+ }
+
+ @Test
+ public void testAntishArithmetic() throws Exception {
+ CallSupportArithmetic ja = new CallSupportArithmetic(true);
+ JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).arithmetic(ja).create();
+ Map lmap = new TreeMap();
+ JexlContext jc = new MapContext(lmap);
+ lmap.put("java.math.BigInteger", java.math.BigInteger.class);
+ runTestCall(jexl, jc);
+ lmap.put("java.math.BigInteger", new ClassReference(java.math.BigInteger.class));
+ runTestCall(jexl, jc);
+ lmap.remove("java.math.BigInteger");
+ try {
+ runTestCall(jexl, jc);
+ Assert.fail("should have failed");
+ } catch(JexlException xjexl) {
+ //
+ }
+ }
+
+ void runTestCall(JexlEngine jexl, JexlContext jc) throws Exception {
+ JexlScript check1 = jexl.createScript("var x = java.math.BigInteger; x('1234')");
+ JexlScript check2 = jexl.createScript("java.math.BigInteger('4321')");
+
+ Object o1 = check1.execute(jc);
+ Assert.assertEquals("Result is not 1234", new java.math.BigInteger("1234"), o1);
+
+ Object o2 = check2.execute(jc);
+ Assert.assertEquals("Result is not 4321", new java.math.BigInteger("4321"), o2);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
index 961ac9f52..0a03a0b40 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
@@ -71,6 +71,12 @@ public void testRegexp() throws Exception {
asserter.assertExpression("str !~ match", Boolean.FALSE);
asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
+ asserter.setVariable("match", new StringBuilder("abc.*"));
+ asserter.setVariable("nomatch", new StringBuilder(".*123"));
+ asserter.assertExpression("str =~ match", Boolean.TRUE);
+ asserter.assertExpression("str !~ match", Boolean.FALSE);
+ asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
+ asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
asserter.setVariable("match", java.util.regex.Pattern.compile("abc.*"));
asserter.setVariable("nomatch", java.util.regex.Pattern.compile(".*123"));
asserter.assertExpression("str =~ match", Boolean.TRUE);
@@ -94,6 +100,16 @@ public void testStartsEndsWithString() throws Exception {
asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
}
+ @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");
@@ -104,6 +120,36 @@ public void testNotStartsEndsWithString() throws Exception {
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);
+ }
+
+ @Test
+ public void testStartsEndsWithStringBuilder() throws Exception {
+ asserter.setVariable("x", new StringBuilder("foobar"));
+ asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
+ asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
+ asserter.setVariable("x", new StringBuilder("barfoo"));
+ asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
+ asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
+ }
+
+ @Test
+ public void testNotStartsEndsWithStringBuilder() throws Exception {
+ asserter.setVariable("x", new StringBuilder("foobar"));
+ asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
+ asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
+ asserter.setVariable("x", new StringBuilder("barfoo"));
+ asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
+ asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
+ }
+
public static class MatchingContainer {
private final Set values;
@@ -349,6 +395,10 @@ public Object arraySet(Date date, String identifier, Object value) throws Except
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 {
@@ -368,6 +418,34 @@ public String format(Number number, String fmt) {
}
}
+ @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();
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 7b7802ba5..f521088cc 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -25,6 +25,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.Assert;
@@ -304,6 +305,7 @@ public void testBigExponentLiterals() throws Exception {
}
// JEXL-24: doubles with exponent
+ @Test
public void test2DoubleLiterals() throws Exception {
JexlEvalContext ctxt = new JexlEvalContext();
ctxt.setStrictArithmetic(true);
@@ -428,6 +430,7 @@ public void testMultClass() throws Exception {
Assert.assertEquals(java.math.BigDecimal.class, r1.getClass());
}
+ @Test
public void testDivClass() throws Exception {
JexlEngine jexl = new JexlBuilder().create();
JexlContext jc = new MapContext();
@@ -439,6 +442,7 @@ public void testDivClass() throws Exception {
Assert.assertEquals(java.math.BigDecimal.class, r1.getClass());
}
+ @Test
public void testPlusClass() throws Exception {
JexlEngine jexl = new JexlBuilder().create();
JexlContext jc = new MapContext();
@@ -450,6 +454,7 @@ public void testPlusClass() throws Exception {
Assert.assertEquals(java.math.BigDecimal.class, r1.getClass());
}
+ @Test
public void testMinusClass() throws Exception {
JexlEngine jexl = new JexlBuilder().create();
JexlContext jc = new MapContext();
@@ -537,6 +542,22 @@ public void testAddWithStringsStrict() throws Exception {
Assert.assertEquals("1.21.2", result);
}
+ @Test
+ public void testOption() throws Exception {
+ Map vars = new HashMap();
+ JexlEvalContext context = new JexlEvalContext(vars);
+ JexlScript script = JEXL.createScript("0 + '1.2' ");
+ Object result;
+
+ context.setStrictArithmetic(true);
+ result = script.execute(context);
+ Assert.assertEquals("01.2", result);
+
+ context.setStrictArithmetic(false);
+ result = script.execute(context);
+ Assert.assertEquals(1.2d, (Double) result, EPSILON);
+ }
+
@Test
public void testIsFloatingPointPattern() throws Exception {
JexlArithmetic ja = new JexlArithmetic(true);
@@ -548,11 +569,24 @@ public void testIsFloatingPointPattern() throws Exception {
Assert.assertFalse(ja.isFloatingPointNumber("+10.2a+34"));
Assert.assertFalse(ja.isFloatingPointNumber("0"));
Assert.assertFalse(ja.isFloatingPointNumber("1"));
+ Assert.assertFalse(ja.isFloatingPointNumber("12A"));
+ Assert.assertFalse(ja.isFloatingPointNumber("2F3"));
+ Assert.assertFalse(ja.isFloatingPointNumber("23"));
+ Assert.assertFalse(ja.isFloatingPointNumber("+3"));
+ Assert.assertFalse(ja.isFloatingPointNumber("+34"));
+ Assert.assertFalse(ja.isFloatingPointNumber("+3-4"));
+ Assert.assertFalse(ja.isFloatingPointNumber("+3.-4"));
+ Assert.assertFalse(ja.isFloatingPointNumber("3ee4"));
Assert.assertTrue(ja.isFloatingPointNumber("0."));
Assert.assertTrue(ja.isFloatingPointNumber("1."));
Assert.assertTrue(ja.isFloatingPointNumber("1.2"));
Assert.assertTrue(ja.isFloatingPointNumber("1.2e3"));
+ Assert.assertTrue(ja.isFloatingPointNumber("2e3"));
+ Assert.assertTrue(ja.isFloatingPointNumber("+2e-3"));
+ Assert.assertTrue(ja.isFloatingPointNumber("+23E-34"));
+ Assert.assertTrue(ja.isFloatingPointNumber("+23.E-34"));
+ Assert.assertTrue(ja.isFloatingPointNumber("-23.4E+45"));
Assert.assertTrue(ja.isFloatingPointNumber("1.2e34"));
Assert.assertTrue(ja.isFloatingPointNumber("10.2e34"));
Assert.assertTrue(ja.isFloatingPointNumber("+10.2e34"));
@@ -599,8 +633,12 @@ public void testEmpty() throws Exception {
"var x = []; return empty(x);", true,
"var x = [1, 2]; return empty(x);", false,
"var x = ['a', 'b']; return empty(x);", false,
+ "var x = [...]; return empty(x);", true,
+ "var x = [1, 2,...]; return empty(x);", false,
"var x = {:}; return empty(x);", true,
- "var x = {1:'A', 2:'B'}; return empty(x);", false
+ "var x = {1:'A', 2:'B'}; return empty(x);", false,
+ "var x = {}; return empty(x);", true,
+ "var x = {'A','B'}; return empty(x);", false
};
JexlEngine jexl = new JexlBuilder().create();
JexlContext jc = new EmptyTestContext();
@@ -1297,4 +1335,74 @@ public void testCoerceBigDecimal() throws Exception {
Assert.assertEquals(BigDecimal.valueOf(0.), ja.toBigDecimal(false));
}
+ @Test
+ public void testAtomicBoolean() throws Exception {
+ // in a condition
+ JexlScript e = JEXL.createScript("if (x) 1 else 2;", "x");
+ JexlContext jc = new MapContext();
+ AtomicBoolean ab = new AtomicBoolean(false);
+ Object o;
+ o = e.execute(jc, ab);
+ Assert.assertEquals("Result is not 2", new Integer(2), o);
+ ab.set(true);
+ o = e.execute(jc, ab);
+ Assert.assertEquals("Result is not 1", new Integer(1), o);
+ // in a binary logical op
+ e = JEXL.createScript("x && y", "x", "y");
+ ab.set(true);
+ o = e.execute(jc, ab, Boolean.FALSE);
+ Assert.assertFalse((Boolean) o);
+ ab.set(true);
+ o = e.execute(jc, ab, Boolean.TRUE);
+ Assert.assertTrue((Boolean) o);
+ ab.set(false);
+ o = e.execute(jc, ab, Boolean.FALSE);
+ Assert.assertFalse((Boolean) o);
+ ab.set(false);
+ o = e.execute(jc, ab, Boolean.FALSE);
+ Assert.assertFalse((Boolean) o);
+ // in arithmetic op
+ e = JEXL.createScript("x + y", "x", "y");
+ ab.set(true);
+ o = e.execute(jc, ab, 10);
+ Assert.assertEquals(11, o);
+ o = e.execute(jc, 10, ab);
+ Assert.assertEquals(11, o);
+ o = e.execute(jc, ab, 10.d);
+ Assert.assertEquals(11.d, (Double) o, EPSILON);
+ o = e.execute(jc, 10.d, ab);
+ Assert.assertEquals(11.d, (Double) o, EPSILON);
+
+ BigInteger bi10 = BigInteger.TEN;
+ ab.set(false);
+ o = e.execute(jc, ab, bi10);
+ Assert.assertEquals(bi10, o);
+ o = e.execute(jc, bi10, ab);
+ Assert.assertEquals(bi10, o);
+
+ BigDecimal bd10 = BigDecimal.TEN;
+ ab.set(false);
+ o = e.execute(jc, ab, bd10);
+ Assert.assertEquals(bd10, o);
+ o = e.execute(jc, bd10, ab);
+ Assert.assertEquals(bd10, o);
+
+ // in a (the) monadic op
+ e = JEXL.createScript("!x", "x");
+ ab.set(true);
+ o = e.execute(jc, ab);
+ Assert.assertFalse((Boolean) o);
+ ab.set(false);
+ o = e.execute(jc, ab);
+ Assert.assertTrue((Boolean) o);
+
+ // in a (the) monadic op
+ e = JEXL.createScript("-x", "x");
+ ab.set(true);
+ o = e.execute(jc, ab);
+ Assert.assertFalse((Boolean) o);
+ ab.set(false);
+ o = e.execute(jc, ab);
+ Assert.assertTrue((Boolean) o);
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java b/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java
index fa1fadb15..a42b330f1 100644
--- a/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java
@@ -59,6 +59,7 @@ public void setUp() {
/**
* test simple array access
*/
+ @Test
public void testArrayAccess() throws Exception {
/*
@@ -190,6 +191,7 @@ public void testDoubleMaps() throws Exception {
asserter.assertExpression("foo.0.1", "three");
}
+ @Test
public void testArrayProperty() throws Exception {
Foo foo = new Foo();
@@ -202,6 +204,7 @@ public void testArrayProperty() throws Exception {
}
// This is JEXL-26
+ @Test
public void testArrayAndDottedConflict() throws Exception {
Object[] objects = new Object[] {"an", "array", new Long(0)};
asserter.setStrict(false);
@@ -216,6 +219,7 @@ public void testArrayAndDottedConflict() throws Exception {
asserter.assertExpression("base.objects.1.status", null);
}
+ @Test
public void testArrayIdentifierParsing() throws Exception {
Map map = new HashMap();
map.put("00200", -42.42d);
@@ -228,6 +232,7 @@ public void testArrayIdentifierParsing() throws Exception {
asserter.assertExpression("objects.200", 42.42d);
}
+ @Test
public void testArrayMethods() throws Exception {
Object[] objects = new Object[] {"an", "array", new Long(0)};
@@ -239,6 +244,7 @@ public void testArrayMethods() throws Exception {
asserter.assertExpression("objects[1]", "dion");
}
+ @Test
public void testArrayArray() throws Exception {
Integer i42 = Integer.valueOf(42);
Integer i43 = Integer.valueOf(43);
@@ -304,4 +310,23 @@ public void testArrayArray() throws Exception {
asserter.assertExpression("foo[zero][zero][two]", s42);
}
}
+
+ public static class Sample {
+ private int[] array;
+ public void setFoo(int[] a) {
+ array = a;
+ }
+ public int[] getFoo() {
+ return array;
+ }
+ }
+ @Test
+ public void testArrayGetSet() throws Exception {
+ Sample bar = new Sample();
+ bar.setFoo(new int[]{24});
+ asserter.setVariable("bar", bar);
+ asserter.assertExpression("bar.foo[0]", 24);
+ asserter.assertExpression("bar.foo = []", new int[0]);
+ //asserter.assertExpression("bar.foo[0]", 42);
+ }
}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/jexl3/AssignTest.java b/src/test/java/org/apache/commons/jexl3/AssignTest.java
index bb8584930..7b13ed34d 100644
--- a/src/test/java/org/apache/commons/jexl3/AssignTest.java
+++ b/src/test/java/org/apache/commons/jexl3/AssignTest.java
@@ -109,6 +109,7 @@ public void testBeanish() throws Exception {
Assert.assertEquals("Result is not 10", new Integer(10), o);
}
+ @Test
public void testAmbiguous() throws Exception {
JexlExpression assign = JEXL.createExpression("froboz.nosuchbean = 10");
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 000000000..2ae0aa655
--- /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 captured = new ArrayList();
+
+ 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/ContextNamespaceTest.java b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
index 9a354d3a9..48f41d610 100644
--- a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
@@ -71,4 +71,52 @@ public void testThreadedContext() throws Exception {
Assert.assertEquals(186., result);
}
+ public static class Vat {
+ private double vat;
+
+ Vat(double vat) {
+ this.vat = vat;
+ }
+
+ public double getVAT() {
+ return vat;
+ }
+
+ public void setVAT(double vat) {
+ this.vat = vat;
+ }
+
+ public double getvat() {
+ throw new UnsupportedOperationException("no way");
+ }
+
+ public void setvat(double vat) {
+ throw new UnsupportedOperationException("no way");
+ }
+ }
+
+ @Test
+ public void testObjectContext() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).silent(false).create();
+ Vat vat = new Vat(18.6);
+ ObjectContext ctxt = new ObjectContext(jexl, vat);
+ Assert.assertEquals(18.6d, (Double) ctxt.get("VAT"), 0.0001d);
+ ctxt.set("VAT", 20.0d);
+ Assert.assertEquals(20.0d, (Double) ctxt.get("VAT"), 0.0001d);
+
+ try {
+ ctxt.get("vat");
+ Assert.fail("should have failed");
+ } catch(JexlException.Property xprop) {
+ //
+ }
+
+ try {
+ ctxt.set("vat", 33.0d);
+ Assert.fail("should have failed");
+ } catch(JexlException.Property xprop) {
+ //
+ }
+ }
+
}
diff --git a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
index 003aa1353..b565effad 100644
--- a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
@@ -31,15 +31,30 @@ public ExceptionTest() {
}
public static class ThrowNPE {
- public String method() {
+ boolean doThrow = false;
+ public String npe() {
throw new NullPointerException("ThrowNPE");
}
+
+ public void setFail(boolean f) {
+ doThrow = f;
+ if (f) {
+ throw new NullPointerException("ThrowNPE/set");
+ }
+ }
+
+ public boolean getFail() {
+ if (doThrow) {
+ throw new NullPointerException("ThrowNPE/get");
+ }
+ return doThrow;
+ }
}
@Test
public void testWrappedEx() throws Exception {
JexlEngine jexl = new Engine();
- JexlExpression e = jexl.createExpression("method()");
+ JexlExpression e = jexl.createExpression("npe()");
JexlContext jc = new ObjectContext(jexl, new ThrowNPE());
try {
e.evaluate(jc);
@@ -50,6 +65,60 @@ public void testWrappedEx() throws Exception {
}
}
+ @Test
+ public void testWrappedExmore() throws Exception {
+ JexlEngine jexl = new Engine();
+ ThrowNPE npe = new ThrowNPE();
+ try {
+ Object r = jexl.getProperty(npe, "foo");
+ Assert.fail("Should have thrown JexlException.Property");
+ } catch (JexlException.Property xany) {
+ Throwable xth = xany.getCause();
+ Assert.assertNull(xth);
+ }
+ try {
+ jexl.setProperty(npe, "foo", 42);
+ Assert.fail("Should have thrown JexlException.Property");
+ } catch (JexlException.Property xany) {
+ Throwable xth = xany.getCause();
+ Assert.assertNull(xth);
+ }
+
+ boolean b = (Boolean) jexl.getProperty(npe, "fail");
+ Assert.assertFalse(b);
+ try {
+ jexl.setProperty(npe, "fail", false);
+ jexl.setProperty(npe, "fail", true);
+ Assert.fail("Should have thrown JexlException.Property");
+ } catch (JexlException.Property xany) {
+ Throwable xth = xany.getCause();
+ Assert.assertEquals(NullPointerException.class, xth.getClass());
+ }
+ try {
+ jexl.getProperty(npe, "fail");
+ Assert.fail("Should have thrown JexlException.Property");
+ } catch (JexlException.Property xany) {
+ Throwable xth = xany.getCause();
+ Assert.assertEquals(NullPointerException.class, xth.getClass());
+ }
+
+ try {
+ jexl.invokeMethod(npe, "foo", 42);
+ Assert.fail("Should have thrown JexlException.Method");
+ } catch (JexlException.Method xany) {
+ Throwable xth = xany.getCause();
+ Assert.assertNull(xth);
+ }
+ try {
+ jexl.invokeMethod(npe, "npe");
+ Assert.fail("Should have thrown NullPointerException");
+ } catch (JexlException.Method xany) {
+ Throwable xth = xany.getCause();
+ Assert.assertEquals(NullPointerException.class, xth.getClass());
+ }
+ }
+
+
// Unknown vars and properties versus null operands
@Test
public void testEx() throws Exception {
@@ -160,4 +229,52 @@ public void testExMethod() throws Exception {
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/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
new file mode 100644
index 000000000..3a64362c4
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for blocks
+ * @since 1.1
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class FeaturesTest extends JexlTestCase {
+ private final JexlEngine jexl = new JexlBuilder().create();
+ /**
+ * Create the test
+ */
+ public FeaturesTest() {
+ super("BlockTest");
+ }
+
+ /**
+ * Checks that the script is valid with all features on then verifies it
+ * throws a feature exception with the given set (in features param).
+ * @param features
+ * @param scripts
+ * @throws Exception
+ */
+ private void checkFeature(JexlFeatures features, String[] scripts) throws Exception {
+ for (String script : scripts) {
+ JexlScript ctl = JEXL.createScript(script);
+ Assert.assertNotNull(ctl);
+ try {
+ JexlScript e = jexl.createScript(features, null, script, null);
+ Assert.fail("should fail parse: " + script);
+ } catch (JexlException.Feature xfeature) {
+ String msg = xfeature.getMessage();
+ Assert.assertNotNull(msg);
+ } catch (JexlException.Parsing xparsing) {
+ String msg = xparsing.getMessage();
+ Assert.assertNotNull(msg);
+ }
+ }
+ }
+
+ private void assertOk(JexlFeatures features, String[] scripts) {
+ for(String str : scripts) {
+ try {
+ JexlScript e = jexl.createScript(str);
+ } catch (JexlException.Feature xfeature) {
+ Assert.fail(str + " :: should not fail parse: " + xfeature.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testNoScript() throws Exception {
+ JexlFeatures f = new JexlFeatures().script(false);
+ String[] scripts = new String[]{
+ "if (false) { block(); }",
+ "{ noway(); }",
+ "while(true);",
+ "for(var i : {0 .. 10}) { bar(i); }"
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testNoLoop() throws Exception {
+ JexlFeatures f = new JexlFeatures().loops(false);
+ String[] scripts = new String[]{
+ "while(true);",
+ "for(var i : {0 .. 10}) { bar(i); }"
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testNoLambda() throws Exception {
+ JexlFeatures f = new JexlFeatures().lambda(false);
+ String[] scripts = new String[]{
+ "var x = ()->{ return 0 };",
+ "()->{ return 0 };",
+ "(x, y)->{ return 0 };",
+ "function() { return 0 };",
+ "function(x, y) { return 0 };",
+ "if (false) { (function(x, y) { return x + y })(3, 4) }"
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testNoNew() throws Exception {
+ JexlFeatures f = new JexlFeatures().newInstance(false);
+ String[] scripts = new String[]{
+ "return new(clazz);",
+ "new('java.math.BigDecimal', 12) + 1"
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testNoSideEffects() throws Exception {
+ JexlFeatures f = new JexlFeatures().sideEffect(false);
+ String[] scripts = new String[]{
+ "x = 1",
+ "x.y = 1",
+ "x().y = 1",
+ "x += 1",
+ "x.y += 1",
+ "x().y += 1",
+ "x -= 1",
+ "x *= 1",
+ "x /= 1",
+ "x ^= 1",
+ "x &= 1",
+ "x |= 1",
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testNoSideEffectsGlobal() throws Exception {
+ JexlFeatures f = new JexlFeatures().sideEffectGlobal(false);
+ String[] scripts = new String[]{
+ "x = 1",
+ "x.y = 1",
+ "x().y = 1",
+ "x += 1",
+ "x.y += 1",
+ "x().y += 1",
+ "x -= 1",
+ "x *= 1",
+ "x /= 1",
+ "x ^= 1",
+ "x &= 1",
+ "x |= 1",
+ "4 + (x.y = 1)",
+ "if (true) x.y.z = 4"
+ };
+ // these should all fail with x undeclared as local, thus x as global
+ checkFeature(f, scripts);
+ // same ones with x as local should work
+ for(String str : scripts) {
+ try {
+ JexlScript e = jexl.createScript("var x = foo(); " + str);
+ } catch (JexlException.Feature xfeature) {
+ Assert.fail(str + " :: should not fail parse: " + xfeature.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testNoLocals() throws Exception {
+ JexlFeatures f = new JexlFeatures().localVar(false);
+ String[] scripts = new String[]{
+ "var x = 0;",
+ "(x)->{ x }"
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testReservedVars() throws Exception {
+ JexlFeatures f = new JexlFeatures().reservedNames(Arrays.asList("foo", "bar"));
+ String[] scripts = new String[]{
+ "var foo = 0;",
+ "(bar)->{ bar }",
+ "var f = function(bar) { bar; }"
+ };
+ checkFeature(f, scripts);
+ String[] scriptsOk = new String[]{
+ "var foo0 = 0;",
+ "(bar1)->{ bar }",
+ "var f = function(bar2) { bar2; }"
+ };
+ assertOk(f, scriptsOk);
+ }
+
+ @Test
+ public void testArrayRefs() throws Exception {
+ JexlFeatures f = new JexlFeatures().arrayReferenceExpr(false);
+
+ String[] scripts = new String[]{
+ "x[y]",
+ "x['a'][b]",
+ "x()['a'][b]",
+ "x.y['a'][b]"
+ };
+ checkFeature(f, scripts);
+ assertOk(f, scripts);
+ // same ones with constant array refs should work
+ String[] scriptsOk = new String[]{
+ "x['y']",
+ "x['a'][1]",
+ "x()['a']['b']",
+ "x.y['a']['b']"
+ };
+ assertOk(f, scriptsOk);
+ }
+ @Test
+ public void testMethodCalls() throws Exception {
+ JexlFeatures f = new JexlFeatures().methodCall(false);
+ String[] scripts = new String[]{
+ "x.y(z)",
+ "x['a'].m(b)",
+ "x()['a'](b)",
+ "x.y['a'](b)"
+ };
+ checkFeature(f, scripts);
+ // same ones with constant array refs should work
+ String[] scriptsOk = new String[]{
+ "x('y')",
+ "x('a')[1]",
+ "x()['a']['b']",
+ };
+ assertOk(f, scriptsOk);
+ }
+
+ @Test
+ public void testStructuredLiterals() throws Exception {
+ JexlFeatures f = new JexlFeatures().structuredLiteral(false);
+ String[] scripts = new String[]{
+ "{1, 2, 3}",
+ "[1, 2, 3]",
+ "{ 1 :'one', 2 : 'two', 3 : 'three' }",
+ "(1 .. 5)"
+ };
+ checkFeature(f, scripts);
+ assertOk(f, scripts);
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ JexlFeatures f = new JexlFeatures().annotation(false);
+ String[] scripts = new String[]{
+ "@synchronized(2) { return 42; }",
+ "@two var x = 3;"
+ };
+ checkFeature(f, scripts);
+ }
+
+
+ @Test
+ public void testPragma() throws Exception {
+ JexlFeatures f = new JexlFeatures().pragma(false);
+ String[] scripts = new String[]{
+ "#pragma foo 42",
+ "@two var x = 3; #pragma foo 'bar'"
+ };
+ checkFeature(f, scripts);
+ }
+
+ @Test
+ public void testMixedFeatures() throws Exception {
+ // no new, no local, no lambda, no loops, no-side effects
+ JexlFeatures f = new JexlFeatures()
+ .newInstance(false)
+ .localVar(false)
+ .lambda(false)
+ .loops(false)
+ .sideEffectGlobal(false);
+ String[] scripts = new String[]{
+ "return new(clazz);",
+ "()->{ return 0 };",
+ "var x = 0;",
+ "(x, y)->{ return 0 };",
+ "for(var i : {0 .. 10}) { bar(i); }",
+ "x += 1",
+ "x.y += 1"
+ };
+ checkFeature(f, scripts);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/Foo.java b/src/test/java/org/apache/commons/jexl3/Foo.java
index 6b69b03e8..8a7ea20c8 100644
--- a/src/test/java/org/apache/commons/jexl3/Foo.java
+++ b/src/test/java/org/apache/commons/jexl3/Foo.java
@@ -51,9 +51,12 @@ public Foo getInnerFoo()
return new Foo();
}
- public String get(String arg)
- {
- return "Repeat : " + arg;
+ public String getQuux() {
+ return "String : quux";
+ }
+
+ public String repeat(String str) {
+ return "Repeat : " + str;
}
public String convertBoolean(boolean b)
diff --git a/src/test/java/org/apache/commons/jexl3/IfTest.java b/src/test/java/org/apache/commons/jexl3/IfTest.java
index d76977d21..83f0d2fe0 100644
--- a/src/test/java/org/apache/commons/jexl3/IfTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IfTest.java
@@ -115,6 +115,46 @@ public void testIfWithSimpleExpression() throws Exception {
Assert.assertEquals("Result is not true", Boolean.TRUE, o);
}
+ @Test
+ public void testIfElseIfExpression() throws Exception {
+ JexlScript e = JEXL.createScript("if (x == 1) { 10; } else if (x == 2) 20 else 30", "x");
+ Object o = e.execute(null, 1);
+ Assert.assertEquals(10, o);
+ o = e.execute(null, 2);
+ Assert.assertEquals(20, o);
+ o = e.execute(null, 4);
+ Assert.assertEquals(30, o);
+ }
+
+ @Test
+ public void testIfElseIfReturnExpression0() throws Exception {
+ JexlScript e = JEXL.createScript(
+ "if (x == 1) return 10; if (x == 2) return 20; else if (x == 3) return 30 else { return 40 }",
+ "x");
+ Object o = e.execute(null, 1);
+ Assert.assertEquals(10, o);
+ o = e.execute(null, 2);
+ Assert.assertEquals(20, o);
+ o = e.execute(null, 3);
+ Assert.assertEquals(30, o);
+ o = e.execute(null, 4);
+ Assert.assertEquals(40, o);
+ }
+ @Test
+ public void testIfElseIfReturnExpression() throws Exception {
+ JexlScript e = JEXL.createScript(
+ "if (x == 1) return 10; if (x == 2) return 20 else if (x == 3) return 30; else return 40;",
+ "x");
+ Object o = e.execute(null, 1);
+ Assert.assertEquals(10, o);
+ o = e.execute(null, 2);
+ Assert.assertEquals(20, o);
+ o = e.execute(null, 3);
+ Assert.assertEquals(30, o);
+ o = e.execute(null, 4);
+ Assert.assertEquals(40, o);
+ }
+
/**
* Test the if statement evaluates arithmetic expressions correctly
*
@@ -222,13 +262,14 @@ public void testTernary() throws Exception {
/**
* Ternary operator condition undefined or null evaluates to false
- * independantly of engine flags.
+ * independently of engine flags; same for null coalescing operator.
* @throws Exception
*/
@Test
public void testTernaryShorthand() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
JexlExpression e = JEXL.createExpression("x.y.z = foo?:'quux'");
+ JexlExpression f = JEXL.createExpression("foo??'quux'");
Object o;
// undefined foo
@@ -239,6 +280,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be quux", "quux", o);
}
jc.set("foo", null);
@@ -250,6 +293,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be quux", "quux", o);
}
jc.set("foo", Boolean.FALSE);
@@ -261,6 +306,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be false", false, o);
}
jc.set("foo", Double.NaN);
@@ -272,6 +319,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertTrue("Should be NaN", Double.isNaN((Double) o));
}
jc.set("foo", "");
@@ -283,6 +332,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be empty string", "", o);
}
jc.set("foo", "false");
@@ -294,6 +345,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be 'false'", "false", o);
}
jc.set("foo", 0d);
@@ -305,6 +358,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be 0", 0.d, o);
}
jc.set("foo", 0);
@@ -316,6 +371,8 @@ public void testTernaryShorthand() throws Exception {
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
Assert.assertEquals("Should be quux", "quux", o);
+ o = f.evaluate(jc);
+ Assert.assertEquals("Should be 0", 0, o);
}
jc.set("foo", "bar");
@@ -332,4 +389,41 @@ public void testTernaryShorthand() throws Exception {
debuggerCheck(JEXL);
}
+ @Test
+ public void testNullCoaelescing() throws Exception {
+ Object o;
+ JexlEvalContext jc = new JexlEvalContext();
+ JexlExpression xtrue = JEXL.createExpression("x??true");
+ o = xtrue.evaluate(jc);
+ Assert.assertEquals("Should be true", true, o);
+ jc.set("x", false);
+ o = xtrue.evaluate(jc);
+ Assert.assertEquals("Should be false", false, o);
+ JexlExpression yone = JEXL.createExpression("y??1");
+ o = yone.evaluate(jc);
+ Assert.assertEquals("Should be 1", 1, o);
+ jc.set("y", 0);
+ o = yone.evaluate(jc);
+ Assert.assertEquals("Should be 0", 0, o);
+ debuggerCheck(JEXL);
+ }
+
+ @Test
+ public void testNullCoaelescingScript() throws Exception {
+ Object o;
+ JexlEvalContext jc = new JexlEvalContext();
+ JexlScript xtrue = JEXL.createScript("x??true");
+ o = xtrue.execute(jc);
+ Assert.assertEquals("Should be true", true, o);
+ jc.set("x", false);
+ o = xtrue.execute(jc);
+ Assert.assertEquals("Should be false", false, o);
+ JexlScript yone = JEXL.createScript("y??1");
+ o = yone.execute(jc);
+ Assert.assertEquals("Should be 1", 1, o);
+ jc.set("y", 0);
+ o = yone.execute(jc);
+ Assert.assertEquals("Should be 0", 0, o);
+ debuggerCheck(JEXL);
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest.java b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
index cf3c71ec4..477afa58b 100644
--- a/src/test/java/org/apache/commons/jexl3/IssuesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
@@ -17,24 +17,17 @@
package org.apache.commons.jexl3;
import org.apache.commons.jexl3.internal.Engine;
-import java.math.BigDecimal;
-import java.math.MathContext;
+import org.apache.commons.jexl3.internal.introspection.Uberspect;
+
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.List;
-import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
//import org.apache.commons.beanutils.LazyDynaMap;
/**
- * Test cases for reported issue .
+ * Test cases for reported issue between JEXL-1 and JEXL-100.
*/
@SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
public class IssuesTest extends JexlTestCase {
@@ -64,7 +57,7 @@ public void test49() throws Exception {
// JEXL-48: bad assignment detection
public static class Another {
public String name = "whatever";
- private Boolean foo = Boolean.TRUE;
+ private final Boolean foo = Boolean.TRUE;
public Boolean foo() {
return foo;
@@ -76,7 +69,7 @@ public int goo() {
}
public static class Foo {
- private Another inner;
+ private final Another inner;
Foo() {
inner = new Another();
@@ -118,15 +111,15 @@ public void test47() throws Exception {
JexlExpression expr = jexl.createExpression("true//false\n");
Object value = expr.evaluate(ctxt);
- Assert.assertTrue("should be true", ((Boolean) value).booleanValue());
+ Assert.assertTrue("should be true", (Boolean) value);
expr = jexl.createExpression("/*true*/false");
value = expr.evaluate(ctxt);
- Assert.assertFalse("should be false", ((Boolean) value).booleanValue());
+ Assert.assertFalse("should be false", (Boolean) value);
expr = jexl.createExpression("/*\"true\"*/false");
value = expr.evaluate(ctxt);
- Assert.assertFalse("should be false", ((Boolean) value).booleanValue());
+ Assert.assertFalse("should be false", (Boolean) value);
}
// JEXL-42: NullPointerException evaluating an expression
@@ -170,7 +163,7 @@ public void test40() throws Exception {
JexlExpression expr = jexl.createExpression("derived.foo()");
Object value = expr.evaluate(ctxt);
- Assert.assertTrue("should be true", ((Boolean) value).booleanValue());
+ Assert.assertTrue("should be true", (Boolean) value);
}
// JEXL-52: can be implemented by deriving Interpreter.{g,s}etAttribute; later
@@ -416,763 +409,4 @@ public void test98() throws Exception {
}
}
- @Test
- public void test100() throws Exception {
- JexlEngine jexl = new JexlBuilder().cache(4).create();
- JexlContext ctxt = new MapContext();
- int[] foo = {42};
- ctxt.set("foo", foo);
- Object value;
- for (int l = 0; l < 2; ++l) {
- value = jexl.createExpression("foo[0]").evaluate(ctxt);
- Assert.assertEquals(42, value);
- value = jexl.createExpression("foo[0] = 43").evaluate(ctxt);
- Assert.assertEquals(43, value);
- value = jexl.createExpression("foo.0").evaluate(ctxt);
- Assert.assertEquals(43, value);
- value = jexl.createExpression("foo.0 = 42").evaluate(ctxt);
- Assert.assertEquals(42, value);
- }
- }
-
-// A's class definition
- public static class A105 {
- String nameA;
- String propA;
-
- public A105(String nameA, String propA) {
- this.nameA = nameA;
- this.propA = propA;
- }
-
- @Override
- public String toString() {
- return "A [nameA=" + nameA + ", propA=" + propA + "]";
- }
-
- public String getNameA() {
- return nameA;
- }
-
- public String getPropA() {
- return propA;
- }
-
- public String uppercase(String str) {
- return str.toUpperCase();
- }
- }
-
- @Test
- public void test105() throws Exception {
- JexlContext context = new MapContext();
- JexlExpression selectExp = new Engine().createExpression("[a.propA]");
- context.set("a", new A105("a1", "p1"));
- Object[] r = (Object[]) selectExp.evaluate(context);
- Assert.assertEquals("p1", r[0]);
-
-//selectExp = new Engine().createExpression("[a.propA]");
- context.set("a", new A105("a2", "p2"));
- r = (Object[]) selectExp.evaluate(context);
- Assert.assertEquals("p2", r[0]);
- }
-
- @Test
- public void test106() throws Exception {
- JexlEvalContext context = new JexlEvalContext();
- context.setStrict(true, true);
- context.set("a", new BigDecimal(1));
- context.set("b", new BigDecimal(3));
- JexlEngine jexl = new Engine();
- try {
- Object value = jexl.createExpression("a / b").evaluate(context);
- Assert.assertNotNull(value);
- } catch (JexlException xjexl) {
- Assert.fail("should not occur");
- }
- context.setMathContext(MathContext.UNLIMITED);
- context.setMathScale(2);
- try {
- jexl.createExpression("a / b").evaluate(context);
- Assert.fail("should fail");
- } catch (JexlException xjexl) {
- //ok to fail
- }
- }
-
- @Test
- public void test107() throws Exception {
- String[] exprs = {
- "'Q4'.toLowerCase()", "q4",
- "(Q4).toLowerCase()", "q4",
- "(4).toString()", "4",
- "(1 + 3).toString()", "4",
- "({ 'q' : 'Q4'}).get('q').toLowerCase()", "q4",
- "{ 'q' : 'Q4'}.get('q').toLowerCase()", "q4",
- "({ 'q' : 'Q4'})['q'].toLowerCase()", "q4",
- "(['Q4'])[0].toLowerCase()", "q4"
- };
-
- JexlContext context = new MapContext();
- context.set("Q4", "Q4");
- JexlEngine jexl = new Engine();
- for (int e = 0; e < exprs.length; e += 2) {
- JexlExpression expr = jexl.createExpression(exprs[e]);
- Object expected = exprs[e + 1];
- Object value = expr.evaluate(context);
- Assert.assertEquals(expected, value);
- expr = jexl.createExpression(expr.getParsedText());
- value = expr.evaluate(context);
- Assert.assertEquals(expected, value);
- }
- }
-
- @Test
- public void test108() throws Exception {
- JexlScript expr;
- Object value;
- JexlEngine jexl = new Engine();
- expr = jexl.createScript("size([])");
- value = expr.execute(null);
- Assert.assertEquals(0, value);
- expr = jexl.createScript(expr.getParsedText());
- value = expr.execute(null);
- Assert.assertEquals(0, value);
-
- expr = jexl.createScript("if (true) { [] } else { {:} }");
- value = expr.execute(null);
- Assert.assertTrue(value.getClass().isArray());
- expr = jexl.createScript(expr.getParsedText());
- value = expr.execute(null);
- Assert.assertTrue(value.getClass().isArray());
-
- expr = jexl.createScript("size({:})");
- value = expr.execute(null);
- Assert.assertEquals(0, value);
- expr = jexl.createScript(expr.getParsedText());
- value = expr.execute(null);
- Assert.assertEquals(0, value);
-
- expr = jexl.createScript("if (false) { [] } else { {:} }");
- value = expr.execute(null);
- Assert.assertTrue(value instanceof Map, ?>);
- expr = jexl.createScript(expr.getParsedText());
- value = expr.execute(null);
- Assert.assertTrue(value instanceof Map, ?>);
- }
-
- @Test
- public void test109() throws Exception {
- JexlEngine jexl = new Engine();
- Object value;
- JexlContext context = new MapContext();
- context.set("foo.bar", 40);
- value = jexl.createExpression("foo.bar + 2").evaluate(context);
- Assert.assertEquals(42, value);
- }
-
- @Test
- public void test110() throws Exception {
- JexlEngine jexl = new Engine();
- String[] names = {"foo"};
- Object value;
- JexlContext context = new MapContext();
- value = jexl.createScript("foo + 2", names).execute(context, 40);
- Assert.assertEquals(42, value);
- context.set("frak.foo", -40);
- value = jexl.createScript("frak.foo - 2", names).execute(context, 40);
- Assert.assertEquals(-42, value);
- }
-
- static public class RichContext extends ObjectContext {
- RichContext(JexlEngine jexl, A105 a105) {
- super(jexl, a105);
- }
- }
-
- @Test
- public void testRichContext() throws Exception {
- A105 a105 = new A105("foo", "bar");
- JexlEngine jexl = new Engine();
- Object value;
- JexlContext context = new RichContext(jexl, a105);
- value = jexl.createScript("uppercase(nameA + propA)").execute(context);
- Assert.assertEquals("FOOBAR", value);
- }
-
- @Test
- public void test111() throws Exception {
- JexlEngine jexl = new Engine();
- Object value;
- JexlContext context = new MapContext();
- String strExpr = "((x>0)?\"FirstValue=\"+(y-x):\"SecondValue=\"+x)";
- JexlExpression expr = jexl.createExpression(strExpr);
-
- context.set("x", 1);
- context.set("y", 10);
- value = expr.evaluate(context);
- Assert.assertEquals("FirstValue=9", value);
-
- context.set("x", 1.0d);
- context.set("y", 10.0d);
- value = expr.evaluate(context);
- Assert.assertEquals("FirstValue=9.0", value);
-
- context.set("x", 1);
- context.set("y", 10.0d);
- value = expr.evaluate(context);
- Assert.assertEquals("FirstValue=9.0", value);
-
- context.set("x", 1.0d);
- context.set("y", 10);
- value = expr.evaluate(context);
- Assert.assertEquals("FirstValue=9.0", value);
-
- context.set("x", -10);
- context.set("y", 1);
- value = expr.evaluate(context);
- Assert.assertEquals("SecondValue=-10", value);
-
- context.set("x", -10.0d);
- context.set("y", 1.0d);
- value = expr.evaluate(context);
- Assert.assertEquals("SecondValue=-10.0", value);
-
- context.set("x", -10);
- context.set("y", 1.0d);
- value = expr.evaluate(context);
- Assert.assertEquals("SecondValue=-10", value);
-
- context.set("x", -10.0d);
- context.set("y", 1);
- value = expr.evaluate(context);
- Assert.assertEquals("SecondValue=-10.0", value);
- }
-
- @Test
- public void testScaleIssue() throws Exception {
- JexlEngine jexlX = new Engine();
- String expStr1 = "result == salary/month * work.percent/100.00";
- JexlExpression exp1 = jexlX.createExpression(expStr1);
- JexlEvalContext ctx = new JexlEvalContext();
- ctx.set("result", new BigDecimal("9958.33"));
- ctx.set("salary", new BigDecimal("119500.00"));
- ctx.set("month", new BigDecimal("12.00"));
- ctx.set("work.percent", new BigDecimal("100.00"));
-
- // will fail because default scale is 5
- Assert.assertFalse((Boolean) exp1.evaluate(ctx));
-
- // will succeed with scale = 2
- ctx.setMathScale(2);
- Assert.assertTrue((Boolean) exp1.evaluate(ctx));
- }
-
- @Test
- public void test112() throws Exception {
- Object result;
- JexlEngine jexl = new Engine();
- result = jexl.createScript(Integer.toString(Integer.MAX_VALUE)).execute(null);
- Assert.assertEquals(Integer.MAX_VALUE, result);
- result = jexl.createScript(Integer.toString(Integer.MIN_VALUE + 1)).execute(null);
- Assert.assertEquals(Integer.MIN_VALUE + 1, result);
- result = jexl.createScript(Integer.toString(Integer.MIN_VALUE)).execute(null);
- Assert.assertEquals(Integer.MIN_VALUE, result);
- }
-
- @Test
- public void test117() throws Exception {
- JexlEngine jexl = new Engine();
- JexlExpression e = jexl.createExpression("TIMESTAMP > 20100102000000");
- JexlContext ctx = new MapContext();
- ctx.set("TIMESTAMP", new Long("20100103000000"));
- Object result = e.evaluate(ctx);
- Assert.assertTrue((Boolean) result);
- }
-
- public static class Foo125 {
- public String method() {
- return "OK";
- }
-
- public String total(String tt) {
- return "total " + tt;
- }
- }
-
- public static class Foo125Context extends ObjectContext {
- public Foo125Context(JexlEngine engine, Foo125 wrapped) {
- super(engine, wrapped);
- }
- }
-
- @Test
- public void test125() throws Exception {
- JexlEngine jexl = new Engine();
- JexlExpression e = jexl.createExpression("method()");
- JexlContext jc = new Foo125Context(jexl, new Foo125());
- Assert.assertEquals("OK", e.evaluate(jc));
- }
-
- @Test
- public void test130a() throws Exception {
- String myName = "Test.Name";
- Object myValue = "Test.Value";
-
- JexlEngine myJexlEngine = new Engine();
- MapContext myMapContext = new MapContext();
- myMapContext.set(myName, myValue);
-
- Object myObjectWithTernaryConditional = myJexlEngine.createScript(myName + "?:null").execute(myMapContext);
- Assert.assertEquals(myValue, myObjectWithTernaryConditional);
- }
-
- @Test
- public void test130b() throws Exception {
- String myName = "Test.Name";
- Object myValue = new Object() {
- @Override
- public String toString() {
- return "Test.Value";
- }
- };
-
- JexlEngine myJexlEngine = new Engine();
- MapContext myMapContext = new MapContext();
- myMapContext.set(myName, myValue);
-
- Object myObjectWithTernaryConditional = myJexlEngine.createScript(myName + "?:null").execute(myMapContext);
- Assert.assertEquals(myValue, myObjectWithTernaryConditional);
- }
-
- @Test
- public void test135() throws Exception {
- JexlEngine jexl = new Engine();
- JexlContext jc = new MapContext();
- JexlScript script;
- Object result;
- Map foo = new HashMap();
- foo.put(3, 42);
- jc.set("state", foo);
-
- script = jexl.createScript("var y = state[3]; y");
- result = script.execute(jc, foo);
- Assert.assertEquals(42, result);
-
- jc.set("a", 3);
- script = jexl.createScript("var y = state[a]; y");
- result = script.execute(jc, foo);
- Assert.assertEquals(42, result);
-
- jc.set("a", 2);
- script = jexl.createScript("var y = state[a + 1]; y");
- result = script.execute(jc, foo);
- Assert.assertEquals(42, result);
-
- jc.set("a", 2);
- jc.set("b", 1);
- script = jexl.createScript("var y = state[a + b]; y");
- result = script.execute(jc, foo);
- Assert.assertEquals(42, result);
-
- script = jexl.createScript("var y = state[3]; y", "state");
- result = script.execute(null, foo, 3);
- Assert.assertEquals(42, result);
-
- script = jexl.createScript("var y = state[a]; y", "state", "a");
- result = script.execute(null, foo, 3);
- Assert.assertEquals(42, result);
-
- script = jexl.createScript("var y = state[a + 1]; y", "state", "a");
- result = script.execute(null, foo, 2);
- Assert.assertEquals(42, result);
-
- script = jexl.createScript("var y = state[a + b]; y", "state", "a", "b");
- result = script.execute(null, foo, 2, 1);
- Assert.assertEquals(42, result);
- }
-
- @Test
- public void test136() throws Exception {
- JexlEngine jexl = new Engine();
- JexlContext jc = new MapContext();
- JexlScript script;
- JexlExpression expr;
- Object result;
-
- script = jexl.createScript("var x = $TAB[idx]; return x;", "idx");
- jc.set("fn01", script);
-
- script = jexl.createScript("$TAB = { 1:11, 2:22, 3:33}; IDX=2;");
- script.execute(jc);
-
- expr = jexl.createExpression("fn01(IDX)");
- result = expr.evaluate(jc);
- Assert.assertEquals("EXPR01 result", 22, result);
- }
-
-// @Test public void test138() throws Exception {
-// MapContext ctxt = new MapContext();
-// ctxt.set("tz", java.util.TimeZone.class);
-// String source = ""
-// + "var currentDate = new('java.util.Date');"
-// + "var gmt = tz.getTimeZone('GMT');"
-// + "var cet = tz.getTimeZone('CET');"
-// + "var calendarGMT = new('java.util.GregorianCalendar' , gmt);"
-// + "var calendarCET = new('java.util.GregorianCalendar', cet);"
-// + "var diff = calendarCET.getTime() - calendarGMT.getTime();"
-// + "return diff";
-//
-// JexlEngine jexl = new Engine();
-// JexlScript script = jexl.createScript(source);
-// Object result = script.execute(ctxt);
-// Assert.Assert.assertNotNull(result);
-// }
- @Test
- public void test143() throws Exception {
- JexlEngine jexl = new Engine();
- JexlContext jc = new MapContext();
- JexlScript script;
- Object result;
-
- script = jexl.createScript("var total = 10; total = (total - ((x < 3)? y : z)) / (total / 10); total", "x", "y", "z");
- result = script.execute(jc, 2, 2, 1);
- Assert.assertEquals(8, result);
- script = jexl.createScript("var total = 10; total = (total - ((x < 3)? y : 1)) / (total / 10); total", "x", "y", "z");
- result = script.execute(jc, 2, 2, 1);
- Assert.assertEquals(8, result);
- }
-
- @Test
- public void test144() throws Exception {
- JexlEngine jexl = new Engine();
- JexlContext jc = new MapContext();
- JexlScript script;
- Object result;
- script = jexl.createScript("var total = 10; total('tt')");
- try {
- result = script.execute(jc);
- Assert.fail("total() is not solvable");
- } catch (JexlException.Method ambiguous) {
- Assert.assertEquals("total", ambiguous.getMethod());
- }
- }
-
- /**
- * Test cases for empty array assignment.
- */
- public static class Quux144 {
- String[] arr;
- String[] arr2;
-
- public Quux144() {
- }
-
- public String[] getArr() {
- return arr;
- }
-
- public String[] getArr2() {
- return arr2;
- }
-
- public void setArr(String[] arr) {
- this.arr = arr;
- }
-
- public void setArr2(String[] arr2) {
- this.arr2 = arr2;
- }
-
- // Overloaded setter with different argument type.
- public void setArr2(Integer[] arr2) {
- }
- }
-
- @Test
- public void test144a() throws Exception {
- JexlEngine JEXL = new Engine();
- JexlContext jc = new MapContext();
- jc.set("quuxClass", Quux144.class);
- JexlExpression create = JEXL.createExpression("quux = new(quuxClass)");
- JexlExpression assignArray = JEXL.createExpression("quux.arr = [ 'hello', 'world' ]");
- JexlExpression checkArray = JEXL.createExpression("quux.arr");
-
- // test with a string
- Quux144 quux = (Quux144) create.evaluate(jc);
- Assert.assertNotNull("quux is null", quux);
-
- // test with a nonempty string array
- Object o = assignArray.evaluate(jc);
- Assert.assertEquals("Result is not a string array", String[].class, o.getClass());
- o = checkArray.evaluate(jc);
- Assert.assertEquals("The array elements are equal", Arrays.asList("hello", "world"), Arrays.asList((String[]) o));
-
- // test with a null array
- assignArray = JEXL.createExpression("quux.arr = null");
- o = assignArray.evaluate(jc);
- Assert.assertNull("Result is not null", o);
- o = checkArray.evaluate(jc);
- Assert.assertNull("Result is not null", o);
-
- // test with an empty array
- assignArray = JEXL.createExpression("quux.arr = [ ]");
- o = assignArray.evaluate(jc);
- Assert.assertNotNull("Result is null", o);
- o = checkArray.evaluate(jc);
- Assert.assertEquals("The array elements are not equal", Arrays.asList(new String[0]), Arrays.asList((String[]) o));
- Assert.assertEquals("The array size is not zero", 0, ((String[]) o).length);
-
- // test with an empty array on the overloaded setter for different types.
- // so, the assignment should fail with logging 'The ambiguous property, arr2, should have failed.'
- try {
- assignArray = JEXL.createExpression("quux.arr2 = [ ]");
- o = assignArray.evaluate(jc);
- Assert.fail("The arr2 property shouldn't be set due to its ambiguity (overloaded setters with different types).");
- } catch (JexlException.Property e) {
- //System.out.println("Expected ambiguous property setting exception: " + e);
- }
- Assert.assertNull("The arr2 property value should remain as null, not an empty array.", quux.arr2);
- }
-
- @Test
- public void test147b() throws Exception {
- String[] scripts = {"var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one", // results to 1
- "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one",// results to 1
- "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']",//results to 1
- "var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']"// result to null?
- };
-
- JexlEngine JEXL = new Engine();
- JexlContext jc = new MapContext();
- for (String s : scripts) {
- Object o = JEXL.createScript(s).execute(jc);
- Assert.assertEquals(1, o);
- }
- }
-
- @Test
- public void test147c() throws Exception {
- String[] scripts = {
- "var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one",
- "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one",
- "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']",
- "var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']"
- };
- JexlEngine JEXL = new Engine();
- for (String s : scripts) {
- JexlContext jc = new MapContext();
- Object o = JEXL.createScript(s).execute(jc);
- Assert.assertEquals(1, o);
- }
- }
-
- @Test
- public void test5115a() throws Exception {
- String str = "{\n"
- + " var x = \"A comment\";\n"
- + " var y = \"A comment\";\n"
- + "}";
- try {
- JexlEngine JEXL = new Engine();
- JexlScript s = JEXL.createScript(str);
- } catch (JexlException.Parsing xparse) {
- throw xparse;
- }
- }
-
- @Test
- public void test5115b() throws Exception {
- String str = "{\n"
- + " var x = \"A comment\";\n"
- + "}";
- try {
- JexlEngine JEXL = new Engine();
- JexlScript s = JEXL.createScript(str);
- } catch (JexlException.Parsing xparse) {
- throw xparse;
- }
- }
-
- static final String TESTA = "src/test/scripts/testA.jexl";
-
- @Test
- public void test5115c() throws Exception {
- URL testUrl = new File(TESTA).toURI().toURL();
- try {
- JexlEngine JEXL = new Engine();
- JexlScript s = JEXL.createScript(testUrl);
- } catch (JexlException.Parsing xparse) {
- throw xparse;
- }
- }
-
- public static class Utils {
- public List asList(T[] array) {
- return Arrays.asList(array);
- }
-
- public List asList(int[] array) {
- List l = new ArrayList(array.length);
- for (int i : array) {
- l.add(i);
- }
- return l;
- }
- }
-
- @Test
- public void test148a() throws Exception {
- JexlEngine jexl = new Engine();
- JexlContext jc = new MapContext();
- jc.set("u", new Utils());
-
- String src = "u.asList(['foo', 'bar'])";
- JexlScript e = jexl.createScript(src);
- Object o = e.execute(jc);
- Assert.assertTrue(o instanceof List);
- Assert.assertEquals(Arrays.asList("foo", "bar"), o);
-
- src = "u.asList([1, 2])";
- e = jexl.createScript(src);
- o = e.execute(jc);
- Assert.assertTrue(o instanceof List);
- Assert.assertEquals(Arrays.asList(1, 2), o);
- }
-
- @Test
- public void test155() throws Exception {
- JexlEngine jexlEngine = new Engine();
- JexlExpression jexlExpresssion = jexlEngine.createExpression("first.second.name");
- JexlContext jc = new MapContext();
- jc.set("first.second.name", "RIGHT");
- jc.set("name", "WRONG");
- Object value = jexlExpresssion.evaluate(jc);
- Assert.assertEquals("RIGHT", value.toString());
- }
-
- public static class Question42 extends MapContext {
- public String functionA(String arg) {
- return "a".equals(arg) ? "A" : "";
- }
-
- public String functionB(String arg) {
- return "b".equals(arg) ? "B" : "";
- }
-
- public String functionC(String arg) {
- return "c".equals(arg) ? "C" : "";
- }
-
- public String functionD(String arg) {
- return "d".equals(arg) ? "D" : "";
- }
- }
-
- public static class Arithmetic42 extends JexlArithmetic {
- public Arithmetic42() {
- super(false);
- }
-
- public Object and(String lhs, String rhs) {
- if (rhs.isEmpty()) {
- return "";
- }
- if (lhs.isEmpty()) {
- return "";
- }
- return lhs + rhs;
- }
-
- public Object or(String lhs, String rhs) {
- if (rhs.isEmpty()) {
- return lhs;
- }
- if (lhs.isEmpty()) {
- return rhs;
- }
- return lhs + rhs;
- }
- }
-
- @Test
- public void testQuestion42() throws Exception {
- JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic42()).create();
- JexlContext jc = new Question42();
-
- String str0 = "(functionA('z') | functionB('b')) & (functionC('c') | functionD('d') ) ";
- JexlExpression expr0 = jexl.createExpression(str0);
- Object value0 = expr0.evaluate(jc);
- Assert.assertEquals("BCD", value0);
-
- String str1 = "(functionA('z') & functionB('b')) | (functionC('c') & functionD('d') ) ";
- JexlExpression expr1 = jexl.createExpression(str1);
- Object value1 = expr1.evaluate(jc);
- Assert.assertEquals("CD", value1);
- }
-
- @Test
- public void test179() throws Exception {
- JexlContext jc = new MapContext();
- JexlEngine jexl = new JexlBuilder().create();
- String src = "x = new ('java.util.HashSet'); x.add(1); x";
- JexlScript e = jexl.createScript(src);
- Object o = e.execute(jc);
- Assert.assertTrue(o instanceof Set);
- Assert.assertTrue(((Set) o).contains(1));
- }
-//
-//
-// @Test
-// public void testUnderscoreInName() {
-// JexlEngine jexl = new Engine();
-// String jexlExp = "(x.length_mm * x.width)";
-// JexlExpression e = jexl.createExpression( jexlExp );
-// JexlContext jc = new MapContext();
-//
-// LazyDynaMap object = new LazyDynaMap();
-// object.set("length_mm", "10.0");
-// object.set("width", "5.0");
-//
-// jc.set("x", object );
-//
-// Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
-// }
-//
-// @Test
-// public void testFullStopInName() {
-// JexlEngine jexl = new Engine();
-// String jexlExp = "(x.length.mm * x.width)";
-// JexlExpression e = jexl.createExpression( jexlExp );
-// JexlContext jc = new MapContext();
-//
-// LazyDynaMap object = new LazyDynaMap();
-// object.set("length.mm", "10.0");
-// object.set("width", "5.0");
-//
-// Assert.assertEquals(null, object.get("length.mm"), "10.0");
-//
-// jc.set("x", object );
-//
-// Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
-// }
-//
-// @Test
-// public void testFullStopInNameMakingSubObject() {
-// JexlEngine jexl = new Engine();
-// String jexlExp = "(x.length.mm * x.width)";
-// JexlExpression e = jexl.createExpression( jexlExp );
-// JexlContext jc = new MapContext();
-//
-// LazyDynaMap object = new LazyDynaMap();
-// LazyDynaMap subObject = new LazyDynaMap();
-// object.set("length", subObject);
-// subObject.set("mm", "10.0");
-// object.set("width", "5.0");
-//
-// jc.set("x", object );
-//
-// Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
-// }
-
}
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest100.java b/src/test/java/org/apache/commons/jexl3/IssuesTest100.java
new file mode 100644
index 000000000..35454f14b
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest100.java
@@ -0,0 +1,814 @@
+/*
+ * 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 org.apache.commons.jexl3.internal.Engine;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.MathContext;
+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;
+
+/**
+ * Test cases for reported issue between JEXL-100 and JEXL-199.
+ */
+@SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class IssuesTest100 extends JexlTestCase {
+ public IssuesTest100() {
+ super("IssuesTest100", null);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ // ensure jul logging is only error to avoid warning in silent mode
+ java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
+ }
+
+
+ @Test
+ public void test100() throws Exception {
+ JexlEngine jexl = new JexlBuilder().cache(4).create();
+ JexlContext ctxt = new MapContext();
+ int[] foo = {42};
+ ctxt.set("foo", foo);
+ Object value;
+ for (int l = 0; l < 2; ++l) {
+ value = jexl.createExpression("foo[0]").evaluate(ctxt);
+ Assert.assertEquals(42, value);
+ value = jexl.createExpression("foo[0] = 43").evaluate(ctxt);
+ Assert.assertEquals(43, value);
+ value = jexl.createExpression("foo.0").evaluate(ctxt);
+ Assert.assertEquals(43, value);
+ value = jexl.createExpression("foo.0 = 42").evaluate(ctxt);
+ Assert.assertEquals(42, value);
+ }
+ }
+
+// A's class definition
+ public static class A105 {
+ String nameA;
+ String propA;
+
+ public A105(String nameA, String propA) {
+ this.nameA = nameA;
+ this.propA = propA;
+ }
+
+ @Override
+ public String toString() {
+ return "A [nameA=" + nameA + ", propA=" + propA + "]";
+ }
+
+ public String getNameA() {
+ return nameA;
+ }
+
+ public String getPropA() {
+ return propA;
+ }
+
+ public String uppercase(String str) {
+ return str.toUpperCase();
+ }
+ }
+
+ @Test
+ public void test105() throws Exception {
+ JexlContext context = new MapContext();
+ JexlExpression selectExp = new Engine().createExpression("[a.propA]");
+ context.set("a", new A105("a1", "p1"));
+ Object[] r = (Object[]) selectExp.evaluate(context);
+ Assert.assertEquals("p1", r[0]);
+
+//selectExp = new Engine().createExpression("[a.propA]");
+ context.set("a", new A105("a2", "p2"));
+ r = (Object[]) selectExp.evaluate(context);
+ Assert.assertEquals("p2", r[0]);
+ }
+
+ @Test
+ public void test106() throws Exception {
+ JexlEvalContext context = new JexlEvalContext();
+ context.setStrict(true, true);
+ context.set("a", new BigDecimal(1));
+ context.set("b", new BigDecimal(3));
+ JexlEngine jexl = new Engine();
+ try {
+ Object value = jexl.createExpression("a / b").evaluate(context);
+ Assert.assertNotNull(value);
+ } catch (JexlException xjexl) {
+ Assert.fail("should not occur");
+ }
+ context.setMathContext(MathContext.UNLIMITED);
+ context.setMathScale(2);
+ try {
+ jexl.createExpression("a / b").evaluate(context);
+ Assert.fail("should fail");
+ } catch (JexlException xjexl) {
+ //ok to fail
+ }
+ }
+
+ @Test
+ public void test107() throws Exception {
+ String[] exprs = {
+ "'Q4'.toLowerCase()", "q4",
+ "(Q4).toLowerCase()", "q4",
+ "(4).toString()", "4",
+ "(1 + 3).toString()", "4",
+ "({ 'q' : 'Q4'}).get('q').toLowerCase()", "q4",
+ "{ 'q' : 'Q4'}.get('q').toLowerCase()", "q4",
+ "({ 'q' : 'Q4'})['q'].toLowerCase()", "q4",
+ "(['Q4'])[0].toLowerCase()", "q4"
+ };
+
+ JexlContext context = new MapContext();
+ context.set("Q4", "Q4");
+ JexlEngine jexl = new Engine();
+ for (int e = 0; e < exprs.length; e += 2) {
+ JexlExpression expr = jexl.createExpression(exprs[e]);
+ Object expected = exprs[e + 1];
+ Object value = expr.evaluate(context);
+ Assert.assertEquals(expected, value);
+ expr = jexl.createExpression(expr.getParsedText());
+ value = expr.evaluate(context);
+ Assert.assertEquals(expected, value);
+ }
+ }
+
+ @Test
+ public void test108() throws Exception {
+ JexlScript expr;
+ Object value;
+ JexlEngine jexl = new Engine();
+ expr = jexl.createScript("size([])");
+ value = expr.execute(null);
+ Assert.assertEquals(0, value);
+ expr = jexl.createScript(expr.getParsedText());
+ value = expr.execute(null);
+ Assert.assertEquals(0, value);
+
+ expr = jexl.createScript("if (true) { [] } else { {:} }");
+ value = expr.execute(null);
+ Assert.assertTrue(value.getClass().isArray());
+ expr = jexl.createScript(expr.getParsedText());
+ value = expr.execute(null);
+ Assert.assertTrue(value.getClass().isArray());
+
+ expr = jexl.createScript("size({:})");
+ value = expr.execute(null);
+ Assert.assertEquals(0, value);
+ expr = jexl.createScript(expr.getParsedText());
+ value = expr.execute(null);
+ Assert.assertEquals(0, value);
+
+ expr = jexl.createScript("if (false) { [] } else { {:} }");
+ value = expr.execute(null);
+ Assert.assertTrue(value instanceof Map, ?>);
+ expr = jexl.createScript(expr.getParsedText());
+ value = expr.execute(null);
+ Assert.assertTrue(value instanceof Map, ?>);
+ }
+
+ @Test
+ public void test109() throws Exception {
+ JexlEngine jexl = new Engine();
+ Object value;
+ JexlContext context = new MapContext();
+ context.set("foo.bar", 40);
+ value = jexl.createExpression("foo.bar + 2").evaluate(context);
+ Assert.assertEquals(42, value);
+ }
+
+ @Test
+ public void test110() throws Exception {
+ JexlEngine jexl = new Engine();
+ String[] names = {"foo"};
+ Object value;
+ JexlContext context = new MapContext();
+ value = jexl.createScript("foo + 2", names).execute(context, 40);
+ Assert.assertEquals(42, value);
+ context.set("frak.foo", -40);
+ value = jexl.createScript("frak.foo - 2", names).execute(context, 40);
+ Assert.assertEquals(-42, value);
+ }
+
+ static public class RichContext extends ObjectContext {
+ RichContext(JexlEngine jexl, A105 a105) {
+ super(jexl, a105);
+ }
+ }
+
+ @Test
+ public void testRichContext() throws Exception {
+ A105 a105 = new A105("foo", "bar");
+ JexlEngine jexl = new Engine();
+ Object value;
+ JexlContext context = new RichContext(jexl, a105);
+ value = jexl.createScript("uppercase(nameA + propA)").execute(context);
+ Assert.assertEquals("FOOBAR", value);
+ }
+
+ @Test
+ public void test111() throws Exception {
+ JexlEngine jexl = new Engine();
+ Object value;
+ JexlContext context = new MapContext();
+ String strExpr = "((x>0)?\"FirstValue=\"+(y-x):\"SecondValue=\"+x)";
+ JexlExpression expr = jexl.createExpression(strExpr);
+
+ context.set("x", 1);
+ context.set("y", 10);
+ value = expr.evaluate(context);
+ Assert.assertEquals("FirstValue=9", value);
+
+ context.set("x", 1.0d);
+ context.set("y", 10.0d);
+ value = expr.evaluate(context);
+ Assert.assertEquals("FirstValue=9.0", value);
+
+ context.set("x", 1);
+ context.set("y", 10.0d);
+ value = expr.evaluate(context);
+ Assert.assertEquals("FirstValue=9.0", value);
+
+ context.set("x", 1.0d);
+ context.set("y", 10);
+ value = expr.evaluate(context);
+ Assert.assertEquals("FirstValue=9.0", value);
+
+ context.set("x", -10);
+ context.set("y", 1);
+ value = expr.evaluate(context);
+ Assert.assertEquals("SecondValue=-10", value);
+
+ context.set("x", -10.0d);
+ context.set("y", 1.0d);
+ value = expr.evaluate(context);
+ Assert.assertEquals("SecondValue=-10.0", value);
+
+ context.set("x", -10);
+ context.set("y", 1.0d);
+ value = expr.evaluate(context);
+ Assert.assertEquals("SecondValue=-10", value);
+
+ context.set("x", -10.0d);
+ context.set("y", 1);
+ value = expr.evaluate(context);
+ Assert.assertEquals("SecondValue=-10.0", value);
+ }
+
+ @Test
+ public void testScaleIssue() throws Exception {
+ JexlEngine jexlX = new Engine();
+ String expStr1 = "result == salary/month * work.percent/100.00";
+ JexlExpression exp1 = jexlX.createExpression(expStr1);
+ JexlEvalContext ctx = new JexlEvalContext();
+ ctx.set("result", new BigDecimal("9958.33"));
+ ctx.set("salary", new BigDecimal("119500.00"));
+ ctx.set("month", new BigDecimal("12.00"));
+ ctx.set("work.percent", new BigDecimal("100.00"));
+
+ // will fail because default scale is 5
+ Assert.assertFalse((Boolean) exp1.evaluate(ctx));
+
+ // will succeed with scale = 2
+ ctx.setMathScale(2);
+ Assert.assertTrue((Boolean) exp1.evaluate(ctx));
+ }
+
+ @Test
+ public void test112() throws Exception {
+ Object result;
+ JexlEngine jexl = new Engine();
+ result = jexl.createScript(Integer.toString(Integer.MAX_VALUE)).execute(null);
+ Assert.assertEquals(Integer.MAX_VALUE, result);
+ result = jexl.createScript(Integer.toString(Integer.MIN_VALUE + 1)).execute(null);
+ Assert.assertEquals(Integer.MIN_VALUE + 1, result);
+ result = jexl.createScript(Integer.toString(Integer.MIN_VALUE)).execute(null);
+ Assert.assertEquals(Integer.MIN_VALUE, result);
+ }
+
+ @Test
+ public void test117() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlExpression e = jexl.createExpression("TIMESTAMP > 20100102000000");
+ JexlContext ctx = new MapContext();
+ ctx.set("TIMESTAMP", new Long("20100103000000"));
+ Object result = e.evaluate(ctx);
+ Assert.assertTrue((Boolean) result);
+ }
+
+ public static class Foo125 {
+ public String method() {
+ return "OK";
+ }
+
+ public String total(String tt) {
+ return "total " + tt;
+ }
+ }
+
+ public static class Foo125Context extends ObjectContext {
+ public Foo125Context(JexlEngine engine, Foo125 wrapped) {
+ super(engine, wrapped);
+ }
+ }
+
+ @Test
+ public void test125() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlExpression e = jexl.createExpression("method()");
+ JexlContext jc = new Foo125Context(jexl, new Foo125());
+ Assert.assertEquals("OK", e.evaluate(jc));
+ }
+
+ @Test
+ public void test130a() throws Exception {
+ String myName = "Test.Name";
+ Object myValue = "Test.Value";
+
+ JexlEngine myJexlEngine = new Engine();
+ MapContext myMapContext = new MapContext();
+ myMapContext.set(myName, myValue);
+
+ Object myObjectWithTernaryConditional = myJexlEngine.createScript(myName + "?:null").execute(myMapContext);
+ Assert.assertEquals(myValue, myObjectWithTernaryConditional);
+ }
+
+ @Test
+ public void test130b() throws Exception {
+ String myName = "Test.Name";
+ Object myValue = new Object() {
+ @Override
+ public String toString() {
+ return "Test.Value";
+ }
+ };
+
+ JexlEngine myJexlEngine = new Engine();
+ MapContext myMapContext = new MapContext();
+ myMapContext.set(myName, myValue);
+
+ Object myObjectWithTernaryConditional = myJexlEngine.createScript(myName + "?:null").execute(myMapContext);
+ Assert.assertEquals(myValue, myObjectWithTernaryConditional);
+ }
+
+ @Test
+ public void test135() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ JexlScript script;
+ Object result;
+ Map foo = new HashMap();
+ foo.put(3, 42);
+ jc.set("state", foo);
+
+ script = jexl.createScript("var y = state[3]; y");
+ result = script.execute(jc, foo);
+ Assert.assertEquals(42, result);
+
+ jc.set("a", 3);
+ script = jexl.createScript("var y = state[a]; y");
+ result = script.execute(jc, foo);
+ Assert.assertEquals(42, result);
+
+ jc.set("a", 2);
+ script = jexl.createScript("var y = state[a + 1]; y");
+ result = script.execute(jc, foo);
+ Assert.assertEquals(42, result);
+
+ jc.set("a", 2);
+ jc.set("b", 1);
+ script = jexl.createScript("var y = state[a + b]; y");
+ result = script.execute(jc, foo);
+ Assert.assertEquals(42, result);
+
+ script = jexl.createScript("var y = state[3]; y", "state");
+ result = script.execute(null, foo, 3);
+ Assert.assertEquals(42, result);
+
+ script = jexl.createScript("var y = state[a]; y", "state", "a");
+ result = script.execute(null, foo, 3);
+ Assert.assertEquals(42, result);
+
+ script = jexl.createScript("var y = state[a + 1]; y", "state", "a");
+ result = script.execute(null, foo, 2);
+ Assert.assertEquals(42, result);
+
+ script = jexl.createScript("var y = state[a + b]; y", "state", "a", "b");
+ result = script.execute(null, foo, 2, 1);
+ Assert.assertEquals(42, result);
+ }
+
+ @Test
+ public void test136() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ JexlScript script;
+ JexlExpression expr;
+ Object result;
+
+ script = jexl.createScript("var x = $TAB[idx]; return x;", "idx");
+ jc.set("fn01", script);
+
+ script = jexl.createScript("$TAB = { 1:11, 2:22, 3:33}; IDX=2;");
+ script.execute(jc);
+
+ expr = jexl.createExpression("fn01(IDX)");
+ result = expr.evaluate(jc);
+ Assert.assertEquals("EXPR01 result", 22, result);
+ }
+
+// @Test
+// public void test138() throws Exception {
+// MapContext ctxt = new MapContext();
+// ctxt.set("tz", java.util.TimeZone.class);
+// String source = ""
+// + "var currentDate = new('java.util.Date');"
+// + "var gmt = tz.getTimeZone('GMT');"
+// + "var cet = tz.getTimeZone('CET');"
+// + "var calendarGMT = new('java.util.GregorianCalendar' , gmt);"
+// + "var calendarCET = new('java.util.GregorianCalendar', cet);"
+// + "var diff = calendarCET.getTime() - calendarGMT.getTime();"
+// + "return diff";
+//
+// JexlEngine jexl = new Engine();
+// JexlScript script = jexl.createScript(source);
+// Object result = script.execute(ctxt);
+// Assert.Assert.assertNotNull(result);
+// }
+ @Test
+ public void test143() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ JexlScript script;
+ Object result;
+
+ script = jexl.createScript("var total = 10; total = (total - ((x < 3)? y : z)) / (total / 10); total", "x", "y", "z");
+ result = script.execute(jc, 2, 2, 1);
+ Assert.assertEquals(8, result);
+ script = jexl.createScript("var total = 10; total = (total - ((x < 3)? y : 1)) / (total / 10); total", "x", "y", "z");
+ result = script.execute(jc, 2, 2, 1);
+ Assert.assertEquals(8, result);
+ }
+
+ @Test
+ public void test144() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ JexlScript script;
+ Object result;
+ script = jexl.createScript("var total = 10; total('tt')");
+ try {
+ result = script.execute(jc);
+ Assert.fail("total() is not solvable");
+ } catch (JexlException.Method ambiguous) {
+ Assert.assertEquals("total", ambiguous.getMethod());
+ }
+ }
+
+ /**
+ * Test cases for empty array assignment.
+ */
+ public static class Quux144 {
+ String[] arr;
+ String[] arr2;
+
+ public Quux144() {
+ }
+
+ public String[] getArr() {
+ return arr;
+ }
+
+ public String[] getArr2() {
+ return arr2;
+ }
+
+ public void setArr(String[] arr) {
+ this.arr = arr;
+ }
+
+ public void setArr2(String[] arr2) {
+ this.arr2 = arr2;
+ }
+
+ // Overloaded setter with different argument type.
+ public void setArr2(Integer[] arr2) {
+ }
+ }
+
+ @Test
+ public void test144a() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ jc.set("quuxClass", Quux144.class);
+ JexlExpression create = jexl.createExpression("quux = new(quuxClass)");
+ JexlExpression assignArray = jexl.createExpression("quux.arr = [ 'hello', 'world' ]");
+ JexlExpression checkArray = jexl.createExpression("quux.arr");
+
+ // test with a string
+ Quux144 quux = (Quux144) create.evaluate(jc);
+ Assert.assertNotNull("quux is null", quux);
+
+ // test with a nonempty string array
+ Object o = assignArray.evaluate(jc);
+ Assert.assertEquals("Result is not a string array", String[].class, o.getClass());
+ o = checkArray.evaluate(jc);
+ Assert.assertEquals("The array elements are equal", Arrays.asList("hello", "world"), Arrays.asList((String[]) o));
+
+ // test with a null array
+ assignArray = jexl.createExpression("quux.arr = null");
+ o = assignArray.evaluate(jc);
+ Assert.assertNull("Result is not null", o);
+ o = checkArray.evaluate(jc);
+ Assert.assertNull("Result is not null", o);
+
+ // test with an empty array
+ assignArray = jexl.createExpression("quux.arr = [ ]");
+ o = assignArray.evaluate(jc);
+ Assert.assertNotNull("Result is null", o);
+ o = checkArray.evaluate(jc);
+ Assert.assertEquals("The array elements are not equal", Arrays.asList(new String[0]), Arrays.asList((String[]) o));
+ Assert.assertEquals("The array size is not zero", 0, ((String[]) o).length);
+
+ // test with an empty array on the overloaded setter for different types.
+ // so, the assignment should fail with logging 'The ambiguous property, arr2, should have failed.'
+ try {
+ assignArray = jexl.createExpression("quux.arr2 = [ ]");
+ o = assignArray.evaluate(jc);
+ Assert.fail("The arr2 property shouldn't be set due to its ambiguity (overloaded setters with different types).");
+ } catch (JexlException.Property e) {
+ //System.out.println("Expected ambiguous property setting exception: " + e);
+ }
+ Assert.assertNull("The arr2 property value should remain as null, not an empty array.", quux.arr2);
+ }
+
+ @Test
+ public void test147b() throws Exception {
+ String[] scripts = {"var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one", // results to 1
+ "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one",// results to 1
+ "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']",//results to 1
+ "var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']"// result to null?
+ };
+
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ for (String s : scripts) {
+ Object o = jexl.createScript(s).execute(jc);
+ Assert.assertEquals(1, o);
+ }
+ }
+
+ @Test
+ public void test147c() throws Exception {
+ String[] scripts = {
+ "var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one",
+ "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x.one",
+ "x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']",
+ "var x = new ('java.util.HashMap'); x.one = 1; x.two = 2; x['one']"
+ };
+ JexlEngine jexl = new Engine();
+ for (String s : scripts) {
+ JexlContext jc = new MapContext();
+ Object o = jexl.createScript(s).execute(jc);
+ Assert.assertEquals(1, o);
+ }
+ }
+
+ @Test
+ public void test5115a() throws Exception {
+ String str = "{\n"
+ + " var x = \"A comment\";\n"
+ + " var y = \"A comment\";\n"
+ + "}";
+ try {
+ JexlEngine jexl = new Engine();
+ JexlScript s = jexl.createScript(str);
+ } catch (JexlException.Parsing xparse) {
+ throw xparse;
+ }
+ }
+
+ @Test
+ public void test5115b() throws Exception {
+ String str = "{\n"
+ + " var x = \"A comment\";\n"
+ + "}";
+ try {
+ JexlEngine jexl = new Engine();
+ JexlScript s = jexl.createScript(str);
+ } catch (JexlException.Parsing xparse) {
+ throw xparse;
+ }
+ }
+
+ static final String TESTA = "src/test/scripts/testA.jexl";
+
+ @Test
+ public void test5115c() throws Exception {
+ URL testUrl = new File(TESTA).toURI().toURL();
+ try {
+ JexlEngine jexl = new Engine();
+ JexlScript s = jexl.createScript(testUrl);
+ } catch (JexlException.Parsing xparse) {
+ throw xparse;
+ }
+ }
+
+ public static class Utils {
+ public List asList(T[] array) {
+ return Arrays.asList(array);
+ }
+
+ public List asList(int[] array) {
+ List l = new ArrayList(array.length);
+ for (int i : array) {
+ l.add(i);
+ }
+ return l;
+ }
+ }
+
+ @Test
+ public void test148a() throws Exception {
+ JexlEngine jexl = new Engine();
+ JexlContext jc = new MapContext();
+ jc.set("u", new Utils());
+
+ String src = "u.asList(['foo', 'bar'])";
+ JexlScript e = jexl.createScript(src);
+ Object o = e.execute(jc);
+ Assert.assertTrue(o instanceof List);
+ Assert.assertEquals(Arrays.asList("foo", "bar"), o);
+
+ src = "u.asList([1, 2])";
+ e = jexl.createScript(src);
+ o = e.execute(jc);
+ Assert.assertTrue(o instanceof List);
+ Assert.assertEquals(Arrays.asList(1, 2), o);
+ }
+
+ @Test
+ public void test155() throws Exception {
+ JexlEngine jexlEngine = new Engine();
+ JexlExpression jexlExpresssion = jexlEngine.createExpression("first.second.name");
+ JexlContext jc = new MapContext();
+ jc.set("first.second.name", "RIGHT");
+ jc.set("name", "WRONG");
+ Object value = jexlExpresssion.evaluate(jc);
+ Assert.assertEquals("RIGHT", value.toString());
+ }
+
+ public static class Question42 extends MapContext {
+ public String functionA(String arg) {
+ return "a".equals(arg) ? "A" : "";
+ }
+
+ public String functionB(String arg) {
+ return "b".equals(arg) ? "B" : "";
+ }
+
+ public String functionC(String arg) {
+ return "c".equals(arg) ? "C" : "";
+ }
+
+ public String functionD(String arg) {
+ return "d".equals(arg) ? "D" : "";
+ }
+ }
+
+ public static class Arithmetic42 extends JexlArithmetic {
+ public Arithmetic42() {
+ super(false);
+ }
+
+ public Object and(String lhs, String rhs) {
+ if (rhs.isEmpty()) {
+ return "";
+ }
+ if (lhs.isEmpty()) {
+ return "";
+ }
+ return lhs + rhs;
+ }
+
+ public Object or(String lhs, String rhs) {
+ if (rhs.isEmpty()) {
+ return lhs;
+ }
+ if (lhs.isEmpty()) {
+ return rhs;
+ }
+ return lhs + rhs;
+ }
+ }
+
+ @Test
+ public void testQuestion42() throws Exception {
+ JexlEngine jexl = new JexlBuilder().arithmetic(new Arithmetic42()).create();
+ JexlContext jc = new Question42();
+
+ String str0 = "(functionA('z') | functionB('b')) & (functionC('c') | functionD('d') ) ";
+ JexlExpression expr0 = jexl.createExpression(str0);
+ Object value0 = expr0.evaluate(jc);
+ Assert.assertEquals("BCD", value0);
+
+ String str1 = "(functionA('z') & functionB('b')) | (functionC('c') & functionD('d') ) ";
+ JexlExpression expr1 = jexl.createExpression(str1);
+ Object value1 = expr1.evaluate(jc);
+ Assert.assertEquals("CD", value1);
+ }
+
+ @Test
+ public void test179() throws Exception {
+ JexlContext jc = new MapContext();
+ JexlEngine jexl = new JexlBuilder().create();
+ String src = "x = new ('java.util.HashSet'); x.add(1); x";
+ JexlScript e = jexl.createScript(src);
+ Object o = e.execute(jc);
+ Assert.assertTrue(o instanceof Set);
+ Assert.assertTrue(((Set) o).contains(1));
+ }
+
+ public static class C192 {
+ public C192() {
+ }
+
+ public static Integer callme(Integer n) {
+ if (n == null) {
+ return null;
+ } else {
+ return n >= 0 ? 42 : -42;
+ }
+ }
+
+ public static Object kickme() {
+ return C192.class;
+ }
+ }
+
+ @Test
+ public void test192() throws Exception {
+ JexlContext jc = new MapContext();
+ jc.set("x.y.z", C192.class);
+ JexlEngine jexl = new JexlBuilder().create();
+ JexlExpression js0 = jexl.createExpression("x.y.z.callme(t)");
+ jc.set("t", null);
+ Assert.assertNull(js0.evaluate(jc));
+ jc.set("t", 10);
+ Assert.assertEquals(42, js0.evaluate(jc));
+ jc.set("t", -10);
+ Assert.assertEquals(-42, js0.evaluate(jc));
+ jc.set("t", null);
+ Assert.assertNull(js0.evaluate(jc));
+ js0 = jexl.createExpression("x.y.z.kickme().callme(t)");
+ jc.set("t", null);
+ Assert.assertNull(js0.evaluate(jc));
+ jc.set("t", 10);
+ Assert.assertEquals(42, js0.evaluate(jc));
+ jc.set("t", -10);
+ Assert.assertEquals(-42, js0.evaluate(jc));
+ 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);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest200.java b/src/test/java/org/apache/commons/jexl3/IssuesTest200.java
new file mode 100644
index 000000000..5f4b7e48e
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest200.java
@@ -0,0 +1,350 @@
+/*
+ * 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.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import java.util.TreeSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for reported issue between JEXL-200 and JEXL-299.
+ */
+@SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class IssuesTest200 extends JexlTestCase {
+ public IssuesTest200() {
+ super("IssuesTest200", null);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ // ensure jul logging is only error to avoid warning in silent mode
+ java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
+ }
+
+ 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 funcs = new HashMap();
+ 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);
+ }
+
+ @Test
+ public void test209a() throws Exception {
+ JexlContext jc = new MapContext();
+ JexlEngine jexl = new JexlBuilder().create();
+ JexlScript e = jexl.createScript("var x = new('java.util.HashMap'); x.a = ()->{return 1}; x['a']()");
+ Object r = e.execute(jc);
+ Assert.assertEquals(1, r);
+ }
+
+ @Test
+ public void test209b() throws Exception {
+ JexlContext jc = new MapContext();
+ JexlEngine jexl = new JexlBuilder().create();
+ JexlScript e = jexl.createScript("var x = new('java.util.HashMap'); x['a'] = ()->{return 1}; x.a()");
+ Object r = e.execute(jc);
+ Assert.assertEquals(1, r);
+ }
+
+ public class T210 {
+ public void npe() {
+ throw new NullPointerException("NPE210");
+ }
+ }
+
+ @Test
+ public void test210() throws Exception {
+ JexlContext jc = new MapContext();
+ jc.set("v210", new T210());
+ JexlEngine jexl = new JexlBuilder().strict(false).silent(false).create();
+ JexlScript e = jexl.createScript("v210.npe()");
+ try {
+ e.execute(jc);
+ Assert.fail("should have thrown an exception");
+ } catch(JexlException xjexl) {
+ Throwable th = xjexl.getCause();
+ Assert.assertEquals("NPE210", th.getMessage());
+ }
+ }
+
+ @Test
+ public void test217() throws Exception {
+ JexlEvalContext jc = new JexlEvalContext();
+ jc.set("foo", new int[]{0, 1, 2, 42});
+ JexlEngine jexl;
+ JexlScript e;
+ Object r;
+ jexl = new JexlBuilder().strict(false).silent(false).create();
+ e = jexl.createScript("foo[3]");
+ r = e.execute(jc);
+ Assert.assertEquals(42, r);
+
+ // cache and fail?
+ jc.set("foo", new int[]{0, 1});
+ jc.setStrict(true);
+ try {
+ r = e.execute(jc);
+ Assert.fail("should have thrown an exception");
+ } catch(JexlException xjexl) {
+ Throwable th = xjexl.getCause();
+ Assert.assertTrue(ArrayIndexOutOfBoundsException.class.equals(th.getClass()));
+ }
+ //
+ jc.setStrict(false);
+ r = e.execute(jc);
+ Assert.assertNull("oob adverted", r);
+ }
+
+
+ @Test
+ public void test221() throws Exception {
+ JexlEvalContext jc = new JexlEvalContext();
+ Map map = new HashMap();
+ map.put("one", 1);
+ jc.set("map", map);
+ JexlEngine jexl = new JexlBuilder().cache(256).create();
+ JexlScript e = jexl.createScript("(x)->{ map[x] }");
+ Object r;
+ r = e.execute(jc, (Object) null);
+ Assert.assertEquals(null, r);
+ r = e.execute(jc, (Object) null);
+ Assert.assertEquals(null, r);
+ r = e.execute(jc, "one");
+ Assert.assertEquals(1, r);
+ }
+
+
+ public static class JexlArithmetic224 extends JexlArithmetic {
+ public JexlArithmetic224(boolean astrict) {
+ super(astrict);
+ }
+
+ protected Object nth(Collection> c, int i) {
+ if (c instanceof List) {
+ // tell engine to use default
+ return JexlEngine.TRY_FAILED;
+ }
+ for (Object o : c) {
+ if (i-- == 0) {
+ return o;
+ }
+ }
+ return null;
+ }
+
+ public Object propertyGet(Collection> c, Number n) {
+ return nth(c, n.intValue());
+ }
+
+ public Object arrayGet(Collection> c, Number n) {
+ return nth(c, n.intValue());
+ }
+
+ public Object call(Collection> c, Number n) {
+ if (c instanceof List) {
+ return ((List) c).get(n.intValue());
+ }
+ return nth(c, n.intValue());
+ }
+ }
+
+ @Test
+ public void test224() throws Exception {
+ List a0 = Arrays.asList("one", "two");
+ Set a1 = new TreeSet(a0);
+ JexlContext jc = new MapContext();
+ JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic224(true)).create();
+ Object r;
+ JexlScript e = jexl.createScript("(map, x)->{ map[x] }");
+ r = e.execute(jc, a0, 1);
+ Assert.assertEquals("two", r);
+ r = e.execute(jc, a1, 1);
+ Assert.assertEquals("two", r);
+ e = jexl.createScript("(map)->{ map.1 }");
+ r = e.execute(jc, a0);
+ Assert.assertEquals("two", r);
+ r = e.execute(jc, a1);
+ Assert.assertEquals("two", r);
+ e = jexl.createScript("(map, x)->{ map(x) }");
+ r = e.execute(jc, a0, 1);
+ Assert.assertEquals("two", r);
+ r = e.execute(jc, a1, 1);
+ Assert.assertEquals("two", r);
+ }
+
+ public static class Context225 extends MapContext {
+ public String bar(){
+ return "bar";
+ }
+ }
+
+ @Test
+ public void test225() throws Exception {
+ Context225 df = new Context225();
+ JexlEngine jexl = new JexlBuilder().create();
+
+ JexlExpression expression = jexl.createExpression("bar()");
+ Assert.assertEquals("bar", expression.evaluate(df));
+ ObjectContext context = new ObjectContext(jexl, df);
+ Assert.assertEquals("bar", expression.evaluate(context));
+ }
+
+ private static void handle(ExecutorService pool, final JexlScript script, final Map payload) {
+ pool.submit(new Runnable() {
+ @Override public void run() {
+ script.execute(new MapContext(payload));
+ }
+ });
+ }
+
+ @Test
+ public void test241() throws Exception {
+ ExecutorService pool;
+ JexlScript script = new JexlBuilder().create().createScript("`${item}`");
+
+ pool = Executors.newFixedThreadPool(4);
+
+ Map m1 = new HashMap();
+ m1.put("item", "A");
+ Map m2 = new HashMap();
+ m2.put("item", "B");
+
+ handle(pool, script, m1);
+ script.execute(new MapContext(m2));
+ pool.shutdown();
+ }
+
+ @Test
+ public void test242() throws Exception {
+ Double a = -40.05d;
+ Double b = -8.01d;
+ Double c = a + b;
+ final JexlContext context = new MapContext();
+ context.set("a", a);
+ context.set("b", b);
+ JexlEngine JEXL_ENGINE = new JexlBuilder().strict(true).silent(true).create();
+ JexlExpression jsp = JEXL_ENGINE.createExpression("a + b");
+ Double e = (Double) jsp.evaluate(context);
+ Assert.assertTrue(Double.doubleToLongBits(e) + " != " + Double.doubleToLongBits(c), c.doubleValue() == e.doubleValue());
+ Assert.assertTrue(Double.doubleToLongBits(e) + " != " + Double.doubleToLongBits(c), a + b == e);
+ }
+
+
+ @Test
+ public void test243a() throws Exception {
+ JexlEngine jexl = new JexlBuilder().cache(32).create();
+ JexlScript script = jexl.createScript("while(true);");
+ try {
+ JexlExpression expr = jexl.createExpression("while(true);");
+ Assert.fail("should have failed!, expr do not allow 'while' statement");
+ } catch (JexlException.Parsing xparse) {
+ // ok
+ } catch (JexlException xother) {
+ // ok
+ }
+ }
+
+ public static class Foo245 {
+ private Object bar = null;
+
+ void setBar(Object bar) {
+ this.bar = bar;
+ }
+
+ public Object getBar() {
+ return bar;
+ }
+ }
+
+ @Test
+ public void test245() throws Exception {
+ MapContext ctx = new MapContext();
+ Foo245 foo245 = new Foo245();
+ ctx.set("foo", foo245);
+
+ JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
+ JexlExpression foobar = engine.createExpression("foo.bar");
+ JexlExpression foobaz = engine.createExpression("foo.baz");
+ JexlExpression foobarbaz = engine.createExpression("foo.bar.baz");
+ // add ambiguity with null & not-null
+ Object[] args = { null, 245 };
+ for(Object arg : args ){
+ foo245.setBar(arg);
+ // ok
+ Assert.assertEquals(foo245.getBar(), foobar.evaluate(ctx));
+ // fail level 1
+ try {
+ foobaz.evaluate(ctx);
+ Assert.fail("foo.baz is not solvable");
+ } catch(JexlException xp) {
+ Assert.assertTrue(xp instanceof JexlException.Property);
+ }
+ // fail level 2
+ try {
+ foobarbaz.evaluate(ctx);
+ Assert.fail("foo.bar.baz is not solvable");
+ } catch(JexlException xp) {
+ Assert.assertTrue(xp instanceof JexlException.Property);
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index 3d808991e..1ae1dbfd6 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -680,10 +680,14 @@ public void testOneLinerVar() throws Exception {
@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,10 +722,13 @@ public void testInterpolationParameter() throws Exception {
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);
}
//
//
-// @Test public void testDeferredTemplate() throws Exception {
+// @Test
+// public void testDeferredTemplate() throws Exception {
// JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(
// "select * from \n"+
// "##for(var c : tables) {\n"+
diff --git a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
index cda8fc13f..4a64f0742 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
@@ -24,7 +24,10 @@
/**
* A JEXL evaluation environment wrapping variables, namespace and options.
*/
-public class JexlEvalContext implements JexlContext, JexlContext.NamespaceResolver, JexlEngine.Options {
+public class JexlEvalContext implements
+ JexlContext,
+ JexlContext.NamespaceResolver,
+ JexlEngine.Options {
/** The marker for the empty vars. */
private static final Map EMPTY_MAP = Collections.emptyMap();
/** The variables.*/
@@ -35,6 +38,8 @@ public class JexlEvalContext implements JexlContext, JexlContext.NamespaceResolv
private Boolean silent = null;
/** Whether the engine should be strict. */
private Boolean strict = null;
+ /** Whether the engine should be cancellable. */
+ private Boolean cancellable = null;
/** Whether the arithmetic should be strict. */
private Boolean mathStrict = null;
/** The math scale the arithmetic should use. */
@@ -109,10 +114,22 @@ public Object resolveNamespace(String name) {
public void clearOptions() {
silent = null;
strict = null;
+ cancellable = null;
mathScale = -1;
mathContext = null;
}
+ /**
+ * Set options from engine.
+ * @param jexl the engine
+ */
+ public void setOptions(JexlEngine jexl) {
+ silent = jexl.isSilent();
+ strict = jexl.isStrict();
+ mathScale = jexl.getArithmetic().getMathScale();
+ mathContext = jexl.getArithmetic().getMathContext();
+ }
+
/**
* Sets whether the engine will throw JexlException during evaluation when an error is triggered.
* @param s true means no JexlException will occur, false allows them
@@ -126,14 +143,27 @@ public Boolean isSilent() {
return this.silent;
}
+ /**
+ * Sets whether the engine will throw JexlException.Cancel during evaluation when interrupted.
+ * @param s true means JexlException.Cancel will be thrown, false implies null will be returned
+ */
+ public void setCancellable(boolean c) {
+ this.cancellable = c ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ @Override
+ public Boolean isCancellable() {
+ return this.cancellable;
+ }
+
/**
* Sets the engine and arithmetic strict flags in one call.
* @param se the engine strict flag
* @param sa the arithmetic strict flag
*/
- public void setStrict(boolean se, boolean sa) {
- this.strict = se ? Boolean.TRUE : Boolean.FALSE;
- this.mathStrict = sa ? Boolean.TRUE : Boolean.FALSE;
+ public void setStrict(Boolean se, Boolean sa) {
+ this.strict = se == null? null : se ? Boolean.TRUE : Boolean.FALSE;
+ this.mathStrict = sa == null? null : sa ? Boolean.TRUE : Boolean.FALSE;
}
/**
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index 0e2a8bed7..8db318bdb 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -89,7 +89,7 @@ public void testStringLit() throws Exception {
*/
JexlContext jc = new MapContext();
jc.set("foo", new Foo());
- assertExpression(jc, "foo.get(\"woogie\")", "Repeat : woogie");
+ assertExpression(jc, "foo.repeat(\"woogie\")", "Repeat : woogie");
}
@Test
@@ -213,21 +213,20 @@ public void testSize() throws Exception {
// support generic int size() method
BitSet bitset = new BitSet(5);
jc.set("bitset", bitset);
-//
-// assertExpression(jc, "size(s)", new Integer(5));
-// assertExpression(jc, "size(array)", new Integer(5));
-// assertExpression(jc, "size(list)", new Integer(5));
-// assertExpression(jc, "size(map)", new Integer(5));
-// assertExpression(jc, "size(set)", new Integer(5));
-// assertExpression(jc, "size(bitset)", new Integer(64));
-// assertExpression(jc, "list.size()", new Integer(5));
-// assertExpression(jc, "map.size()", new Integer(5));
-// assertExpression(jc, "set.size()", new Integer(5));
-// assertExpression(jc, "bitset.size()", new Integer(64));
-//
-// assertExpression(jc, "list.get(size(list) - 1)", "5");
-// assertExpression(jc, "list[size(list) - 1]", "5");
- // here
+
+ assertExpression(jc, "size(s)", new Integer(5));
+ assertExpression(jc, "size(array)", new Integer(5));
+ assertExpression(jc, "size(list)", new Integer(5));
+ assertExpression(jc, "size(map)", new Integer(5));
+ assertExpression(jc, "size(set)", new Integer(5));
+ assertExpression(jc, "size(bitset)", new Integer(64));
+ assertExpression(jc, "list.size()", new Integer(5));
+ assertExpression(jc, "map.size()", new Integer(5));
+ assertExpression(jc, "set.size()", new Integer(5));
+ assertExpression(jc, "bitset.size()", new Integer(64));
+
+ assertExpression(jc, "list.get(size(list) - 1)", "5");
+ assertExpression(jc, "list[size(list) - 1]", "5");
assertExpression(jc, "list.get(list.size() - 1)", "5");
}
@@ -236,15 +235,17 @@ public void testSizeAsProperty() throws Exception {
JexlContext jc = new MapContext();
Map map = new HashMap();
map.put("size", "cheese");
+ map.put("si & ze", "cheese");
jc.set("map", map);
jc.set("foo", new Foo());
assertExpression(jc, "map['size']", "cheese");
-// PR - unsure whether or not we should support map.size or force usage of the above 'escaped' version
-// assertExpression(jc, "map.size", "cheese");
- assertExpression(jc, "foo.getSize()", new Integer(22));
- // failing assertion for size property
- //assertExpression(jc, "foo.size", new Integer(22));
+ assertExpression(jc, "map['si & ze']", "cheese");
+ assertExpression(jc, "map.'si & ze'", "cheese");
+ assertExpression(jc, "map.size()", 2);
+ assertExpression(jc, "size(map)", 2);
+ assertExpression(jc, "foo.getSize()", 22);
+ assertExpression(jc, "foo.'size'", 22);
}
/**
@@ -265,7 +266,7 @@ public void testNew() throws Exception {
Assert.assertEquals(expr.toString(), new Float(100.0), value);
expr = JEXL.createExpression("new(foo).quux");
value = expr.evaluate(jc);
- Assert.assertEquals(expr.toString(), "Repeat : quux", value);
+ Assert.assertEquals(expr.toString(), "String : quux", value);
}
/**
@@ -686,7 +687,7 @@ public void testAssignment() throws Exception {
Foo foo = new Foo();
jc.set("foo", foo);
Parser parser = new Parser(new StringReader(";"));
- parser.parse(null, "aString = 'World';", null, false, false);
+ parser.parse(null, new JexlFeatures().register(false), "aString = 'World';", null);
assertExpression(jc, "hello = 'world'", "world");
Assert.assertEquals("hello variable not changed", "world", jc.get("hello"));
diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
index cb052f7b1..a4f18e961 100644
--- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java
@@ -33,6 +33,7 @@ public LambdaTest() {
super("LambdaTest");
}
+ @Test
public void testScriptArguments() throws Exception {
JexlEngine jexl = new Engine();
JexlScript s = jexl.createScript(" x + x ", "x");
diff --git a/src/test/java/org/apache/commons/jexl3/MethodTest.java b/src/test/java/org/apache/commons/jexl3/MethodTest.java
index 8e38d286d..9849fcfc3 100644
--- a/src/test/java/org/apache/commons/jexl3/MethodTest.java
+++ b/src/test/java/org/apache/commons/jexl3/MethodTest.java
@@ -21,6 +21,7 @@
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.junit.Asserter;
import java.util.Arrays;
+import java.util.Date;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -191,6 +192,25 @@ public static int PLUS20(int num) {
public static Class> NPEIfNull(Object x) {
return x.getClass();
}
+
+ public Object over(String f, int i) {
+ return f + " + " + i;
+ }
+
+ public Object over(String f, Date g) {
+ return f + " + " + g;
+ }
+
+ public Object over(String f, String g) {
+ return f + " + " + g;
+ }
+ }
+
+ public static class FunctorOver extends Functor {
+
+ public Object over(Object f, Object g) {
+ return f + " + " + g;
+ }
}
@Test
@@ -210,6 +230,31 @@ public void testInvoke() throws Exception {
} catch (Exception xj0) {
// ignore
}
+
+ Object result;
+ try {
+ result = JEXL.invokeMethod(func, "over", "foo", 42);
+ Assert.assertEquals("foo + 42", result);
+ } catch (Exception xj0) {
+ // ignore
+ result = xj0;
+ }
+
+ try {
+ result = JEXL.invokeMethod(func, "over", null, null);
+ Assert.fail("method should have thrown!");
+ } catch (Exception xj0) {
+ // ignore
+ result = xj0;
+ }
+
+ func = new FunctorOver();
+ try {
+ result = JEXL.invokeMethod(func, "over", null, null);
+ Assert.assertEquals("null + null", result);
+ } catch (Exception xj0) {
+ Assert.fail("method should not have thrown!");
+ }
}
/**
@@ -513,7 +558,7 @@ public void testScriptCall() throws Exception {
final JexlArithmetic ja = JEXL.getArithmetic();
JexlMethod mplus = new JexlMethod() {
@Override
- public Object invoke(Object obj, Object[] params) throws Exception {
+ public Object invoke(Object obj, Object ... params) throws Exception {
if (obj instanceof Map, ?>) {
return ja.add(params[0], params[1]);
} else {
@@ -522,7 +567,7 @@ public Object invoke(Object obj, Object[] params) throws Exception {
}
@Override
- public Object tryInvoke(String name, Object obj, Object[] params) {
+ public Object tryInvoke(String name, Object obj, Object ... params) {
try {
if ("plus".equals(name)) {
return invoke(obj, params);
@@ -579,4 +624,72 @@ public void testFizzCall() throws Exception {
o = bar.execute(context);
Assert.assertEquals("Wrong choice", "champaign", o);
}
+
+ public static class ZArithmetic extends JexlArithmetic {
+ public ZArithmetic(boolean astrict) {
+ super(astrict);
+ }
+
+ public int zzzz(int z) {
+ return 38 + z;
+ }
+ }
+
+ public static class ZSpace {
+ public int zzz(int z) {
+ return 39 + z;
+ }
+ }
+
+ public static class ZContext extends MapContext {
+ public ZContext(Map map) {
+ super(map);
+ }
+
+ public int zz(int z) {
+ return 40 + z;
+ }
+
+ public int z(int z) {
+ return 181 + z;
+ }
+ }
+
+ @Test
+ public void testVariousFunctionLocation() throws Exception {
+ // see JEXL-190
+ Map vars = new HashMap();
+ Map funcs = new HashMap();
+ funcs.put(null, new ZSpace());
+ JexlEngine jexl = new JexlBuilder().namespaces(funcs).arithmetic(new ZArithmetic(true)).create();
+
+ JexlContext zjc = new ZContext(vars); // that implements a z(int x) function
+ String z41 = "z(41)";
+ JexlScript callz41 = jexl.createScript(z41);
+ Object onovar = callz41.execute(zjc);
+ Assert.assertEquals(222, onovar);
+
+ // override z() with global var
+ JexlScript z241 = jexl.createScript("(x)->{ return x + 241}");
+ vars.put("z", z241);
+ Object oglobal = callz41.execute(zjc);
+ Assert.assertEquals(282, oglobal);
+ // clear global and execute again
+ vars.remove("z");
+ onovar = callz41.execute(zjc);
+ Assert.assertEquals(222, onovar);
+
+ // override z() with local var
+ String slocal = "var z = (x)->{ return x + 141}; z(1)";
+ JexlScript jlocal = jexl.createScript(slocal);
+ Object olocal = jlocal.execute(zjc);
+ Assert.assertEquals(142, olocal);
+
+ // and now try the context, the null namespace and the arithmetic
+ Assert.assertEquals(42, jexl.createScript("zz(2)").execute(zjc));
+ Assert.assertEquals(42, jexl.createScript("zzz(3)").execute(zjc));
+ Assert.assertEquals(42, jexl.createScript("zzzz(4)").execute(zjc));
+ }
+
+
}
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index 0463aac71..3eff5790d 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -39,16 +39,31 @@ public PragmaTest() {
public void testPragmas() throws Exception {
JexlContext jc = new MapContext();
try {
- JexlScript script = JEXL.createScript("#pragma one 1\n#pragma the.very.hard 'truth'\n2;");
- Assert.assertTrue(script != null);
- Map pragmas = script.getPragmas();
- Assert.assertEquals(2, pragmas.size());
- Assert.assertEquals(1, pragmas.get("one"));
- Assert.assertEquals("truth", pragmas.get("the.very.hard"));
- } catch(JexlException xjexl) {
+ JexlScript script = JEXL.createScript("#pragma one 1\n#pragma the.very.hard 'truth'\n2;");
+ Assert.assertTrue(script != null);
+ Map pragmas = script.getPragmas();
+ Assert.assertEquals(2, pragmas.size());
+ Assert.assertEquals(1, pragmas.get("one"));
+ Assert.assertEquals("truth", pragmas.get("the.very.hard"));
+ } catch (JexlException xjexl) {
String s = xjexl.toString();
}
}
+ @Test
+ public void testJxltPragmas() throws Exception {
+ JexlContext jc = new MapContext();
+ try {
+ JxltEngine engine = new JexlBuilder().create().createJxltEngine();
+ JxltEngine.Template tscript = engine.createTemplate("$$ #pragma one 1\n$$ #pragma the.very.hard 'truth'\n2;");
+ Assert.assertTrue(tscript != null);
+ Map pragmas = tscript.getPragmas();
+ Assert.assertEquals(2, pragmas.size());
+ Assert.assertEquals(1, pragmas.get("one"));
+ Assert.assertEquals("truth", pragmas.get("the.very.hard"));
+ } catch (JexlException xjexl) {
+ String s = xjexl.toString();
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
index 09137c57b..7d81cdb0f 100644
--- a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
@@ -20,6 +20,7 @@
import java.util.Map;
import org.apache.commons.jexl3.internal.Debugger;
+import org.apache.commons.jexl3.internal.introspection.IndexedType;
import org.apache.commons.jexl3.junit.Asserter;
import org.junit.Assert;
import org.junit.Before;
@@ -45,8 +46,8 @@ public void setUp() {
asserter = new Asserter(JEXL);
}
-
- @Test public void testPropertyProperty() throws Exception {
+ @Test
+ public void testPropertyProperty() throws Exception {
Integer i42 = Integer.valueOf(42);
Integer i43 = Integer.valueOf(43);
String s42 = "fourty-two";
@@ -71,11 +72,14 @@ public void setUp() {
}
}
- public static class Container {
+ /**
+ * A base property container; can only set from string.
+ */
+ public static class PropertyContainer {
String value0;
int value1;
- public Container(String name, int number) {
+ public PropertyContainer(String name, int number) {
value0 = name;
value1 = number;
}
@@ -90,16 +94,104 @@ public Object getProperty(String name) {
}
}
- public Object getProperty(int ref) {
- if (0 == ref) {
- return value0;
- } else if (1 == ref) {
- return value1;
- } else {
+ public void setProperty(String name, String value) {
+ if ("name".equals(name)) {
+ this.value0 = value.toUpperCase();
+ }
+ if ("number".equals(name)) {
+ this.value1 = Integer.parseInt(value) + 1000;
+ }
+ }
+ }
+
+
+ /**
+ * Overloads propertySet.
+ */
+ public static class PropertyArithmetic extends JexlArithmetic {
+ int ncalls = 0;
+
+ public PropertyArithmetic(boolean astrict) {
+ super(astrict);
+ }
+
+ public Object propertySet(IndexedType.IndexedContainer map, String key, Integer value) {
+ if (map.getContainerClass().equals(PropertyContainer.class)
+ && map.getContainerName().equals("property")) {
+ try {
+ map.set(key, value.toString());
+ ncalls += 1;
+ } catch (Exception xany) {
+ throw new JexlException.Operator(null, key + "." + value.toString(), xany);
+ }
return null;
}
+ return JexlEngine.TRY_FAILED;
}
+ public int getCalls() {
+ return ncalls;
+ }
+ }
+
+ @Test
+ public void testInnerViaArithmetic() throws Exception {
+ PropertyArithmetic pa = new PropertyArithmetic(true);
+ JexlEngine jexl = new JexlBuilder().arithmetic(pa).debug(true).strict(true).cache(32).create();
+ PropertyContainer quux = new PropertyContainer("bar", 169);
+ Object result;
+
+ JexlScript getName = jexl.createScript("foo.property.name", "foo");
+ result = getName.execute(null, quux);
+ Assert.assertEquals("bar", result);
+ int calls = pa.getCalls();
+ JexlScript setName = jexl.createScript("foo.property.name = $0", "foo", "$0");
+ setName.execute(null, quux, 123);
+ result = getName.execute(null, quux);
+ Assert.assertEquals("123", result);
+ setName.execute(null, quux, 456);
+ result = getName.execute(null, quux);
+ Assert.assertEquals("456", result);
+ Assert.assertEquals(calls + 2, pa.getCalls());
+ setName.execute(null, quux, "quux");
+ result = getName.execute(null, quux);
+ Assert.assertEquals("QUUX", result);
+ Assert.assertEquals(calls + 2, pa.getCalls());
+
+ JexlScript getNumber = jexl.createScript("foo.property.number", "foo");
+ result = getNumber.execute(null, quux);
+ Assert.assertEquals(169, result);
+ JexlScript setNumber = jexl.createScript("foo.property.number = $0", "foo", "$0");
+ setNumber.execute(null, quux, 42);
+ result = getNumber.execute(null, quux);
+ Assert.assertEquals(1042, result);
+ setNumber.execute(null, quux, 24);
+ result = getNumber.execute(null, quux);
+ Assert.assertEquals(1024, result);
+ Assert.assertEquals(calls + 4, pa.getCalls());
+ setNumber.execute(null, quux, "42");
+ result = getNumber.execute(null, quux);
+ Assert.assertEquals(1042, result);
+ Assert.assertEquals(calls + 4, pa.getCalls());
+ }
+
+ public static class Container extends PropertyContainer {
+ public Container(String name, int number) {
+ super(name, number);
+ }
+
+ public Object getProperty(int ref) {
+ switch (ref) {
+ case 0:
+ return value0;
+ case 1:
+ return value1;
+ default:
+ return null;
+ }
+ }
+
+ @Override
public void setProperty(String name, String value) {
if ("name".equals(name)) {
this.value0 = value;
@@ -125,11 +217,15 @@ public void setProperty(int ref, int value) {
}
}
- @Test public void testInnerProperty() throws Exception {
+ @Test
+ public void testInnerProperty() throws Exception {
+ PropertyArithmetic pa = new PropertyArithmetic(true);
+ JexlEngine jexl = new JexlBuilder().arithmetic(pa).debug(true).strict(true).cache(32).create();
Container quux = new Container("quux", 42);
JexlScript get;
Object result;
+ int calls = pa.getCalls();
JexlScript getName = JEXL.createScript("foo.property.name", "foo");
result = getName.execute(null, quux);
Assert.assertEquals("quux", result);
@@ -173,10 +269,13 @@ public void setProperty(int ref, int value) {
Assert.assertEquals(24, result);
result = get1.execute(null, quux);
Assert.assertEquals(24, result);
+
+ Assert.assertEquals(calls, pa.getCalls());
}
- @Test public void testStringIdentifier() throws Exception {
+ @Test
+ public void testStringIdentifier() throws Exception {
Map foo = new HashMap();
JexlContext jc = new MapContext();
diff --git a/src/test/java/org/apache/commons/jexl3/PublicFieldsTest.java b/src/test/java/org/apache/commons/jexl3/PublicFieldsTest.java
index 54edd7f3a..1f594daf3 100644
--- a/src/test/java/org/apache/commons/jexl3/PublicFieldsTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PublicFieldsTest.java
@@ -33,6 +33,7 @@ public class PublicFieldsTest extends JexlTestCase {
*/
public static class Inner {
public double aDouble = 42.0;
+ public static double NOT42 = -42.0;
}
/**
@@ -61,14 +62,16 @@ public void setUp() {
ctxt.set("pub", pub);
}
- @Test public void testGetInt() throws Exception {
+ @Test
+ public void testGetInt() throws Exception {
JexlExpression get = JEXL.createExpression("pub.anInt");
Assert.assertEquals(42, get.evaluate(ctxt));
JEXL.setProperty(pub, "anInt", -42);
Assert.assertEquals(-42, get.evaluate(ctxt));
}
- @Test public void testSetInt() throws Exception {
+ @Test
+ public void testSetInt() throws Exception {
JexlExpression set = JEXL.createExpression("pub.anInt = value");
ctxt.set("value", -42);
Assert.assertEquals(-42, set.evaluate(ctxt));
@@ -83,14 +86,16 @@ public void setUp() {
} catch(JexlException xjexl) {}
}
- @Test public void testGetString() throws Exception {
+ @Test
+ public void testGetString() throws Exception {
JexlExpression get = JEXL.createExpression("pub.aString");
Assert.assertEquals(LOWER42, get.evaluate(ctxt));
JEXL.setProperty(pub, "aString", UPPER42);
Assert.assertEquals(UPPER42, get.evaluate(ctxt));
}
- @Test public void testSetString() throws Exception {
+ @Test
+ public void testSetString() throws Exception {
JexlExpression set = JEXL.createExpression("pub.aString = value");
ctxt.set("value", UPPER42);
Assert.assertEquals(UPPER42, set.evaluate(ctxt));
@@ -100,14 +105,16 @@ public void setUp() {
Assert.assertEquals(LOWER42, JEXL.getProperty(pub, "aString"));
}
- @Test public void testGetInnerDouble() throws Exception {
+ @Test
+ public void testGetInnerDouble() throws Exception {
JexlExpression get = JEXL.createExpression("pub.inner.aDouble");
Assert.assertEquals(42.0, get.evaluate(ctxt));
JEXL.setProperty(pub, "inner.aDouble", -42);
Assert.assertEquals(-42.0, get.evaluate(ctxt));
}
- @Test public void testSetInnerDouble() throws Exception {
+ @Test
+ public void testSetInnerDouble() throws Exception {
JexlExpression set = JEXL.createExpression("pub.inner.aDouble = value");
ctxt.set("value", -42.0);
Assert.assertEquals(-42.0, set.evaluate(ctxt));
@@ -122,4 +129,25 @@ public void setUp() {
} catch(JexlException xjexl) {}
}
+ public enum Gender { MALE, FEMALE };
+
+ @Test
+ public void testGetEnum() throws Exception {
+ ctxt.set("com.jexl.gender", Gender.class);
+ String src = "x = com.jexl.gender.FEMALE";
+ JexlScript script = JEXL.createScript(src);
+ Object result = script.execute(ctxt);
+ Assert.assertEquals(Gender.FEMALE, result);
+ Assert.assertEquals(Gender.FEMALE, ctxt.get("x"));
+ }
+
+ @Test
+ public void testGetStaticField() throws Exception {
+ ctxt.set("com.jexl", Inner.class);
+ String src = "x = com.jexl.NOT42";
+ JexlScript script = JEXL.createScript(src);
+ Object result = script.execute(ctxt);
+ Assert.assertEquals(Inner.NOT42, result);
+ Assert.assertEquals(Inner.NOT42, ctxt.get("x"));
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java b/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
index e1f3e8e64..97fe8c18f 100644
--- a/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
+++ b/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
@@ -66,10 +66,14 @@ public Boolean isSilent() {
@Override
public Boolean isStrict() {
- // egnie
return options == null? null : options.isStrict();
}
+ @Override
+ public Boolean isCancellable() {
+ return options == null? null : options.isCancellable();
+ }
+
@Override
public Boolean isStrictArithmetic() {
return options == null? null : options.isStrict();
diff --git a/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java b/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
index ec15877e3..0e4e34bc2 100644
--- a/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
@@ -16,21 +16,29 @@
*/
package org.apache.commons.jexl3;
+import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
+import java.util.concurrent.Semaphore;
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;
/**
* Tests around asynchronous script execution and interrupts.
*/
+@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");
}
@@ -42,50 +50,104 @@ public void testFuture() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(future);
+ Object t = 42;
try {
- future.get(100, TimeUnit.MILLISECONDS);
+ t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
+ future.cancel(true);
+ } finally {
+ executor.shutdown();
}
- Thread.sleep(100);
- future.cancel(true);
Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
}
@Test
- public void testCallable() throws Exception {
- JexlScript e = JEXL.createScript("while(true);");
- Callable c = e.callable(null);
+ public void testCallableCancel() throws Exception {
+ List lr = null;
+ final Semaphore latch = new Semaphore(0);
+ JexlContext ctxt = new MapContext();
+ ctxt.set("latch", latch);
+
+ JexlScript e = JEXL.createScript("latch.release(); while(true);");
+ final Script.Callable c = (Script.Callable) e.callable(ctxt);
+ Object t = 42;
+ Callable kc = new Callable() {
+ @Override
+ public Object call() throws Exception {
+ latch.acquire();
+ 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 {
+ lr = executor.shutdownNow();
+ }
+ Assert.assertTrue(c.isCancelled());
+ Assert.assertTrue(lr == null || lr.isEmpty());
+ }
+
+ @Test
+ public void testCallableTimeout() throws Exception {
+ List lr = null;
+ final Semaphore latch = new Semaphore(0);
+ JexlContext ctxt = new MapContext();
+ ctxt.set("latch", latch);
+
+ JexlScript e = JEXL.createScript("latch.release(); while(true);");
+ Callable c = e.callable(ctxt);
+ Object t = 42;
ExecutorService executor = Executors.newFixedThreadPool(1);
Future> future = executor.submit(c);
try {
- future.get(100, TimeUnit.MILLISECONDS);
+ latch.acquire();
+ t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
+ future.cancel(true);
+ } finally {
+ lr = executor.shutdownNow();
}
- future.cancel(true);
Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCallableClosure() throws Exception {
+ List lr = null;
JexlScript e = JEXL.createScript("function(t) {while(t);}");
Callable c = e.callable(null, Boolean.TRUE);
+ Object t = 42;
ExecutorService executor = Executors.newFixedThreadPool(1);
Future> future = executor.submit(c);
try {
- future.get(100, TimeUnit.MILLISECONDS);
+ t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
+ future.cancel(true);
+ } finally {
+ lr = executor.shutdownNow();
}
- future.cancel(true);
Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
public static class TestContext extends MapContext implements JexlContext.NamespaceResolver {
@@ -110,104 +172,327 @@ public int waitInterrupt(int s) {
}
public int runForever() {
- boolean x = false;
while (true) {
- if (x) {
+ if (Thread.currentThread().isInterrupted()) {
break;
}
}
return 1;
}
+
+ public int interrupt() throws InterruptedException {
+ Thread.currentThread().interrupt();
+ return 42;
+ }
+
+ public void sleep(long millis) throws InterruptedException {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException xint) {
+ throw xint;
+ }
+ }
+
+ public int hangs(Object t) {
+ return 1;
+ }
}
@Test
public void testNoWait() throws Exception {
+ List lr = null;
JexlScript e = JEXL.createScript("wait(0)");
Callable c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
- Future> future = executor.submit(c);
- Object t = future.get(2, TimeUnit.SECONDS);
- Assert.assertTrue(future.isDone());
- Assert.assertEquals(0, t);
+ try {
+ Future> future = executor.submit(c);
+ Object t = future.get(2, TimeUnit.SECONDS);
+ Assert.assertTrue(future.isDone());
+ Assert.assertEquals(0, t);
+ } finally {
+ lr = executor.shutdownNow();
+ }
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testWait() throws Exception {
+ List lr = null;
JexlScript e = JEXL.createScript("wait(1)");
Callable c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
- Future> future = executor.submit(c);
- Object t = future.get(2, TimeUnit.SECONDS);
- Assert.assertEquals(1, t);
+ try {
+ Future> future = executor.submit(c);
+ Object t = future.get(2, TimeUnit.SECONDS);
+ Assert.assertEquals(1, t);
+ } finally {
+ lr = executor.shutdownNow();
+ }
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelWait() throws Exception {
+ List lr = null;
JexlScript e = JEXL.createScript("wait(10)");
Callable c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
- Future> future = executor.submit(c);
try {
- future.get(100, TimeUnit.MILLISECONDS);
- Assert.fail("should have timed out");
- } catch (TimeoutException xtimeout) {
- // ok, ignore
+ Future> future = executor.submit(c);
+ Object t = 42;
+ try {
+ t = future.get(100, TimeUnit.MILLISECONDS);
+ Assert.fail("should have timed out");
+ } catch (TimeoutException xtimeout) {
+ // ok, ignore
+ future.cancel(true);
+ }
+ Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
+ } finally {
+ lr = executor.shutdownNow();
}
- future.cancel(true);
- Assert.assertTrue(future.isCancelled());
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelWaitInterrupt() throws Exception {
+ List lr = null;
JexlScript e = JEXL.createScript("waitInterrupt(42)");
Callable c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
Future> future = executor.submit(c);
+ Object t = 42;
+
try {
- future.get(100, TimeUnit.MILLISECONDS);
+ t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
+ future.cancel(true);
+ } finally {
+ lr = executor.shutdownNow();
}
- future.cancel(true);
Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelForever() throws Exception {
- JexlScript e = JEXL.createScript("runForever()");
- Callable c = e.callable(new TestContext());
+ List lr = null;
+ final Semaphore latch = new Semaphore(0);
+ JexlContext ctxt = new TestContext();
+ ctxt.set("latch", latch);
+
+ JexlScript e = JEXL.createScript("latch.release(); runForever()");
+ Callable c = e.callable(ctxt);
ExecutorService executor = Executors.newFixedThreadPool(1);
Future> future = executor.submit(c);
+ Object t = 42;
+
try {
- future.get(100, TimeUnit.MILLISECONDS);
+ latch.acquire();
+ t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
// ok, ignore
+ future.cancel(true);
+ } finally {
+ lr = executor.shutdownNow();
}
- future.cancel(true);
Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
}
@Test
public void testCancelLoopWait() throws Exception {
+ List lr = null;
JexlScript e = JEXL.createScript("while (true) { wait(10) }");
Callable c = e.callable(new TestContext());
ExecutorService executor = Executors.newFixedThreadPool(1);
Future> future = executor.submit(c);
+ Object t = 42;
+
try {
- future.get(100, TimeUnit.MILLISECONDS);
+ t = future.get(100, TimeUnit.MILLISECONDS);
Assert.fail("should have timed out");
} catch (TimeoutException xtimeout) {
- // ok, ignore
+ future.cancel(true);
+ } finally {
+ lr = executor.shutdownNow();
}
- future.cancel(true);
Assert.assertTrue(future.isCancelled());
+ Assert.assertEquals(42, t);
+ Assert.assertTrue(lr.isEmpty());
+ }
+
+ @Test
+ public void testInterruptVerboseStrict() throws Exception {
+ runInterrupt(new JexlBuilder().silent(false).strict(true).create());
+ }
+
+ @Test
+ public void testInterruptVerboseLenient() throws Exception {
+ runInterrupt(new JexlBuilder().silent(false).strict(false).create());
+ }
+
+ @Test
+ public void testInterruptSilentStrict() throws Exception {
+ runInterrupt(new JexlBuilder().silent(true).strict(true).create());
+ }
+
+ @Test
+ public void testInterruptSilentLenient() throws Exception {
+ runInterrupt(new JexlBuilder().silent(true).strict(false).create());
+ }
+
+ @Test
+ public void testInterruptCancellable() throws Exception {
+ runInterrupt(new JexlBuilder().silent(true).strict(true).cancellable(true).create());
+ }
+
+ /**
+ * Redundant test with previous ones but impervious to JEXL engine configuation.
+ * @param silent silent engine flag
+ * @param strict strict (aka not lenient) engine flag
+ * @throws Exception if there is a regression
+ */
+ private void runInterrupt(JexlEngine jexl) throws Exception {
+ List lr = null;
+ ExecutorService exec = Executors.newFixedThreadPool(2);
+ try {
+ JexlContext ctxt = new TestContext();
+
+ // run an interrupt
+ JexlScript sint = jexl.createScript("interrupt(); return 42");
+ Object t = null;
+ Script.Callable c = (Script.Callable) sint.callable(ctxt);
+ try {
+ t = c.call();
+ if (c.isCancellable()) {
+ Assert.fail("should have thrown a Cancel");
+ }
+ } catch (JexlException.Cancel xjexl) {
+ if (!c.isCancellable()) {
+ Assert.fail("should not have thrown " + xjexl);
+ }
+ }
+ Assert.assertTrue(c.isCancelled());
+ Assert.assertNotEquals(42, t);
+
+ // self interrupt
+ Future f = null;
+ c = (Script.Callable) sint.callable(ctxt);
+ try {
+ f = exec.submit(c);
+ t = f.get();
+ if (c.isCancellable()) {
+ Assert.fail("should have thrown a Cancel");
+ }
+ } catch (ExecutionException xexec) {
+ if (!c.isCancellable()) {
+ Assert.fail("should not have thrown " + xexec);
+ }
+ }
+ Assert.assertTrue(c.isCancelled());
+ Assert.assertNotEquals(42, t);
+
+ // timeout a sleep
+ JexlScript ssleep = jexl.createScript("sleep(30000); return 42");
+ try {
+ f = exec.submit(ssleep.callable(ctxt));
+ t = f.get(100L, TimeUnit.MILLISECONDS);
+ Assert.fail("should timeout");
+ } catch (TimeoutException xtimeout) {
+ if (f != null) {
+ f.cancel(true);
+ }
+ }
+ Assert.assertNotEquals(42, t);
+
+ // cancel a sleep
+ try {
+ final Future fc = exec.submit(ssleep.callable(ctxt));
+ Runnable cancels = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(200L);
+ } catch (Exception xignore) {
+
+ }
+ fc.cancel(true);
+ }
+ };
+ exec.submit(cancels);
+ t = f.get(100L, TimeUnit.MILLISECONDS);
+ Assert.fail("should be cancelled");
+ } catch (CancellationException xexec) {
+ // this is the expected result
+ }
+
+ // timeout a while(true)
+ JexlScript swhile = jexl.createScript("while(true); return 42");
+ try {
+ f = exec.submit(swhile.callable(ctxt));
+ t = f.get(100L, TimeUnit.MILLISECONDS);
+ Assert.fail("should timeout");
+ } catch (TimeoutException xtimeout) {
+ if (f != null) {
+ f.cancel(true);
+ }
+ }
+ Assert.assertNotEquals(42, t);
+
+ // cancel a while(true)
+ try {
+ final Future fc = exec.submit(swhile.callable(ctxt));
+ Runnable cancels = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(200L);
+ } catch (Exception xignore) {
+
+ }
+ fc.cancel(true);
+ }
+ };
+ exec.submit(cancels);
+ t = fc.get();
+ Assert.fail("should be cancelled");
+ } catch (CancellationException xexec) {
+ // this is the expected result
+ }
+ Assert.assertNotEquals(42, t);
+ } finally {
+ lr = exec.shutdownNow();
+ }
+ Assert.assertTrue(lr.isEmpty());
+ }
+
+ @Test
+ public void testHangs() throws Exception {
+ JexlScript e = JEXL.createScript("hangs()");
+ Callable c = e.callable(new TestContext());
+
+ ExecutorService executor = Executors.newFixedThreadPool(1);
+ try {
+ Future> future = executor.submit(c);
+ Object t = future.get(1, TimeUnit.SECONDS);
+ Assert.fail("hangs should not be solved");
+ } catch(ExecutionException xexec) {
+ Assert.assertTrue(xexec.getCause() instanceof JexlException.Method);
+ } finally {
+ executor.shutdown();
+ }
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/ScriptTest.java b/src/test/java/org/apache/commons/jexl3/ScriptTest.java
index 1bdf2c56c..0921c168f 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.
@@ -53,7 +54,8 @@ public ScriptTest() {
/**
* Test creating a script from a string.
*/
- @Test public void testSimpleScript() throws Exception {
+ @Test
+ public void testSimpleScript() throws Exception {
String code = "while (x < 10) x = x + 1;";
JexlScript s = JEXL.createScript(code);
JexlContext jc = new MapContext();
@@ -64,7 +66,8 @@ public ScriptTest() {
Assert.assertEquals("getText is wrong", code, s.getSourceText());
}
- @Test public void testScriptFromFile() throws Exception {
+ @Test
+ public void testScriptFromFile() throws Exception {
File testScript = new File(TEST1);
JexlScript s = JEXL.createScript(testScript);
JexlContext jc = new MapContext();
@@ -74,8 +77,20 @@ public ScriptTest() {
Assert.assertEquals("Wrong result", new Integer(7), result);
}
- @Test public void testScriptFromURL() throws Exception {
- URL testUrl = new File("src/test/scripts/test1.jexl").toURI().toURL();
+ @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(TEST1).toURI().toURL();
JexlScript s = JEXL.createScript(testUrl);
JexlContext jc = new MapContext();
jc.set("out", System.out);
@@ -84,7 +99,19 @@ public ScriptTest() {
Assert.assertEquals("Wrong result", new Integer(7), result);
}
- @Test public void testScriptUpdatesContext() throws Exception {
+ @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);
JexlScript s = JEXL.createScript(jexlCode);
diff --git a/src/test/java/org/apache/commons/jexl3/SetLiteralTest.java b/src/test/java/org/apache/commons/jexl3/SetLiteralTest.java
index 8617789ec..218077b4b 100644
--- a/src/test/java/org/apache/commons/jexl3/SetLiteralTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SetLiteralTest.java
@@ -17,6 +17,7 @@
package org.apache.commons.jexl3;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.Assert;
@@ -87,6 +88,16 @@ public void testSetLiteralWithOneEntryBlock() throws Exception {
Assert.assertTrue(check.equals(o));
}
+ @Test
+ public void testSetLiteralWithOneNestedSet() throws Exception {
+ JexlScript e = JEXL.createScript("{ { 'foo' } }");
+ JexlContext jc = new MapContext();
+
+ Object o = e.execute(jc);
+ Set> check = createSet(createSet("foo"));
+ Assert.assertTrue(check.equals(o));
+ }
+
@Test
public void testSetLiteralWithNumbers() throws Exception {
JexlExpression e = JEXL.createExpression("{ 5.0 , 10 }");
@@ -100,6 +111,7 @@ public void testSetLiteralWithNumbers() throws Exception {
@Test
public void testSetLiteralWithNulls() throws Exception {
String[] exprs = {
+ "{ }",
"{ 10 }",
"{ 10 , null }",
"{ 10 , null , 20}",
@@ -107,6 +119,7 @@ public void testSetLiteralWithNulls() throws Exception {
"{ null, '10' , 20 }"
};
Set>[] checks = {
+ Collections.emptySet(),
createSet(new Integer(10)),
createSet(new Integer(10), null),
createSet(new Integer(10), null, new Integer(20)),
diff --git a/src/test/java/org/apache/commons/jexl3/SideEffectTest.java b/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
index f90f69f0c..55f5ac0eb 100644
--- a/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
+++ b/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
@@ -16,9 +16,17 @@
*/
package org.apache.commons.jexl3;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
+import java.util.logging.Level;
import org.apache.commons.jexl3.junit.Asserter;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -83,6 +91,45 @@ public void testSideEffectVar() throws Exception {
Assert.assertEquals(context.get("foo"), (long)(i41 ^ 2));
}
+ @Test
+ public void testSideEffectVarDots() throws Exception {
+ Map 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);
@@ -120,6 +167,43 @@ public void testSideEffectArray() throws Exception {
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);
@@ -440,4 +524,147 @@ public JexlOperator selfXor(Var lhs, Var rhs) {
return JexlOperator.ASSIGN;
}
}
+
+ /**
+ * An arithmetic that implements 2 selfAdd methods.
+ */
+ public static class Arithmetic246 extends JexlArithmetic {
+ public Arithmetic246(boolean astrict) {
+ super(astrict);
+ }
+
+ public JexlOperator selfAdd(Collection c, String item) throws IOException {
+ c.add(item);
+ return JexlOperator.ASSIGN;
+ }
+
+ public JexlOperator selfAdd(Appendable c, String item) throws IOException {
+ c.append(item);
+ return JexlOperator.ASSIGN;
+ }
+
+ @Override
+ public Object add(Object right, Object left) {
+ return super.add(left, right);
+ }
+ }
+
+ public static class Arithmetic246b extends Arithmetic246 {
+ public Arithmetic246b(boolean astrict) {
+ super(astrict);
+ }
+
+ public Object selfAdd(Object c, String item) throws IOException {
+ if (c == null) {
+ return new ArrayList(Arrays.asList(item));
+ }
+ if (c instanceof Appendable) {
+ ((Appendable) c).append(item);
+ return JexlOperator.ASSIGN;
+ }
+ return JexlEngine.TRY_FAILED;
+ }
+ }
+
+ @Test
+ public void test246() throws Exception {
+ run246(new Arithmetic246(true));
+ }
+
+ @Test
+ public void test246b() throws Exception {
+ run246(new Arithmetic246b(true));
+ }
+
+ private void run246(JexlArithmetic j246) throws Exception {
+ Log log246 = LogFactory.getLog(SideEffectTest.class);
+ // quiesce the logger
+ java.util.logging.Logger ll246 = java.util.logging.LogManager.getLogManager().getLogger(SideEffectTest.class.getName());
+ // ll246.setLevel(Level.WARNING);
+ JexlEngine jexl = new JexlBuilder().arithmetic(j246).cache(32).debug(true).logger(log246).create();
+ JexlScript script = jexl.createScript("z += x", "x");
+ MapContext ctx = new MapContext();
+ List z = new ArrayList(1);
+ Object zz = null;
+
+ // no ambiguous, std case
+ ctx.set("z", z);
+ zz = script.execute(ctx, "42");
+ Assert.assertTrue(zz == z);
+ Assert.assertEquals(1, z.size());
+ z.clear();
+ ctx.clear();
+
+ boolean t246 = false;
+ // call with null
+ try {
+ script.execute(ctx, "42");
+ zz = ctx.get("z");
+ Assert.assertTrue(zz instanceof List>);
+ z = (List) zz;
+ Assert.assertEquals(1, z.size());
+ } catch(JexlException xjexl) {
+ t246 = true;
+ Assert.assertTrue(j246.getClass().equals(Arithmetic246.class));
+ } catch(ArithmeticException xjexl) {
+ t246 = true;
+ Assert.assertTrue(j246.getClass().equals(Arithmetic246.class));
+ }
+ ctx.clear();
+
+ // a non ambiguous call still succeeds
+ ctx.set("z", z);
+ zz = script.execute(ctx, "-42");
+ Assert.assertTrue(zz == z);
+ Assert.assertEquals(t246? 1 : 2, z.size());
+ }
+
+ // an arithmetic that performs side effects
+ public static class Arithmetic248 extends JexlArithmetic {
+ public Arithmetic248(boolean strict) {
+ super(strict);
+ }
+
+ public Object arrayGet(List> list, Collection range) {
+ List rl = new ArrayList(range.size());
+ for(int i : range) {
+ rl.add(list.get(i));
+ }
+ return rl;
+ }
+
+ public Object arraySet(List list, Collection range, Object value) {
+ for(int i : range) {
+ list.set(i, value);
+ }
+ return list;
+ }
+ }
+
+ @Test
+ public void test248() throws Exception {
+ MapContext ctx = new MapContext();
+ List foo = new ArrayList();
+ foo.addAll(Arrays.asList(10, 20, 30, 40));
+ ctx.set("foo", foo);
+
+ JexlEngine engine = new JexlBuilder().arithmetic(new Arithmetic248(true)).create();
+ JexlScript foo12 = engine.createScript("foo[1..2]");
+ try {
+ Object r = foo12.execute(ctx);
+ Assert.assertEquals(Arrays.asList(20, 30), r);
+ } catch (JexlException xp) {
+ Assert.assertTrue(xp instanceof JexlException.Property);
+ }
+
+ JexlScript foo12assign = engine.createScript("foo[1..2] = x", "x");
+ try {
+ Object r = foo12assign.execute(ctx, 25);
+ Assert.assertEquals(25, r);
+ Assert.assertEquals(Arrays.asList(10, 25, 25, 40), foo);
+ } catch (JexlException xp) {
+ Assert.assertTrue(xp instanceof JexlException.Property);
+ }
+ }
+
}
diff --git a/src/test/java/org/apache/commons/jexl3/SynchronizedArithmetic.java b/src/test/java/org/apache/commons/jexl3/SynchronizedArithmetic.java
new file mode 100644
index 000000000..9938b2bdd
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/SynchronizedArithmetic.java
@@ -0,0 +1,256 @@
+/*
+ * 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.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicInteger;
+import sun.misc.Unsafe;
+
+/**
+ *
+ * An example arithmetic that uses object intrinsic monitors to synchronize get/set/iteration on Maps.
+ */
+public class SynchronizedArithmetic extends JexlArithmetic {
+ /**
+ * Monitor/synchronized protected access to gets/set/iterator on maps.
+ */
+ private final Monitor monitor;
+
+ /**
+ * A base synchronized arithmetic.
+ * @param monitor the synchronization monitor
+ * @param strict whether the arithmetic is strict or not
+ */
+ protected SynchronizedArithmetic(Monitor monitor, boolean strict) {
+ super(strict);
+ this.monitor = monitor;
+ }
+
+
+ /**
+ * An indirect way to determine we are actually calling what is needed.
+ *
+ * This class counts how many times we called enter & exit; they should be balanced
+ */
+ public static abstract class Monitor{
+ /* Counts the number of times enter is called. */
+ private final AtomicInteger enters = new AtomicInteger(0);
+ /* Counts the number of times exit is called. */
+ private final AtomicInteger exits = new AtomicInteger(0);
+
+ /**
+ * Enter an object monitor.
+ * @param o the monitored object
+ */
+ protected void monitorEnter(Object o) {
+ UNSAFE.monitorEnter(o);
+ enters.incrementAndGet();
+ }
+
+ /**
+ * Exits an object monitor.
+ * @param o the monitored object
+ */
+ protected void monitorExit(Object o) {
+ UNSAFE.monitorExit(o);
+ exits.incrementAndGet();
+ }
+
+ /**
+ * Whether the number of monitor enter is equals to the number of exits.
+ * @return true if balanced, false otherwise
+ */
+ public boolean isBalanced() {
+ return enters.get() == exits.get();
+ }
+
+ /**
+ * The number of enter calls.
+ * @return how many enter were executed
+ */
+ public int getCount() {
+ return enters.get();
+ }
+
+ }
+
+ /**
+ * You should know better than to use this...
+ */
+ private static Unsafe UNSAFE;
+ static {
+ try {
+ Field f = Unsafe.class.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ UNSAFE = (Unsafe) f.get(null);
+ } catch (Exception e) {
+ UNSAFE = null;
+ }
+ }
+
+ /**
+ * Using the unsafe to enter & exit object intrinsic monitors.
+ */
+ static class UnsafeMonitor extends Monitor {
+ @Override protected void monitorEnter(Object o) {
+ UNSAFE.monitorEnter(o);
+ super.monitorEnter(o);
+ }
+
+ @Override protected void monitorExit(Object o) {
+ UNSAFE.monitorExit(o);
+ super.monitorExit(o);
+ }
+ }
+
+ /**
+ * An iterator that implements Closeable (at least implements a close method)
+ * and uses monitors to protect iteration.
+ */
+ public class SynchronizedIterator implements /*Closeable,*/ Iterator {
+ private final Object monitored;
+ private Iterator iterator;
+
+ SynchronizedIterator(Object locked, Iterator ii) {
+ monitored = locked;
+ monitor.monitorEnter(monitored);
+ try {
+ iterator = ii;
+ } finally {
+ if (iterator == null) {
+ monitor.monitorExit(monitored);
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+
+ //@Override
+ public void close() {
+ if (iterator != null) {
+ monitor.monitorExit(monitored);
+ iterator = null;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (iterator == null) {
+ return false;
+ }
+ boolean n = iterator.hasNext();
+ if (!n) {
+ close();
+ }
+ return n;
+ }
+
+ @Override
+ public Object next() {
+ if (iterator == null) {
+ throw new NoSuchElementException();
+ }
+ return iterator.next();
+ }
+
+ @Override
+ public void remove() {
+ if (iterator != null) {
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Jexl pseudo-overload for array-like access get operator (map[key]) for maps.
+ * synchronized(map) { return map.get(key); }
+ * @param map the map
+ * @param key the key
+ * @return the value associated to the key in the map
+ */
+ public Object arrayGet(Map, ?> map, Object key) {
+ monitor.monitorEnter(map);
+ try {
+ return map.get(key);
+ } finally {
+ monitor.monitorExit(map);
+ }
+ }
+ /**
+ * Jexl pseudo-overload for array-like access set operator (map[key] = value) for maps.
+ *
synchronized(map) { map.put(key, value); }
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ */
+ public void arraySet(Map map, Object key, Object value) {
+ monitor.monitorEnter(map);
+ try {
+ map.put(key, value);
+ } finally {
+ monitor.monitorExit(map);
+ }
+ }
+ /**
+ * Jexl pseudo-overload for property access get operator (map.key) for maps.
+ * synchronized(map) { return map.get(key); }
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value associated to the key in the map
+ */
+ public Object propertyGet(Map, ?> map, Object key) {
+ monitor.monitorEnter(map);
+ try {
+ return map.get(key);
+ } finally {
+ monitor.monitorExit(map);
+ }
+ }
+
+ /**
+ * Jexl pseudo-overload for array-like access set operator (map.key = value) for maps.
+ *
synchronized(map) { map.put(key, value); }
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ */
+ public void propertySet(Map map, Object key, Object value) {
+ monitor.monitorEnter(map);
+ try {
+ map.put(key, value);
+ } finally {
+ monitor.monitorExit(map);
+ }
+ }
+
+ /**
+ * Creates an iterator on the map values that executes under the map monitor.
+ * @param map the map
+ * @return the iterator
+ */
+ public Iterator forEach(Map map) {
+ return new SynchronizedIterator(map, map.values().iterator());
+ }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java b/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java
new file mode 100644
index 000000000..b9f9fed11
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/SynchronizedContext.java
@@ -0,0 +1,74 @@
+/*
+ * 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.concurrent.Callable;
+
+/**
+ * Exposes a synchronized call to a script and synchronizes access to get/set methods.
+ */
+public class SynchronizedContext extends MapContext implements JexlContext.AnnotationProcessor {
+ private final JexlContext context;
+
+ public SynchronizedContext(JexlContext ctxt) {
+ this.context = ctxt;
+ }
+
+ /**
+ * Calls a script synchronized by an object monitor.
+ * @param var the object used for sync
+ * @param script the script
+ * @return the script value
+ */
+ public Object call(Object var, JexlScript script) {
+ String[] parms = script.getParameters();
+ boolean varisarg = parms != null && parms.length == 1;
+ if (var == null) {
+ return varisarg ? script.execute(context, var) : script.execute(context);
+ } else {
+ synchronized (var) {
+ return varisarg ? script.execute(context, var) : script.execute(context);
+ }
+ }
+ }
+
+ @Override
+ public Object get(String name) {
+ synchronized (this) {
+ return super.get(name);
+ }
+ }
+
+ @Override
+ public void set(String name, Object value) {
+ synchronized (this) {
+ super.set(name, value);
+ }
+ }
+
+ @Override
+ public Object processAnnotation(String name, Object[] args, Callable statement) throws Exception {
+ if ("synchronized".equals(name)) {
+ final Object arg = args[0];
+ synchronized(arg) {
+ return statement.call();
+ }
+ }
+ throw new IllegalArgumentException("unknown annotation " + name);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java b/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java
new file mode 100644
index 000000000..cf98bce3d
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/SynchronizedOverloadsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Map;
+import java.util.TreeMap;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Test cases for synchronized calls.
+ * May be a base for synchronized calls.
+ */
+@SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class SynchronizedOverloadsTest extends JexlTestCase {
+ public SynchronizedOverloadsTest() {
+ super("SynchronizedOverloadsTest", null);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ // ensure jul logging is only error to avoid warning in silent mode
+ java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
+ }
+
+
+ @Test
+ public void testSynchronizer() throws Exception {
+ Map ns = new TreeMap();
+ ns.put("synchronized", SynchronizedContext.class);
+ JexlContext jc = new MapContext();
+ JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
+ JexlScript js0 = jexl.createScript("synchronized:call(x, (y)->{y.size()})", "x");
+ Object size = js0.execute(jc, "foobar");
+ Assert.assertEquals(6, size);
+ }
+
+ @Test
+ public void testSynchronized() throws Exception {
+ Map ns = new TreeMap();
+ 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 foo = new TreeMap();
+ foo.put("one", 1);
+ foo.put("two", 2);
+ foo.put("three", 3);
+ JexlContext jc = new SynchronizedContext(new MapContext());
+ JexlEngine jexl = new JexlBuilder().arithmetic(new SynchronizedArithmetic(monitor, true)).create();
+ JexlScript js0 = jexl.createScript("x['four'] = 4; var t = 0.0; for(var z: x) { t += z; }; call(t, (y)->{return y});", "x");
+ Object t = js0.execute(jc, foo);
+ 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());
+ }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/examples/ArrayTest.java b/src/test/java/org/apache/commons/jexl3/examples/ArrayTest.java
index 620d656ac..7cac5abd2 100644
--- a/src/test/java/org/apache/commons/jexl3/examples/ArrayTest.java
+++ b/src/test/java/org/apache/commons/jexl3/examples/ArrayTest.java
@@ -67,7 +67,8 @@ static void example(Output out) throws Exception {
* Unit test entry point.
* @throws Exception
*/
- @Test public void testExample() throws Exception {
+ @Test
+ public void testExample() throws Exception {
example(Output.JUNIT);
}
diff --git a/src/test/java/org/apache/commons/jexl3/examples/MethodPropertyTest.java b/src/test/java/org/apache/commons/jexl3/examples/MethodPropertyTest.java
index 7f914472f..247842fa0 100644
--- a/src/test/java/org/apache/commons/jexl3/examples/MethodPropertyTest.java
+++ b/src/test/java/org/apache/commons/jexl3/examples/MethodPropertyTest.java
@@ -17,19 +17,19 @@
package org.apache.commons.jexl3.examples;
-import junit.framework.TestCase;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.MapContext;
+import org.junit.Test;
/**
* Simple example to show how to access method and properties.
*
* @since 1.0
*/
-public class MethodPropertyTest extends TestCase {
+public class MethodPropertyTest {
/**
* An example for method access.
*/
@@ -120,6 +120,7 @@ public String convert(long i) {
* Unit test entry point.
* @throws Exception
*/
+ @Test
public void testExample() throws Exception {
example(Output.JUNIT);
}
diff --git a/src/test/java/org/apache/commons/jexl3/examples/Output.java b/src/test/java/org/apache/commons/jexl3/examples/Output.java
index ea67e9c45..826ee7bf4 100644
--- a/src/test/java/org/apache/commons/jexl3/examples/Output.java
+++ b/src/test/java/org/apache/commons/jexl3/examples/Output.java
@@ -16,7 +16,8 @@
*/
package org.apache.commons.jexl3.examples;
-import junit.framework.TestCase;
+
+import org.junit.Assert;
/**
* Abstracts using a test within Junit or through a main method.
@@ -43,7 +44,7 @@ private Output() {
public static final Output JUNIT = new Output() {
@Override
public void print(String expr, Object actual, Object expected) {
- TestCase.assertEquals(expr, expected, actual);
+ Assert.assertEquals(expr, expected, actual);
}
};
diff --git a/src/test/java/org/apache/commons/jexl3/internal/Util.java b/src/test/java/org/apache/commons/jexl3/internal/Util.java
index 9d3f65136..378567b50 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/Util.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/Util.java
@@ -21,12 +21,13 @@
import java.util.Map;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.parser.ASTJexlScript;
import org.apache.commons.jexl3.parser.JexlNode;
/**
- * Helper methods for debug sessions.
+ * Helper methods for validate sessions.
*/
public class Util {
/**
@@ -44,17 +45,18 @@ public static void debuggerCheck(JexlEngine ijexl) throws Exception {
Engine jdbg = new Engine();
jdbg.parser.allowRegisters(true);
Debugger dbg = new Debugger();
- // iterate over all expression in cache
- Iterator> inodes = jexl.cache.entrySet().iterator();
+ // iterate over all expression in
+ Iterator> inodes = jexl.cache.entries().iterator();
while (inodes.hasNext()) {
- Map.Entry entry = inodes.next();
+ Map.Entry entry = inodes.next();
JexlNode node = entry.getValue();
// recreate expr string from AST
dbg.debug(node);
String expressiondbg = dbg.toString();
+ JexlFeatures features = entry.getKey().getFeatures();
// recreate expr from string
try {
- Script exprdbg = jdbg.createScript(null, expressiondbg, null);
+ Script exprdbg = jdbg.createScript(features, null, expressiondbg, null);
// make arg cause become the root cause
JexlNode root = exprdbg.script;
while (root.jjtGetParent() != null) {
@@ -137,7 +139,7 @@ private static String checkEquals(JexlNode lhs, JexlNode rhs) {
}
/**
- * A helper class to help debug AST problems.
+ * A helper class to help validate AST problems.
* @param e the script
* @return an indented version of the AST
*/
diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/MethodKeyTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/MethodKeyTest.java
index e75dccfbc..5c02df54f 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/introspection/MethodKeyTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/MethodKeyTest.java
@@ -16,13 +16,13 @@
*/
package org.apache.commons.jexl3.internal.introspection;
-import junit.framework.TestCase;
+import org.junit.Assert;
import org.junit.Test;
/**
* Checks the CacheMap.MethodKey implementation
*/
-public class MethodKeyTest extends TestCase {
+public class MethodKeyTest {
// A set of classes (most of them primitives)
private static final Class>[] PRIMS = {
Boolean.TYPE,
@@ -75,25 +75,25 @@ public class MethodKeyTest extends TestCase {
"invokeIt"
};
/** from key to string */
- private static final java.util.Map< MethodKey, String> byKey;
+ private static final java.util.Map< MethodKey, String> BY_KEY;
/** form string to key */
- private static final java.util.Map byString;
+ private static final java.util.Map BY_STRING;
/** the list of keys we generated & test against */
- private static final MethodKey[] keyList;
+ private static final MethodKey[] KEY_LIST;
- /** Creates & inserts a key into the byKey & byString map */
+ /** * Creates & inserts a key into the BY_KEY & byString map */
private static void setUpKey(String name, Class>[] parms) {
MethodKey key = new MethodKey(name, parms);
String str = key.toString();
- byKey.put(key, str);
- byString.put(str, key);
+ BY_KEY.put(key, str);
+ BY_STRING.put(str, key);
}
/** Generate a list of method*(prims*), method(prims*, prims*), method*(prims*,prims*,prims*) */
static {
- byKey = new java.util.HashMap< MethodKey, String>();
- byString = new java.util.HashMap();
+ BY_KEY = new java.util.HashMap< MethodKey, String>();
+ BY_STRING = new java.util.HashMap();
for (int m = 0; m < METHODS.length; ++m) {
String method = METHODS[m];
for (int p0 = 0; p0 < PRIMS.length; ++p0) {
@@ -109,7 +109,7 @@ private static void setUpKey(String name, Class>[] parms) {
}
}
}
- keyList = byKey.keySet().toArray(new MethodKey[byKey.size()]);
+ KEY_LIST = BY_KEY.keySet().toArray(new MethodKey[BY_KEY.size()]);
}
/** Builds a string key */
@@ -124,8 +124,8 @@ String makeStringKey(String method, Class>... params) {
/** Checks that a string key does exist */
void checkStringKey(String method, Class>... params) {
String key = makeStringKey(method, params);
- MethodKey out = byString.get(key);
- assertTrue(out != null);
+ MethodKey out = BY_STRING.get(key);
+ Assert.assertTrue(out != null);
}
/** Builds a method key */
@@ -136,30 +136,37 @@ MethodKey makeKey(String method, Class>... params) {
/** Checks that a method key exists */
void checkKey(String method, Class>... params) {
MethodKey key = makeKey(method, params);
- String out = byKey.get(key);
- assertTrue(out != null);
+ String out = BY_KEY.get(key);
+ Assert.assertTrue(out != null);
+ }
+
+ @Test
+ public void testDebugString() throws Exception {
+ MethodKey c = KEY_LIST[0];
+ String str = c.debugString();
+ Assert.assertNotNull(str);
}
@Test
public void testObjectKey() throws Exception {
- for (int k = 0; k < keyList.length; ++k) {
- MethodKey ctl = keyList[k];
+ for (int k = 0; k < KEY_LIST.length; ++k) {
+ MethodKey ctl = KEY_LIST[k];
MethodKey key = makeKey(ctl.getMethod(), ctl.getParameters());
- String out = byKey.get(key);
- assertTrue(out != null);
- assertTrue(ctl.toString() + " != " + out, ctl.toString().equals(out));
+ String out = BY_KEY.get(key);
+ Assert.assertTrue(out != null);
+ Assert.assertTrue(ctl.toString() + " != " + out, ctl.toString().equals(out));
}
}
@Test
public void testStringKey() throws Exception {
- for (int k = 0; k < keyList.length; ++k) {
- MethodKey ctl = keyList[k];
+ for (int k = 0; k < KEY_LIST.length; ++k) {
+ MethodKey ctl = KEY_LIST[k];
String key = makeStringKey(ctl.getMethod(), ctl.getParameters());
- MethodKey out = byString.get(key);
- assertTrue(out != null);
- assertTrue(ctl.toString() + " != " + key, ctl.equals(out));
+ MethodKey out = BY_STRING.get(key);
+ Assert.assertTrue(out != null);
+ Assert.assertTrue(ctl.toString() + " != " + key, ctl.equals(out));
}
}
@@ -168,11 +175,11 @@ public void testStringKey() throws Exception {
@Test
public void testPerfKey() throws Exception {
for (int l = 0; l < LOOP; ++l) {
- for (int k = 0; k < keyList.length; ++k) {
- MethodKey ctl = keyList[k];
+ for (int k = 0; k < KEY_LIST.length; ++k) {
+ MethodKey ctl = KEY_LIST[k];
MethodKey key = makeKey(ctl.getMethod(), ctl.getParameters());
- String out = byKey.get(key);
- assertTrue(out != null);
+ String out = BY_KEY.get(key);
+ Assert.assertTrue(out != null);
}
}
}
@@ -180,11 +187,11 @@ public void testPerfKey() throws Exception {
@Test
public void testPerfString() throws Exception {
for (int l = 0; l < LOOP; ++l) {
- for (int k = 0; k < keyList.length; ++k) {
- MethodKey ctl = keyList[k];
+ for (int k = 0; k < KEY_LIST.length; ++k) {
+ MethodKey ctl = KEY_LIST[k];
String key = makeStringKey(ctl.getMethod(), ctl.getParameters());
- MethodKey out = byString.get(key);
- assertTrue(out != null);
+ MethodKey out = BY_STRING.get(key);
+ Assert.assertTrue(out != null);
}
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java b/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
index b243b90b4..82eeae3cf 100644
--- a/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
+++ b/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
@@ -40,7 +40,13 @@ public class SandboxTest extends JexlTestCase {
public SandboxTest() {
super("SandboxTest");
- JEXL.setClassLoader(getClass().getClassLoader());
+ }
+
+
+ public static class CantSeeMe {
+ public boolean doIt() {
+ return false;
+ }
}
@NoJexl
@@ -88,6 +94,10 @@ public void setName(String name) {
public String Quux() {
return name + "-quux";
}
+
+ public int doIt() {
+ return 42;
+ }
@NoJexl
public String cantCallMe() {
@@ -195,6 +205,30 @@ public void testSetBlack() throws Exception {
LOGGER.info(xvar.toString());
}
}
+
+ @Test
+ public void testCantSeeMe() throws Exception {
+ JexlContext jc = new MapContext();
+ String expr = "foo.doIt()";
+ JexlScript script;
+ Object result = null;
+
+ JexlSandbox sandbox = new JexlSandbox(false);
+ sandbox.white(Foo.class.getName());
+ JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).create();
+
+ jc.set("foo", new CantSeeMe());
+ script = sjexl.createScript(expr);
+ try {
+ result = script.execute(jc);
+ Assert.fail("should have failed, doIt()");
+ } catch (JexlException xany) {
+ //
+ }
+ jc.set("foo", new Foo("42"));
+ result = script.execute(jc);
+ Assert.assertEquals(42, ((Integer) result).intValue());
+ }
@Test
public void testCtorWhite() throws Exception {
diff --git a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
index eb97ac60d..93f2c7069 100644
--- a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
+++ b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
@@ -16,11 +16,11 @@
*/
package org.apache.commons.jexl3.junit;
+import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
-import junit.framework.Assert;
import org.apache.commons.jexl3.JexlEvalContext;
import org.apache.commons.jexl3.JexlArithmetic;
@@ -28,6 +28,8 @@
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlScript;
+import org.junit.Assert;
+import static org.junit.Assert.fail;
/**
* A utility class for performing JUnit based assertions using Jexl
@@ -99,16 +101,26 @@ public void assertExpression(String expression, Object expected) throws Exceptio
Object value = exp.execute(context);
if (expected instanceof BigDecimal) {
JexlArithmetic jexla = engine.getArithmetic();
- assertTrue("expression: " + expression, ((BigDecimal) expected).compareTo(jexla.toBigDecimal(value)) == 0);
+ Assert.assertTrue("expression: " + expression, ((BigDecimal) expected).compareTo(jexla.toBigDecimal(value)) == 0);
}
if (expected != null && value != null) {
- assertEquals("expression: " + expression + ", "
- + expected.getClass().getSimpleName()
- + " ?= "
- + value.getClass().getSimpleName(),
- expected, value);
+ if (expected.getClass().isArray() && value.getClass().isArray()) {
+ int esz = Array.getLength(expected);
+ int vsz = Array.getLength(value);
+ String report = "expression: " + expression;
+ Assert.assertEquals(report + ", array size", esz, vsz);
+ for (int i = 0; i < vsz; ++i) {
+ Assert.assertEquals(report + ", value@[]" + i, Array.get(expected, i), Array.get(value, i));
+ }
+ } else {
+ Assert.assertEquals("expression: " + expression + ", "
+ + expected.getClass().getSimpleName()
+ + " ?= "
+ + value.getClass().getSimpleName(),
+ expected, value);
+ }
} else {
- assertEquals("expression: " + expression, expected, value);
+ Assert.assertEquals("expression: " + expression, expected, value);
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java b/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java
index 9483047da..480152116 100644
--- a/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java
+++ b/src/test/java/org/apache/commons/jexl3/junit/AsserterTest.java
@@ -17,8 +17,6 @@
/* $Id$ */
package org.apache.commons.jexl3.junit;
-import junit.framework.AssertionFailedError;
-
import org.apache.commons.jexl3.Foo;
import org.apache.commons.jexl3.JexlTestCase;
import org.junit.Assert;
@@ -39,12 +37,12 @@ public AsserterTest() {
public void testThis() throws Exception {
Asserter asserter = new Asserter(JEXL);
asserter.setVariable("this", new Foo());
- asserter.assertExpression("this.get('abc')", "Repeat : abc");
+ asserter.assertExpression("this.repeat('abc')", "Repeat : abc");
try {
asserter.assertExpression("this.count", "Wrong Value");
Assert.fail("This method should have thrown an assertion exception");
}
- catch (AssertionFailedError e) {
+ catch (AssertionError e) {
// it worked!
}
}
@@ -66,7 +64,7 @@ public void testVariable() throws Exception {
asserter.assertExpression("bar.count", new Integer(5));
Assert.fail("This method should have thrown an assertion exception");
}
- catch (AssertionFailedError e) {
+ catch (AssertionError e) {
// it worked!
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java b/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java
index c128acb0e..9bc5fce24 100644
--- a/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java
+++ b/src/test/java/org/apache/commons/jexl3/parser/ParserTest.java
@@ -18,52 +18,62 @@
import java.io.StringReader;
-import junit.framework.TestCase;
import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlFeatures;
+import org.junit.Assert;
+import org.junit.Test;
/**
* @since 1.0
*
*/
-public class ParserTest extends TestCase {
- public ParserTest(String testName) {
- super(testName);
- }
+public class ParserTest {
+ static final JexlFeatures features = new JexlFeatures();
+ public ParserTest() {}
/**
* See if we can parse simple scripts
*/
+ @Test
public void testParse() throws Exception {
Parser parser = new Parser(new StringReader(";"));
-
JexlNode sn;
- sn = parser.parse(null, "foo = 1;", null, false, false);
- assertNotNull("parsed node is null", sn);
+ sn = parser.parse(null, features, "foo = 1;", null);
+ Assert.assertNotNull("parsed node is null", sn);
- sn = parser.parse(null, "foo = \"bar\";", null, false, false);
- assertNotNull("parsed node is null", sn);
+ sn = parser.parse(null, features, "foo = \"bar\";", null);
+ Assert.assertNotNull("parsed node is null", sn);
- sn = parser.parse(null, "foo = 'bar';", null, false, false);
- assertNotNull("parsed node is null", sn);
+ sn = parser.parse(null, features, "foo = 'bar';", null);
+ Assert.assertNotNull("parsed node is null", sn);
}
+ @Test
public void testErrorAssign() throws Exception {
- Parser parser = new Parser(new StringReader(";"));
- try {
- JexlNode sn = parser.parse(null, "foo() = 1;", null, false, false);
- fail("should have failed on invalid assignment");
- } catch (JexlException.Parsing xparse) {
- // ok
+ String[] ops = { "=", "+=", "-=", "/=", "*=", "^=", "&=", "|=" };
+ for(String op : ops) {
+ Parser parser = new Parser(new StringReader(";"));
+ try {
+ JexlNode sn = parser.parse(null, features, "foo() "+op+" 1;", null);
+ Assert.fail("should have failed on invalid assignment " + op);
+ } catch (JexlException.Parsing xparse) {
+ // ok
+ String ss = xparse.getDetail();
+ String sss = xparse.toString();
+ }
}
}
+ @Test
public void testErrorAmbiguous() throws Exception {
Parser parser = new Parser(new StringReader(";"));
try {
- JexlNode sn = parser.parse(null, "x = 1 y = 5", null, false, false);
- fail("should have failed on ambiguous statement");
+ JexlNode sn = parser.parse(null, features, "x = 1 y = 5", null);
+ Assert.fail("should have failed on ambiguous statement");
} catch (JexlException.Ambiguous xambiguous) {
// ok
+ } catch(JexlException xother) {
+ Assert.fail(xother.toString());
}
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
index c2c89c5a3..ab739dc97 100644
--- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineOptionalTest.java
@@ -24,37 +24,41 @@
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
-import junit.framework.TestCase;
+import org.junit.Assert;
+import org.junit.Test;
-public class JexlScriptEngineOptionalTest extends TestCase {
+public class JexlScriptEngineOptionalTest {
private final JexlScriptEngineFactory factory = new JexlScriptEngineFactory();
private final ScriptEngineManager manager = new ScriptEngineManager();
private final ScriptEngine engine = manager.getEngineByName("jexl");
+ @Test
public void testOutput() throws Exception {
String output = factory.getOutputStatement("foo\u00a9bar");
- assertEquals("JEXL.out.print('foo\\u00a9bar')", output);
+ Assert.assertEquals("JEXL.out.print('foo\\u00a9bar')", output);
// redirect output to capture evaluation result
final StringWriter outContent = new StringWriter();
engine.getContext().setWriter(outContent);
engine.eval(output);
- assertEquals("foo\u00a9bar", outContent.toString());
+ Assert.assertEquals("foo\u00a9bar", outContent.toString());
}
+ @Test
public void testError() throws Exception {
String error = "JEXL.err.print('ERROR')";
// redirect error to capture evaluation result
final StringWriter outContent = new StringWriter();
engine.getContext().setErrorWriter(outContent);
engine.eval(error);
- assertEquals("ERROR", outContent.toString());
+ Assert.assertEquals("ERROR", outContent.toString());
}
+ @Test
public void testCompilable() throws Exception {
- assertTrue("Engine should implement Compilable", engine instanceof Compilable);
+ Assert.assertTrue("Engine should implement Compilable", engine instanceof Compilable);
Compilable cengine = (Compilable) engine;
CompiledScript script = cengine.compile("40 + 2");
- assertEquals(Integer.valueOf(42), script.eval());
- assertEquals(Integer.valueOf(42), script.eval());
+ Assert.assertEquals(Integer.valueOf(42), script.eval());
+ Assert.assertEquals(Integer.valueOf(42), script.eval());
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
index 5341396ec..ed6c4e026 100644
--- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
+++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java
@@ -25,9 +25,10 @@
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
-import junit.framework.TestCase;
+import org.junit.Assert;
+import org.junit.Test;
-public class JexlScriptEngineTest extends TestCase {
+public class JexlScriptEngineTest {
private static final List NAMES = Arrays.asList("JEXL", "Jexl", "jexl",
"JEXL2", "Jexl2", "jexl2",
"JEXL3", "Jexl3", "jexl3");
@@ -36,89 +37,94 @@ public class JexlScriptEngineTest extends TestCase {
"application/x-jexl2",
"application/x-jexl3");
+ @Test
public void testScriptEngineFactory() throws Exception {
JexlScriptEngineFactory factory = new JexlScriptEngineFactory();
- assertEquals("JEXL Engine", factory.getParameter(ScriptEngine.ENGINE));
- assertEquals("3.0", factory.getParameter(ScriptEngine.ENGINE_VERSION));
- assertEquals("JEXL", factory.getParameter(ScriptEngine.LANGUAGE));
- assertEquals("3.0", factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
- assertNull(factory.getParameter("THREADING"));
- assertEquals(NAMES, factory.getParameter(ScriptEngine.NAME));
- assertEquals(EXTENSIONS, factory.getExtensions());
- assertEquals(MIMES, factory.getMimeTypes());
+ Assert.assertEquals("JEXL Engine", factory.getParameter(ScriptEngine.ENGINE));
+ Assert.assertEquals("3.2", factory.getParameter(ScriptEngine.ENGINE_VERSION));
+ Assert.assertEquals("JEXL", factory.getParameter(ScriptEngine.LANGUAGE));
+ Assert.assertEquals("3.2", factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
+ Assert.assertNull(factory.getParameter("THREADING"));
+ Assert.assertEquals(NAMES, factory.getParameter(ScriptEngine.NAME));
+ Assert.assertEquals(EXTENSIONS, factory.getExtensions());
+ Assert.assertEquals(MIMES, factory.getMimeTypes());
- assertEquals("42;", factory.getProgram(new String[]{"42"}));
- assertEquals("str.substring(3,4)", factory.getMethodCallSyntax("str", "substring", new String[]{"3", "4"}));
+ Assert.assertEquals("42;", factory.getProgram(new String[]{"42"}));
+ Assert.assertEquals("str.substring(3,4)", factory.getMethodCallSyntax("str", "substring", new String[]{"3", "4"}));
}
+ @Test
public void testScriptingGetBy() throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
- assertNotNull("Manager should not be null", manager);
+ Assert.assertNotNull("Manager should not be null", manager);
for (String name : NAMES) {
ScriptEngine engine = manager.getEngineByName(name);
- assertNotNull("Engine should not be null (name)", engine);
+ Assert.assertNotNull("Engine should not be null (name)", engine);
}
for (String extension : EXTENSIONS) {
ScriptEngine engine = manager.getEngineByExtension(extension);
- assertNotNull("Engine should not be null (extension)", engine);
+ Assert.assertNotNull("Engine should not be null (extension)", engine);
}
for (String mime : MIMES) {
ScriptEngine engine = manager.getEngineByMimeType(mime);
- assertNotNull("Engine should not be null (mime)", engine);
+ Assert.assertNotNull("Engine should not be null (mime)", engine);
}
}
+ @Test
public void testScripting() throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
- assertNotNull("Manager should not be null", manager);
+ Assert.assertNotNull("Manager should not be null", manager);
ScriptEngine engine = manager.getEngineByName("jexl3");
final Integer initialValue = Integer.valueOf(123);
- assertEquals(initialValue,engine.eval("123"));
- assertEquals(initialValue,engine.eval("0;123"));// multiple statements
+ Assert.assertEquals(initialValue,engine.eval("123"));
+ Assert.assertEquals(initialValue,engine.eval("0;123"));// multiple statements
long time1 = System.currentTimeMillis();
Long time2 = (Long) engine.eval(
"sys=context.class.forName(\"java.lang.System\");"
+"now=sys.currentTimeMillis();"
);
- assertTrue("Must take some time to process this",time1 <= time2.longValue());
+ Assert.assertTrue("Must take some time to process this",time1 <= time2.longValue());
engine.put("value", initialValue);
- assertEquals(initialValue,engine.get("value"));
+ Assert.assertEquals(initialValue,engine.get("value"));
final Integer newValue = Integer.valueOf(124);
- assertEquals(newValue,engine.eval("old=value;value=value+1"));
- assertEquals(initialValue,engine.get("old"));
- assertEquals(newValue,engine.get("value"));
- assertEquals(engine.getContext(),engine.get(JexlScriptEngine.CONTEXT_KEY));
+ Assert.assertEquals(newValue,engine.eval("old=value;value=value+1"));
+ Assert.assertEquals(initialValue,engine.get("old"));
+ Assert.assertEquals(newValue,engine.get("value"));
+ Assert.assertEquals(engine.getContext(),engine.get(JexlScriptEngine.CONTEXT_KEY));
// Check behaviour of JEXL object
- assertEquals(engine.getContext().getReader(),engine.eval("JEXL.in"));
- assertEquals(engine.getContext().getWriter(),engine.eval("JEXL.out"));
- assertEquals(engine.getContext().getErrorWriter(),engine.eval("JEXL.err"));
- assertEquals(System.class,engine.eval("JEXL.System"));
+ Assert.assertEquals(engine.getContext().getReader(),engine.eval("JEXL.in"));
+ Assert.assertEquals(engine.getContext().getWriter(),engine.eval("JEXL.out"));
+ Assert.assertEquals(engine.getContext().getErrorWriter(),engine.eval("JEXL.err"));
+ Assert.assertEquals(System.class,engine.eval("JEXL.System"));
}
+ @Test
public void testNulls() throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
- assertNotNull("Manager should not be null", manager);
+ Assert.assertNotNull("Manager should not be null", manager);
ScriptEngine engine = manager.getEngineByName("jexl3");
- assertNotNull("Engine should not be null (name)", engine);
+ Assert.assertNotNull("Engine should not be null (name)", engine);
try {
engine.eval((String)null);
- fail("Should have caused NPE");
+ Assert.fail("Should have caused NPE");
} catch (NullPointerException e) {
// NOOP
}
try {
engine.eval((Reader)null);
- fail("Should have caused NPE");
+ Assert.fail("Should have caused NPE");
} catch (NullPointerException e) {
// NOOP
}
}
+ @Test
public void testScopes() throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
- assertNotNull("Manager should not be null", manager);
+ Assert.assertNotNull("Manager should not be null", manager);
ScriptEngine engine = manager.getEngineByName("JEXL");
- assertNotNull("Engine should not be null (JEXL)", engine);
+ Assert.assertNotNull("Engine should not be null (JEXL)", engine);
manager.put("global",Integer.valueOf(1));
engine.put("local", Integer.valueOf(10));
manager.put("both",Integer.valueOf(7));
@@ -127,30 +133,32 @@ public void testScopes() throws Exception {
engine.eval("global=global+1");
engine.eval("both=both+1"); // should update engine value only
engine.eval("newvar=42;");
- assertEquals(Integer.valueOf(2),manager.get("global"));
- assertEquals(Integer.valueOf(11),engine.get("local"));
- assertEquals(Integer.valueOf(7),manager.get("both"));
- assertEquals(Integer.valueOf(8),engine.get("both"));
- assertEquals(Integer.valueOf(42),engine.get("newvar"));
- assertNull(manager.get("newvar"));
+ Assert.assertEquals(Integer.valueOf(2),manager.get("global"));
+ Assert.assertEquals(Integer.valueOf(11),engine.get("local"));
+ Assert.assertEquals(Integer.valueOf(7),manager.get("both"));
+ Assert.assertEquals(Integer.valueOf(8),engine.get("both"));
+ Assert.assertEquals(Integer.valueOf(42),engine.get("newvar"));
+ Assert.assertNull(manager.get("newvar"));
}
+ @Test
public void testDottedNames() throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
- assertNotNull("Manager should not be null", manager);
+ Assert.assertNotNull("Manager should not be null", manager);
ScriptEngine engine = manager.getEngineByName("JEXL");
- assertNotNull("Engine should not be null (JEXL)", engine);
+ Assert.assertNotNull("Engine should not be null (JEXL)", engine);
engine.eval("this.is.a.test=null");
- assertNull(engine.get("this.is.a.test"));
- assertEquals(Boolean.TRUE, engine.eval("empty(this.is.a.test)"));
+ Assert.assertNull(engine.get("this.is.a.test"));
+ Assert.assertEquals(Boolean.TRUE, engine.eval("empty(this.is.a.test)"));
final Object mymap = engine.eval("testmap={ 'key1' : 'value1', 'key2' : 'value2' }");
- assertTrue(mymap instanceof Map, ?>);
- assertEquals(2,((Map, ?>)mymap).size());
+ Assert.assertTrue(mymap instanceof Map, ?>);
+ Assert.assertEquals(2,((Map, ?>)mymap).size());
}
+ @Test
public void testDirectNew() throws Exception {
ScriptEngine engine = new JexlScriptEngine();
final Integer initialValue = Integer.valueOf(123);
- assertEquals(initialValue,engine.eval("123"));
+ Assert.assertEquals(initialValue,engine.eval("123"));
}
}
diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml
deleted file mode 100644
index d0ae637f6..000000000
--- a/src/test/resources/log4j.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/test/scripts/testAdd.jexl b/src/test/scripts/testAdd.jexl
new file mode 100644
index 000000000..ed97e0c88
--- /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