
org.apache.commons.jexl3.internal.InterpreterBase Maven / Gradle / Ivy
Show all versions of commons-jexl3 Show documentation
/*
* 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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlContext.NamespaceFunctor;
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.ASTNullpNode;
import org.apache.commons.jexl3.parser.ASTReference;
import org.apache.commons.jexl3.parser.ASTTernaryNode;
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 {
/**
* Helping dispatch function calls.
*/
protected class CallDispatcher {
/** The syntactic node. */
final JexlNode node;
/** Whether solution is cacheable. */
final boolean cacheable;
/** Whether arguments have been narrowed. */
boolean narrow;
/** The method to call. */
JexlMethod vm;
/** The method invocation target. */
Object target;
/** The actual arguments. */
Object[] argv;
/** The cacheable funcall if any. */
Funcall funcall;
/**
* 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;
}
/**
* Evaluates the method previously dispatched.
*
* @param methodName the method name
* @return the method invocation result
* @throws Exception when invocation fails
*/
protected Object eval(final String methodName) 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, methodName, argv);
}
/**
* Whether the method is an arithmetic method.
*
* @param methodName the method name
* @param arguments the method arguments
* @return true if arithmetic, false otherwise
*/
protected boolean isArithmeticMethod(final String methodName, final Object[] arguments) {
vm = uberspect.getMethod(arithmetic, methodName, arguments);
if (vm != null) {
argv = arguments;
target = arithmetic;
if (cacheable && vm.isCacheable()) {
funcall = new ArithmeticFuncall(vm, narrow);
}
return true;
}
return false;
}
/**
* Whether the method is a context method.
*
* @param methodName the method name
* @param arguments the method arguments
* @return true if arithmetic, false otherwise
*/
protected boolean isContextMethod(final String methodName, final Object[] arguments) {
vm = uberspect.getMethod(context, methodName, 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 a target method.
*
* @param ntarget the target instance
* @param methodName the method name
* @param arguments the method arguments
* @return true if arithmetic, false otherwise
*/
protected boolean isTargetMethod(final Object ntarget, final String methodName, final Object[] arguments) {
// try a method
vm = uberspect.getMethod(ntarget, methodName, arguments);
if (vm != null) {
argv = arguments;
target = ntarget;
if (cacheable && vm.isCacheable()) {
funcall = new Funcall(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 methodName 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 methodName, 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 (methodName != null && cacheable && ntarget != null) {
final Object cached = node.jjtGetValue();
if (cached instanceof Funcall) {
return ((Funcall) cached).tryInvoke(InterpreterBase.this, methodName, ntarget, arguments);
}
}
return JexlEngine.TRY_FAILED;
}
}
/**
* Cached arithmetic function call.
*/
protected static class ArithmeticFuncall extends Funcall {
/**
* Constructs a new instance.
* @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 {
/**
* Constructs a new instance.
* @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 {
/**
* Constructs a new instance.
* @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));
}
}
/**
* 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;
/**
* Constructs a new instance.
* @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));
}
}
/** Empty parameters for method matching. */
protected static final Object[] EMPTY_PARAMS = {};
/**
* Pretty-prints a failing property value (de)reference.
* Used by calls to unsolvableProperty(...).
* @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) : "???";
}
/** 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;
/** The namespace resolver. */
protected final JexlContext.NamespaceResolver ns;
/** The class name resolver. */
protected final JexlContext.ClassNameResolver fqcnSolver;
/** The operators evaluation delegate. */
protected final JexlOperator.Uberspect operators;
/** The map of 'prefix:function' to object resolving as namespaces. */
protected final Map functions;
/** The map of dynamically created namespaces, NamespaceFunctor or duck-types of those. */
protected Map 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 : JexlEngine.EMPTY_CONTEXT;
this.cache = engine.cache != null;
final JexlArithmetic jexla = jexl.arithmetic;
this.options = opts == null ? engine.evalOptions(aContext) : opts;
this.arithmetic = jexla.options(options);
if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass()) && logger.isWarnEnabled()) {
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 = JexlEngine.EMPTY_NS;
}
AtomicBoolean acancel = null;
if (this.context instanceof JexlContext.CancellationHandle) {
acancel = ((JexlContext.CancellationHandle) context).getCancellation();
}
this.cancelled = acancel != null ? acancel : new AtomicBoolean();
this.functions = options.getNamespaces();
this.functors = null;
JexlOperator.Uberspect ops = uberspect.getOperator(arithmetic);
if (ops == null) {
ops = new Operator(uberspect, arithmetic);
}
this.operators = ops;
// the import package facility
final Collection imports = options.getImports();
this.fqcnSolver = imports.isEmpty()
? engine.classNameSolver
: new FqcnResolver(engine.classNameSolver).importPackages(imports);
}
/**
* 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;
fqcnSolver = ii.fqcnSolver;
}
/**
* 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);
}
if (logger.isDebugEnabled()) {
logger.debug(JexlException.annotationError(node, annotation), cause);
}
return null;
}
/**
* 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;
}
/**
* 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);
}
/**
* 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);
}
}
/**
* 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(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);
}
}
}
}
/**
* Attempt to call close() if supported.
*
This is used when dealing with auto-closeable (duck-like) objects
* @param closeables the object queue we'd like to close
*/
protected void closeIfSupported(final Queue