blob: 5a0609056189bbf8bce7b178dd46a91c5d3dfdcb [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 org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.ParserVisitor;
import org.apache.commons.logging.Log;
/**
* The helper base of an interpreter of JEXL syntax.
* @since 3.0
*/
public abstract class InterpreterBase extends ParserVisitor {
/** The JEXL engine. */
protected final Engine jexl;
/** The logger. */
protected final Log logger;
/** The uberspect. */
protected final JexlUberspect uberspect;
/** The arithmetic handler. */
protected final JexlArithmetic arithmetic;
/** The context to store/retrieve variables. */
protected final JexlContext context;
/** Cancellation support. */
protected volatile boolean cancelled = false;
/** Empty parameters for method matching. */
protected static final Object[] EMPTY_PARAMS = new Object[0];
/**
* Creates an interpreter base.
* @param engine the engine creating this interpreter
* @param aContext the context to evaluate expression
*/
protected InterpreterBase(Engine engine, JexlContext aContext) {
this.jexl = engine;
this.logger = jexl.logger;
this.uberspect = jexl.uberspect;
this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
if (this.context instanceof JexlEngine.Options) {
JexlEngine.Options opts = (JexlEngine.Options) context;
this.arithmetic = jexl.arithmetic.options(opts);
if (!arithmetic.getClass().equals(jexl.arithmetic.getClass())) {
logger.error("expected arithmetic to be " + jexl.arithmetic.getClass().getSimpleName()
+ ", got " + arithmetic.getClass().getSimpleName()
);
}
} else {
this.arithmetic = jexl.arithmetic;
}
}
/** Java7 AutoCloseable interface defined?. */
protected static final Class<?> AUTOCLOSEABLE;
static {
Class<?> c;
try {
c = Class.forName("java.lang.AutoCloseable");
} catch (ClassNotFoundException xclass) {
c = null;
}
AUTOCLOSEABLE = c;
}
/**
* Attempt to call close() if supported.
* <p>This is used when dealing with auto-closeable (duck-like) objects
* @param closeable the object we'd like to close
*/
protected void closeIfSupported(Object closeable) {
if (closeable != null) {
//if (AUTOCLOSEABLE == null || AUTOCLOSEABLE.isAssignableFrom(closeable.getClass())) {
JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
if (mclose != null) {
try {
mclose.invoke(closeable, EMPTY_PARAMS);
} catch (Exception xignore) {
logger.warn(xignore);
}
}
//}
}
}
/**
* Whether this interpreter is currently evaluating with a strict engine flag.
* @return true if strict engine, false otherwise
*/
protected boolean isStrictEngine() {
if (this.context instanceof JexlEngine.Options) {
JexlEngine.Options opts = (JexlEngine.Options) context;
Boolean strict = opts.isStrict();
if (strict != null) {
return strict.booleanValue();
}
}
return jexl.isStrict();
}
/**
* Whether this interpreter is currently evaluating with a silent mode.
* @return true if silent, false otherwise
*/
protected boolean isSilent() {
if (this.context instanceof JexlEngine.Options) {
JexlEngine.Options opts = (JexlEngine.Options) context;
Boolean silent = opts.isSilent();
if (silent != null) {
return silent.booleanValue();
}
}
return jexl.isSilent();
}
/** @return true if interrupt throws a JexlException.Cancel. */
protected boolean isCancellable() {
if (this.context instanceof JexlEngine.Options) {
JexlEngine.Options opts = (JexlEngine.Options) context;
Boolean ocancellable = opts.isCancellable();
if (ocancellable != null) {
return ocancellable.booleanValue();
}
}
return jexl.cancellable;
}
/**
* Finds the node causing a NPE for diadic operators.
* @param xrt the RuntimeException
* @param node the parent node
* @param left the left argument
* @param right the right argument
* @return the left, right or parent node
*/
protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
if (xrt instanceof JexlArithmetic.NullOperand) {
if (left == null) {
return node.jjtGetChild(0);
}
if (right == null) {
return node.jjtGetChild(1);
}
}
return node;
}
/**
* Triggered when a variable can not be resolved.
* @param node the node where the error originated from
* @param var the variable name
* @param undef whether the variable is undefined or null
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object unsolvableVariable(JexlNode node, String var, boolean undef) {
if (isStrictEngine()) {
if (isSilent()) {
logger.warn(JexlException.variableError(node, var, undef));
} else if (undef || arithmetic.isStrict()){
throw new JexlException.Variable(node, var, undef);
}
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.variableError(node, var, undef));
}
return null;
}
/**
* Triggered when a method can not be resolved.
* @param node the node where the error originated from
* @param method the method name
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object unsolvableMethod(JexlNode node, String method) {
if (isStrictEngine()) {
if (isSilent()) {
logger.warn(JexlException.methodError(node, method));
} else {
throw new JexlException.Method(node, method);
}
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.methodError(node, method));
}
return null;
}
/**
* Triggered when a property can not be resolved.
* @param node the node where the error originated from
* @param var the property name
* @param cause the cause if any
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object unsolvableProperty(JexlNode node, String var, Throwable cause) {
if (isStrictEngine()) {
if (isSilent()) {
logger.warn(JexlException.propertyError(node, var), cause);
} else {
throw new JexlException.Property(node, var, cause);
}
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.propertyError(node, var), cause);
}
return null;
}
/**
* Triggered when an operator fails.
* @param node the node where the error originated from
* @param operator the method name
* @param cause the cause of error (if any)
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object operatorError(JexlNode node, JexlOperator operator, Throwable cause) {
if (isStrictEngine()) {
if (isSilent()) {
logger.warn(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
} else {
throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
}
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
}
return null;
}
/**
* Triggered when method, function or constructor invocation fails.
* @param xjexl the JexlException wrapping the original error
* @return throws a JexlException if strict and not silent, null otherwise
*/
protected Object invocationFailed(JexlException xjexl) {
if (xjexl instanceof JexlException.Return
|| xjexl instanceof JexlException.Cancel) {
throw xjexl;
}
if (isStrictEngine()) {
if (isSilent()) {
logger.warn(xjexl.getMessage(), xjexl.getCause());
} else {
throw xjexl;
}
} else if (logger.isDebugEnabled()) {
logger.debug(xjexl);
}
return null;
}
/**
* Wraps an exception thrown by an invocation.
* @param node the node triggering the exception
* @param methodName the method/function name
* @param xany the cause
* @return a JexlException
*/
protected JexlException exceptionOnInvocation(JexlNode node, String methodName, Exception xany) {
Throwable cause = xany.getCause();
if (cause instanceof JexlException) {
return (JexlException) cause;
}
if (cause instanceof InterruptedException) {
cancelled = true;
return new JexlException.Cancel(node);
}
return new JexlException(node, methodName, xany);
}
/**
* Triggered when an annotation processing fails.
* @param node the node where the error originated from
* @param annotation the annotation name
* @param cause the cause of error (if any)
* @return throws a JexlException if strict and not silent, null otherwise
*/
protected Object annotationError(JexlNode node, String annotation, Throwable cause) {
if (isStrictEngine()) {
if (isSilent()) {
logger.warn(JexlException.annotationError(node, annotation), cause);
} else {
throw new JexlException.Annotation(node, annotation, cause);
}
} else if (logger.isDebugEnabled()) {
logger.debug(JexlException.annotationError(node, annotation), cause);
}
return null;
}
/**
* Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
* @return false if already cancelled, true otherwise
*/
protected synchronized boolean cancel() {
if (cancelled) {
return false;
} else {
cancelled = true;
return true;
}
}
/**
* Checks whether this interpreter execution was canceled due to thread interruption.
* @return true if canceled, false otherwise
*/
protected synchronized boolean isCancelled() {
if (!cancelled) {
cancelled = Thread.currentThread().isInterrupted();
}
return cancelled;
}
}