blob: 29e12f4a81660ae046888ce5633805157943847b [file] [log] [blame]
/*
* 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.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
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.JexlException.VariableIssue;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.JexlOptions;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
import org.apache.commons.jexl3.introspection.JexlPropertySet;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.jexl3.parser.ASTArrayAccess;
import org.apache.commons.jexl3.parser.ASTAssignment;
import org.apache.commons.jexl3.parser.ASTFunctionNode;
import org.apache.commons.jexl3.parser.ASTIdentifier;
import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
import org.apache.commons.jexl3.parser.ASTMethodNode;
import org.apache.commons.jexl3.parser.ASTReference;
import org.apache.commons.jexl3.parser.ASTVar;
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;
/** The options. */
protected final JexlOptions options;
/** Cache executors. */
protected final boolean cache;
/** Cancellation support. */
protected final AtomicBoolean cancelled;
/** Empty parameters for method matching. */
protected static final Object[] EMPTY_PARAMS = new Object[0];
/** The namespace resolver. */
protected final JexlContext.NamespaceResolver ns;
/** The operators evaluation delegate. */
protected final Operators operators;
/** The map of 'prefix:function' to object resolving as namespaces. */
protected final Map<String, Object> functions;
/** The map of dynamically creates namespaces, NamespaceFunctor or duck-types of those. */
protected Map<String, Object> functors;
/**
* Creates an interpreter base.
* @param engine the engine creating this interpreter
* @param opts the evaluation options
* @param aContext the evaluation context
*/
protected InterpreterBase(final Engine engine, final JexlOptions opts, final JexlContext aContext) {
this.jexl = engine;
this.logger = jexl.logger;
this.uberspect = jexl.uberspect;
this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
this.cache = engine.cache != null;
final JexlArithmetic jexla = jexl.arithmetic;
this.options = opts == null? engine.options(aContext) : opts;
this.arithmetic = jexla.options(options);
if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) {
logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
+ ", got " + arithmetic.getClass().getSimpleName()
);
}
if (this.context instanceof JexlContext.NamespaceResolver) {
ns = ((JexlContext.NamespaceResolver) context);
} else {
ns = Engine.EMPTY_NS;
}
AtomicBoolean acancel = null;
if (this.context instanceof JexlContext.CancellationHandle) {
acancel = ((JexlContext.CancellationHandle) context).getCancellation();
}
this.cancelled = acancel != null? acancel : new AtomicBoolean(false);
final Map<String,Object> ons = options.getNamespaces();
this.functions = ons.isEmpty()? jexl.functions : ons;
this.functors = null;
this.operators = new Operators(this);
}
/**
* Copy constructor.
* @param ii the base to copy
* @param jexla the arithmetic instance to use (or null)
*/
protected InterpreterBase(final InterpreterBase ii, final JexlArithmetic jexla) {
jexl = ii.jexl;
logger = ii.logger;
uberspect = ii.uberspect;
arithmetic = jexla;
context = ii.context;
options = ii.options.copy();
cache = ii.cache;
ns = ii.ns;
operators = ii.operators;
cancelled = ii.cancelled;
functions = ii.functions;
functors = ii.functors;
}
/**
* Attempt to call close() if supported.
* <p>This is used when dealing with auto-closeable (duck-like) objects
* @param closeable the object we'd like to close
*/
protected void closeIfSupported(final Object closeable) {
if (closeable != null) {
final JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
if (mclose != null) {
try {
mclose.invoke(closeable, EMPTY_PARAMS);
} catch (final Exception xignore) {
logger.warn(xignore);
}
}
}
}
/**
* Resolves a namespace, eventually allocating an instance using context as constructor argument.
* <p>
* The lifetime of such instances span the current expression or script evaluation.</p>
* @param prefix the prefix name (may be null for global namespace)
* @param node the AST node
* @return the namespace instance
*/
protected Object resolveNamespace(final String prefix, final JexlNode node) {
Object namespace;
// check whether this namespace is a functor
synchronized (this) {
if (functors != null) {
namespace = functors.get(prefix);
if (namespace != null) {
return namespace;
}
}
}
// check if namespace is a resolver
namespace = ns.resolveNamespace(prefix);
if (namespace == null) {
namespace = functions.get(prefix);
if (prefix != null && namespace == null) {
throw new JexlException(node, "no such function namespace " + prefix, null);
}
}
// shortcut if ns is known to be not-a-functor
final boolean cacheable = cache;
final Object cached = cacheable ? node.jjtGetValue() : null;
if (cached != JexlContext.NamespaceFunctor.class) {
// allow namespace to instantiate a functor with context if possible, not an error otherwise
Object functor = null;
if (namespace instanceof JexlContext.NamespaceFunctor) {
functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
} else if (namespace instanceof Class<?> || namespace instanceof String) {
// attempt to reuse last ctor cached in volatile JexlNode.value
if (cached instanceof JexlMethod) {
try {
final Object eval = ((JexlMethod) cached).tryInvoke(null, context);
if (JexlEngine.TRY_FAILED != eval) {
functor = eval;
}
} catch (final JexlException.TryFailed xtry) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xtry.getCause());
}
}
// find a ctor with that context class
if (functor == null) {
JexlMethod ctor = uberspect.getConstructor(namespace, context);
if (ctor != null) {
try {
functor = ctor.invoke(namespace, context);
if (cacheable && ctor.isCacheable()) {
node.jjtSetValue(ctor);
}
} catch (final Exception xinst) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
}
}
// try again; find a ctor with no arg
if (functor == null) {
ctor = uberspect.getConstructor(namespace);
if (ctor != null) {
try {
functor = ctor.invoke(namespace);
} catch (final Exception xinst) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
}
}
// try again; use a class, namespace of static methods
if (functor == null) {
// try to find a class with that name
if (namespace instanceof String) {
try {
namespace = uberspect.getClassLoader().loadClass((String) namespace);
} catch (final ClassNotFoundException xignore) {
// not a class
namespace = null;
}
} // we know its a class
}
}
}
}
// got a functor, store it and return it
if (functor != null) {
synchronized (this) {
if (functors == null) {
functors = new HashMap<>();
}
functors.put(prefix, functor);
}
return functor;
} else {
// use the NamespaceFunctor class to tag this node as not-a-functor
node.jjtSetValue(JexlContext.NamespaceFunctor.class);
}
}
return namespace;
}
/**
* Defines a variable.
* @param var the variable to define
* @param frame the frame in which it will be defined
* @return true if definition succeeded, false otherwise
*/
protected boolean defineVariable(final ASTVar var, final LexicalFrame frame) {
final int symbol = var.getSymbol();
if (symbol < 0) {
return false;
}
if (var.isRedefined()) {
return false;
}
return frame.defineSymbol(symbol, var.isCaptured());
}
/**
* Checks whether a variable is defined.
* <p>The var may be either a local variable declared in the frame and
* visible from the block or defined in the context.
* @param frame the frame
* @param block the block
* @param name the variable name
* @return true if variable is defined, false otherwise
*/
protected boolean isVariableDefined(final Frame frame, final LexicalScope block, final String name) {
if (frame != null && block != null) {
final Integer ref = frame.getScope().getSymbol(name);
final int symbol = ref != null? ref : -1;
if (symbol >= 0 && block.hasSymbol(symbol)) {
final Object value = frame.get(symbol);
return value != Scope.UNDEFINED && value != Scope.UNDECLARED;
}
}
return context.has(name);
}
/**
* Gets a value of a defined local variable or from the context.
* @param frame the local frame
* @param block the lexical block if any
* @param identifier the variable node
* @return the value
*/
protected Object getVariable(final Frame frame, final LexicalScope block, final ASTIdentifier identifier) {
final int symbol = identifier.getSymbol();
// if we have a symbol, we have a scope thus a frame
if (options.isLexicalShade() && identifier.isShaded()) {
return undefinedVariable(identifier, identifier.getName());
}
if (symbol >= 0) {
if (frame.has(symbol)) {
final Object value = frame.get(symbol);
if (value != Scope.UNDEFINED) {
return value;
}
}
}
final String name = identifier.getName();
final Object value = context.get(name);
if (value == null && !context.has(name)) {
final boolean ignore = (isSafe()
&& (symbol >= 0
|| identifier.jjtGetParent() instanceof ASTAssignment))
|| (identifier.jjtGetParent() instanceof ASTReference);
if (!ignore) {
return unsolvableVariable(identifier, name, true); // undefined
}
}
return value;
}
/**
* Sets a variable in the global context.
* <p>If interpretation applies lexical shade, the variable must exist (ie
* the context has(...) method returns true) otherwise an error occurs.
* @param node the node
* @param name the variable name
* @param value the variable value
*/
protected void setContextVariable(final JexlNode node, final String name, final Object value) {
if (options.isLexicalShade() && !context.has(name)) {
throw new JexlException.Variable(node, name, true);
}
try {
context.set(name, value);
} catch (final UnsupportedOperationException xsupport) {
throw new JexlException(node, "context is readonly", xsupport);
}
}
/**
* Whether this interpreter is currently evaluating with a strict engine flag.
* @return true if strict engine, false otherwise
*/
protected boolean isStrictEngine() {
return options.isStrict();
}
/**
* Whether this interpreter ignores null in navigation expression as errors.
* @return true if safe, false otherwise
*/
protected boolean isSafe() {
return options.isSafe();
}
/**
* Whether this interpreter is currently evaluating with a silent mode.
* @return true if silent, false otherwise
*/
protected boolean isSilent() {
return options.isSilent();
}
/**
* @return true if interrupt throws a JexlException.Cancel.
*/
protected boolean isCancellable() {
return options.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(final RuntimeException xrt, final JexlNode node, final Object left, final 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(final JexlNode node, final String var, final boolean undef) {
return variableError(node, var, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
}
/**
* Triggered when a variable is lexically known as undefined.
* @param node the node where the error originated from
* @param var the variable name
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object undefinedVariable(final JexlNode node, final String var) {
return variableError(node, var, VariableIssue.UNDEFINED);
}
/**
* Triggered when a variable is lexically known as being redefined.
* @param node the node where the error originated from
* @param var the variable name
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object redefinedVariable(final JexlNode node, final String var) {
return variableError(node, var, VariableIssue.REDEFINED);
}
/**
* Triggered when a variable generates an issue.
* @param node the node where the error originated from
* @param var the variable name
* @param issue the issue type
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object variableError(final JexlNode node, final String var, final VariableIssue issue) {
if (isStrictEngine() && !node.isTernaryProtected()) {
throw new JexlException.Variable(node, var, issue);
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.variableError(node, var, issue));
}
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(final JexlNode node, final String method) {
return unsolvableMethod(node, method, null);
}
/**
* Triggered when a method can not be resolved.
* @param node the node where the error originated from
* @param method the method name
* @param args the method arguments
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object unsolvableMethod(final JexlNode node, final String method, final Object[] args) {
if (isStrictEngine()) {
throw new JexlException.Method(node, method, args);
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.methodError(node, method, args));
}
return null;
}
/**
* Triggered when a property can not be resolved.
* @param node the node where the error originated from
* @param property the property node
* @param cause the cause if any
* @param undef whether the property is undefined or null
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object unsolvableProperty(final JexlNode node, final String property, final boolean undef, final Throwable cause) {
if (isStrictEngine() && !node.isTernaryProtected()) {
throw new JexlException.Property(node, property, undef, cause);
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.propertyError(node, property, undef));
}
return null;
}
/**
* Checks whether a reference child node holds a local variable reference.
* @param node the reference node
* @param which the child we are checking
* @return true if child is local variable, false otherwise
*/
protected boolean isLocalVariable(final ASTReference node, final int which) {
return (node.jjtGetNumChildren() > which
&& node.jjtGetChild(which) instanceof ASTIdentifier
&& ((ASTIdentifier) node.jjtGetChild(which)).getSymbol() >= 0);
}
/**
* Checks whether a reference child node holds a function call.
* @param node the reference node
* @return true if child is function call, false otherwise
*/
protected boolean isFunctionCall(final ASTReference node) {
return (node.jjtGetNumChildren() > 0
&& node.jjtGetChild(0) instanceof ASTFunctionNode);
}
/**
* Pretty-prints a failing property (de)reference.
* <p>Used by calls to unsolvableProperty(...).</p>
* @param node the property node
* @return the (pretty) string
*/
protected String stringifyProperty(final JexlNode node) {
if (node instanceof ASTArrayAccess) {
return "["
+ stringifyPropertyValue(node.jjtGetChild(0))
+ "]";
}
if (node instanceof ASTMethodNode) {
return stringifyPropertyValue(node.jjtGetChild(0));
}
if (node instanceof ASTFunctionNode) {
return stringifyPropertyValue(node.jjtGetChild(0));
}
if (node instanceof ASTIdentifier) {
return ((ASTIdentifier) node).getName();
}
if (node instanceof ASTReference) {
return stringifyProperty(node.jjtGetChild(0));
}
return stringifyPropertyValue(node);
}
/**
* Pretty-prints a failing property value (de)reference.
* <p>Used by calls to unsolvableProperty(...).</p>
* @param node the property node
* @return the (pretty) string value
*/
protected static String stringifyPropertyValue(final JexlNode node) {
return node != null? new Debugger().depth(1).data(node) : "???";
}
/**
* 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(final JexlNode node, final JexlOperator operator, final 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(final JexlNode node, final String annotation, final 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(final JexlNode node, final String methodName, final Throwable xany) {
final Throwable cause = xany.getCause();
if (cause instanceof JexlException) {
return (JexlException) cause;
}
if (cause instanceof InterruptedException) {
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 boolean cancel() {
return cancelled.compareAndSet(false, true);
}
/**
* Checks whether this interpreter execution was cancelled due to thread interruption.
* @return true if cancelled, false otherwise
*/
protected boolean isCancelled() {
return cancelled.get() | Thread.currentThread().isInterrupted();
}
/**
* Throws a JexlException.Cancel if script execution was cancelled.
* @param node the node being evaluated
*/
protected void cancelCheck(final JexlNode node) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
}
/**
* Concatenate arguments in call(...).
* <p>When target == context, we are dealing with a global namespace function call
* @param target the pseudo-method owner, first to-be argument
* @param narrow whether we should attempt to narrow number arguments
* @param args the other (non null) arguments
* @return the arguments array
*/
protected Object[] functionArguments(final Object target, final boolean narrow, final 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
final Object[] nargv = new Object[args.length + 1];
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;
}
/**
* Concatenate arguments in call(...).
* @param target the pseudo-method owner, first to-be argument
* @param narrow whether we should attempt to narrow number arguments
* @param args the other (non null) arguments
* @return the arguments array
*/
protected Object[] callArguments(final Object target, final boolean narrow, final Object[] args) {
// makes target 1st args, copy others - optionally narrow numbers
final Object[] nargv = new Object[args.length + 1];
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;
}
/**
* Optionally narrows an argument for a function call.
* @param narrow whether narrowing should occur
* @param arg the argument
* @return the narrowed argument
*/
protected Object functionArgument(final boolean narrow, final Object arg) {
return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
}
/**
* Cached function call.
*/
protected static class Funcall implements JexlNode.Funcall {
/** Whether narrow should be applied to arguments. */
protected final boolean narrow;
/** The JexlMethod to delegate the call to. */
protected final JexlMethod me;
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected Funcall(final JexlMethod jme, final boolean flag) {
this.me = jme;
this.narrow = flag;
}
/**
* Try invocation.
* @param ii the interpreter
* @param name the method name
* @param target the method target
* @param args the method arguments
* @return the method invocation result (or JexlEngine.TRY_FAILED)
*/
protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
}
}
/**
* Cached arithmetic function call.
*/
protected static class ArithmeticFuncall extends Funcall {
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected ArithmeticFuncall(final JexlMethod jme, final boolean flag) {
super(jme, flag);
}
@Override
protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
}
}
/**
* Cached context function call.
*/
protected static class ContextFuncall extends Funcall {
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected ContextFuncall(final JexlMethod jme, final boolean flag) {
super(jme, flag);
}
@Override
protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
}
}
/**
* A ctor that needs a context as 1st argument.
*/
protected static class ContextualCtor extends Funcall {
/**
* Constructor.
* @param jme the method
* @param flag the narrow flag
*/
protected ContextualCtor(final JexlMethod jme, final boolean flag) {
super(jme, flag);
}
@Override
protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
return me.tryInvoke(name, target, ii.callArguments(ii.context, narrow, args));
}
}
/**
* Helping dispatch function calls.
*/
protected class CallDispatcher {
/**
* The syntactic node.
*/
final JexlNode node;
/**
* Whether solution is cacheable.
*/
boolean cacheable = true;
/**
* Whether arguments have been narrowed.
*/
boolean narrow = false;
/**
* The method to call.
*/
JexlMethod vm = null;
/**
* The method invocation target.
*/
Object target = null;
/**
* The actual arguments.
*/
Object[] argv = null;
/**
* The cacheable funcall if any.
*/
Funcall funcall = null;
/**
* Dispatcher ctor.
*
* @param anode the syntactic node.
* @param acacheable whether resolution can be cached
*/
CallDispatcher(final JexlNode anode, final boolean acacheable) {
this.node = anode;
this.cacheable = acacheable;
}
/**
* Whether the method is a target method.
*
* @param ntarget the target instance
* @param mname the method name
* @param arguments the method arguments
* @return true if arithmetic, false otherwise
*/
protected boolean isTargetMethod(final Object ntarget, final String mname, final Object[] arguments) {
// try a method
vm = uberspect.getMethod(ntarget, mname, arguments);
if (vm != null) {
argv = arguments;
target = ntarget;
if (cacheable && vm.isCacheable()) {
funcall = new Funcall(vm, narrow);
}
return true;
}
return false;
}
/**
* Whether the method is a context method.
*
* @param mname the method name
* @param arguments the method arguments
* @return true if arithmetic, false otherwise
*/
protected boolean isContextMethod(final String mname, final Object[] arguments) {
vm = uberspect.getMethod(context, mname, arguments);
if (vm != null) {
argv = arguments;
target = context;
if (cacheable && vm.isCacheable()) {
funcall = new ContextFuncall(vm, narrow);
}
return true;
}
return false;
}
/**
* Whether the method is an arithmetic method.
*
* @param mname the method name
* @param arguments the method arguments
* @return true if arithmetic, false otherwise
*/
protected boolean isArithmeticMethod(final String mname, final Object[] arguments) {
vm = uberspect.getMethod(arithmetic, mname, arguments);
if (vm != null) {
argv = arguments;
target = arithmetic;
if (cacheable && vm.isCacheable()) {
funcall = new ArithmeticFuncall(vm, narrow);
}
return true;
}
return false;
}
/**
* Attempt to reuse last funcall cached in volatile JexlNode.value (if
* it was cacheable).
*
* @param ntarget the target instance
* @param mname the method name
* @param arguments the method arguments
* @return TRY_FAILED if invocation was not possible or failed, the
* result otherwise
*/
protected Object tryEval(final Object ntarget, final String mname, final Object[] arguments) {
// do we have a method/function name ?
// attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
if (mname != null && cacheable && ntarget != null) {
final Object cached = node.jjtGetValue();
if (cached instanceof Funcall) {
return ((Funcall) cached).tryInvoke(InterpreterBase.this, mname, ntarget, arguments);
}
}
return JexlEngine.TRY_FAILED;
}
/**
* Evaluates the method previously dispatched.
*
* @param mname the method name
* @return the method invocation result
* @throws Exception when invocation fails
*/
protected Object eval(final String mname) throws Exception {
// we have either evaluated and returned or might have found a method
if (vm != null) {
// vm cannot be null if xjexl is null
final Object eval = vm.invoke(target, argv);
// cache executor in volatile JexlNode.value
if (funcall != null) {
node.jjtSetValue(funcall);
}
return eval;
}
return unsolvableMethod(node, mname, argv);
}
}
/**
* Gets an attribute of an object.
*
* @param object to retrieve value from
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
* @param node the node that evaluated as the object
* @return the attribute value
*/
protected Object getAttribute(final Object object, final Object attribute, final JexlNode node) {
if (object == null) {
throw new JexlException(node, "object is null");
}
cancelCheck(node);
final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
final Object result = operators.tryOverload(node, operator, object, attribute);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
Exception xcause = null;
try {
// attempt to reuse last executor cached in volatile JexlNode.value
if (node != null && cache) {
final Object cached = node.jjtGetValue();
if (cached instanceof JexlPropertyGet) {
final JexlPropertyGet vg = (JexlPropertyGet) cached;
final Object value = vg.tryInvoke(object, attribute);
if (!vg.tryFailed(value)) {
return value;
}
}
}
// resolve that property
final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
final JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
if (vg != null) {
final Object value = vg.invoke(object);
// cache executor in volatile JexlNode.value
if (node != null && cache && vg.isCacheable()) {
node.jjtSetValue(vg);
}
return value;
}
} catch (final Exception xany) {
xcause = xany;
}
// lets fail
if (node != null) {
final boolean safe = (node instanceof ASTIdentifierAccess) && ((ASTIdentifierAccess) node).isSafe();
if (safe) {
return null;
} else {
final String attrStr = attribute != null ? attribute.toString() : null;
return unsolvableProperty(node, attrStr, true, xcause);
}
} else {
// direct call
final String error = "unable to get object property"
+ ", class: " + object.getClass().getName()
+ ", property: " + attribute;
throw new UnsupportedOperationException(error, xcause);
}
}
/**
* Sets an attribute of an object.
*
* @param object to set the value to
* @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
* @param value the value to assign to the object's attribute
* @param node the node that evaluated as the object
*/
protected void setAttribute(final Object object, final Object attribute, final Object value, final JexlNode node) {
cancelCheck(node);
final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
final Object result = operators.tryOverload(node, operator, object, attribute, value);
if (result != JexlEngine.TRY_FAILED) {
return;
}
Exception xcause = null;
try {
// attempt to reuse last executor cached in volatile JexlNode.value
if (node != null && cache) {
final Object cached = node.jjtGetValue();
if (cached instanceof JexlPropertySet) {
final JexlPropertySet setter = (JexlPropertySet) cached;
final Object eval = setter.tryInvoke(object, attribute, value);
if (!setter.tryFailed(eval)) {
return;
}
}
}
final List<JexlUberspect.PropertyResolver> 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
final Object[] narrow = {value};
if (arithmetic.narrowArguments(narrow)) {
vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
}
}
if (vs != null) {
// cache executor in volatile JexlNode.value
vs.invoke(object, value);
if (node != null && cache && vs.isCacheable()) {
node.jjtSetValue(vs);
}
return;
}
} catch (final Exception xany) {
xcause = xany;
}
// lets fail
if (node != null) {
final String attrStr = attribute != null ? attribute.toString() : null;
unsolvableProperty(node, attrStr, true, xcause);
} else {
// direct call
final String error = "unable to set object property"
+ ", class: " + object.getClass().getName()
+ ", property: " + attribute
+ ", argument: " + value.getClass().getSimpleName();
throw new UnsupportedOperationException(error, xcause);
}
}
}