JexlEngine.java
/*
* 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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.MathContext;
import java.net.URL;
import java.nio.charset.Charset;
import org.apache.commons.jexl3.introspection.JexlUberspect;
/**
* Creates and evaluates JexlExpression and JexlScript objects.
* Determines the behavior of expressions and scripts during their evaluation with respect to:
* <ul>
* <li>Introspection, see {@link JexlUberspect}</li>
* <li>Arithmetic and comparison, see {@link JexlArithmetic}</li>
* <li>Error reporting</li>
* <li>Logging</li>
* </ul>
*
* <p>Note that methods that evaluate expressions may throw <em>unchecked</em> exceptions;
* The {@link JexlException} are thrown in "non-silent" mode but since these are
* RuntimeException, user-code <em>should</em> catch them wherever most appropriate.</p>
*
* @since 2.0
*/
public abstract class JexlEngine {
/**
* The empty context class, public for instrospection.
*/
public static final class EmptyContext implements JexlContext {
/**
* Default ctor.
*/
EmptyContext() {}
@Override
public Object get(final String name) {
return null;
}
@Override
public boolean has(final String name) {
return false;
}
@Override
public void set(final String name, final Object value) {
throw new UnsupportedOperationException("Not supported in void context.");
}
}
/**
* The empty/static/non-mutable JexlNamespace class, public for instrospection.
*/
public static final class EmptyNamespaceResolver implements JexlContext.NamespaceResolver {
/**
* Default ctor.
*/
EmptyNamespaceResolver() {}
@Override
public Object resolveNamespace(final String name) {
return null;
}
}
/** The failure marker class. */
private static final class FailObject {
/**
* Default ctor.
*/
FailObject() {}
@Override
public String toString() {
return "tryExecute failed";
}
}
/**
* Script evaluation options.
* <p>The JexlContext used for evaluation can implement this interface to alter behavior.</p>
* @deprecated 3.2
*/
@Deprecated
public interface Options {
/**
* The MathContext instance used for +,-,/,*,% operations on big decimals.
*
* @return the math context
*/
MathContext getArithmeticMathContext();
/**
* The BigDecimal scale used for comparison and coercion operations.
*
* @return the scale
*/
int getArithmeticMathScale();
/**
* The charset used for parsing.
*
* @return the charset
*/
Charset getCharset();
/**
* Whether evaluation will throw JexlException.Cancel (true) or return null (false) when interrupted.
* @return true when cancellable, false otherwise
* @since 3.1
*/
Boolean isCancellable();
/**
* Sets whether the engine will throw a {@link JexlException} when an error is encountered during evaluation.
*
* @return true if silent, false otherwise
*/
Boolean isSilent();
/**
* Checks whether the engine considers unknown variables, methods, functions and constructors as errors or
* evaluates them as null.
*
* @return true if strict, false otherwise
*/
Boolean isStrict();
/**
* Checks whether the arithmetic triggers errors during evaluation when null is used as an operand.
*
* @return true if strict, false otherwise
*/
Boolean isStrictArithmetic();
}
/** A marker singleton for invocation failures in tryInvoke. */
public static final Object TRY_FAILED = new FailObject();
/**
* The thread local context.
*/
protected static final java.lang.ThreadLocal<JexlContext.ThreadLocal> CONTEXT =
new java.lang.ThreadLocal<>();
/**
* The thread local engine.
*/
protected static final java.lang.ThreadLocal<JexlEngine> ENGINE =
new java.lang.ThreadLocal<>();
/** Default features. */
public static final JexlFeatures DEFAULT_FEATURES = new JexlFeatures();
/**
* An empty/static/non-mutable JexlContext singleton used instead of null context.
*/
public static final JexlContext EMPTY_CONTEXT = new EmptyContext();
/**
* An empty/static/non-mutable JexlNamespace singleton used instead of null namespace.
*/
public static final JexlContext.NamespaceResolver EMPTY_NS = new EmptyNamespaceResolver();
/** The default Jxlt cache size. */
private static final int JXLT_CACHE_SIZE = 256;
/**
* Accesses the current thread local context.
*
* @return the context or null
*/
public static JexlContext.ThreadLocal getThreadContext() {
return CONTEXT.get();
}
/**
* Accesses the current thread local engine.
* <p>Advanced: you should only use this to retrieve the engine within a method/ctor called through the evaluation
* of a script/expression.</p>
* @return the engine or null
*/
public static JexlEngine getThreadEngine() {
return ENGINE.get();
}
/**
* Sets the current thread local context.
* <p>This should only be used carefully, for instance when re-evaluating a "stored" script that requires a
* given Namespace resolver. Remember to synchronize access if context is shared between threads.
*
* @param tls the thread local context to set
*/
public static void setThreadContext(final JexlContext.ThreadLocal tls) {
CONTEXT.set(tls);
}
/**
* Creates a string from a reader.
*
* @param reader to be read.
* @return the contents of the reader as a String.
* @throws IOException on any error reading the reader.
*/
protected static String toString(final BufferedReader reader) throws IOException {
final StringBuilder buffer = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line).append('\n');
}
return buffer.toString();
}
/**
* Clears the expression cache.
*/
public abstract void clearCache();
/**
* Creates an JexlExpression from a String containing valid JEXL syntax.
* This method parses the expression which must contain either a reference or an expression.
*
* @param info An info structure to carry debugging information if needed
* @param expression A String containing valid JEXL syntax
* @return An {@link JexlExpression} which can be evaluated using a {@link JexlContext}
* @throws JexlException if there is a problem parsing the script
*/
public abstract JexlExpression createExpression(JexlInfo info, String expression);
/**
* Creates a JexlExpression from a String containing valid JEXL syntax.
* This method parses the expression which must contain either a reference or an expression.
*
* @param expression A String containing valid JEXL syntax
* @return An {@link JexlExpression} which can be evaluated using a {@link JexlContext}
* @throws JexlException if there is a problem parsing the script
*/
public final JexlExpression createExpression(final String expression) {
return createExpression(null, expression);
}
/**
* Create an information structure for dynamic set/get/invoke/new.
* <p>This gathers the class, method and line number of the first calling method
* outside of o.a.c.jexl3.</p>
*
* @return a JexlInfo instance
*/
public JexlInfo createInfo() {
return new JexlInfo();
}
/**
* Creates a JexlInfo instance.
*
* @param fn url/file/template/script user given name
* @param l line number
* @param c column number
* @return a JexlInfo instance
*/
public JexlInfo createInfo(final String fn, final int l, final int c) {
return new JexlInfo(fn, l, c);
}
/**
* Creates a new {@link JxltEngine} instance using this engine.
*
* @return a JEXL Template engine
*/
public JxltEngine createJxltEngine() {
return createJxltEngine(true);
}
/**
* Creates a new {@link JxltEngine} instance using this engine.
*
* @param noScript whether the JxltEngine only allows Jexl expressions or scripts
* @return a JEXL Template engine
*/
public JxltEngine createJxltEngine(final boolean noScript) {
return createJxltEngine(noScript, JXLT_CACHE_SIZE, '$', '#');
}
/**
* Creates a new instance of {@link JxltEngine} using this engine.
*
* @param noScript whether the JxltEngine only allows JEXL expressions or scripts
* @param cacheSize the number of expressions in this cache, default is 256
* @param immediate the immediate template expression character, default is '$'
* @param deferred the deferred template expression character, default is '#'
* @return a JEXL Template engine
*/
public abstract JxltEngine createJxltEngine(boolean noScript, int cacheSize, char immediate, char deferred);
/**
* Creates a Script from a {@link File} containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file.
* @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
* @throws JexlException if there is a problem reading or parsing the script.
*/
public final JexlScript createScript(final File scriptFile) {
return createScript(null, null, readSource(scriptFile), (String[]) null);
}
/**
* Creates a Script from a {@link File} containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file.
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation.
* @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
* @throws JexlException if there is a problem reading or parsing the script.
*/
public final JexlScript createScript(final File scriptFile, final String... names) {
return createScript(null, null, readSource(scriptFile), names);
}
/**
* Creates a JexlScript from a String containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param features A set of features that will be enforced during parsing
* @param info An info structure to carry debugging information if needed
* @param source A string containing valid JEXL syntax
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation
* @return A {@link JexlScript} which can be executed using a {@link JexlContext}
* @throws JexlException if there is a problem parsing the script
*/
public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String... names);
/**
* Creates a Script from a {@link File} containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param info An info structure to carry debugging information if needed
* @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file.
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation.
* @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
* @throws JexlException if there is a problem reading or parsing the script.
*/
public final JexlScript createScript(final JexlInfo info, final File scriptFile, final String... names) {
return createScript(null, info, readSource(scriptFile), names);
}
/**
* Creates a JexlScript from a String containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param info An info structure to carry debugging information if needed
* @param source A string containing valid JEXL syntax
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation
* @return A {@link JexlScript} which can be executed using a {@link JexlContext}
* @throws JexlException if there is a problem parsing the script
*/
public final JexlScript createScript(final JexlInfo info, final String source, final String... names) {
return createScript(null, info, source, names);
}
/**
* Creates a Script from a {@link URL} containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param info An info structure to carry debugging information if needed
* @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null.
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation.
* @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
* @throws JexlException if there is a problem reading or parsing the script.
*/
public final JexlScript createScript(final JexlInfo info, final URL scriptUrl, final String... names) {
return createScript(null, info, readSource(scriptUrl), names);
}
/**
* Creates a Script from a String containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param scriptText A String containing valid JEXL syntax
* @return A {@link JexlScript} which can be executed using a {@link JexlContext}
* @throws JexlException if there is a problem parsing the script.
*/
public final JexlScript createScript(final String scriptText) {
return createScript(null, null, scriptText, (String[]) null);
}
/**
* Creates a Script from a String containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param source A String containing valid JEXL syntax
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation
* @return A {@link JexlScript} which can be executed using a {@link JexlContext}
* @throws JexlException if there is a problem parsing the script
*/
public final JexlScript createScript(final String source, final String... names) {
return createScript(null, null, source, names);
}
/**
* Creates a Script from a {@link URL} containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null.
* @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
* @throws JexlException if there is a problem reading or parsing the script.
*/
public final JexlScript createScript(final URL scriptUrl) {
return createScript(null, readSource(scriptUrl), (String[]) null);
}
/**
* Creates a Script from a {@link URL} containing valid JEXL syntax.
* This method parses the script and validates the syntax.
*
* @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null.
* @param names The script parameter names used during parsing; a corresponding array of arguments containing
* values should be used during evaluation.
* @return A {@link JexlScript} which can be executed with a {@link JexlContext}.
* @throws JexlException if there is a problem reading or parsing the script.
*/
public final JexlScript createScript(final URL scriptUrl, final String... names) {
return createScript(null, null, readSource(scriptUrl), names);
}
/**
* Gets this engine underlying {@link JexlArithmetic}.
*
* @return the arithmetic
*/
public abstract JexlArithmetic getArithmetic();
/**
* Gets the charset used for parsing.
*
* @return the charset
*/
public abstract Charset getCharset();
/**
* Accesses properties of a bean using an expression.
* <p>
* If the JEXL engine is silent, errors will be logged through its logger as warning.
* </p>
*
* @param context the evaluation context
* @param bean the bean to get properties from
* @param expr the property expression
* @return the value of the property
* @throws JexlException if there is an error parsing the expression or during evaluation
*/
public abstract Object getProperty(JexlContext context, Object bean, String expr);
/**
* Accesses properties of a bean using an expression.
* <p>
* jexl.get(myobject, "foo.bar"); should equate to
* myobject.getFoo().getBar(); (or myobject.getFoo().get("bar"))
* </p>
* <p>
* If the JEXL engine is silent, errors will be logged through its logger as warning.
* </p>
*
* @param bean the bean to get properties from
* @param expr the property expression
* @return the value of the property
* @throws JexlException if there is an error parsing the expression or during evaluation
*/
public abstract Object getProperty(Object bean, String expr);
/**
* Gets this engine underlying {@link JexlUberspect}.
*
* @return the uberspect
*/
public abstract JexlUberspect getUberspect();
/**
* Invokes an object's method by name and arguments.
*
* @param obj the method's invoker object
* @param meth the method's name
* @param args the method's arguments
* @return the method returned value or null if it failed and engine is silent
* @throws JexlException if method could not be found or failed and engine is not silent
*/
public abstract Object invokeMethod(Object obj, String meth, Object... args);
/**
* Checks whether this engine will throw JexlException.Cancel (true) or return null (false) when interrupted
* during an execution.
*
* @return true if cancellable, false otherwise
*/
public abstract boolean isCancellable();
/**
* Checks whether this engine is in debug mode.
*
* @return true if debug is on, false otherwise
*/
public abstract boolean isDebug();
/**
* Checks whether this engine throws JexlException during evaluation.
*
* @return true if silent, false (default) otherwise
*/
public abstract boolean isSilent();
/**
* Checks whether this engine considers unknown variables, methods, functions and constructors as errors.
*
* @return true if strict, false otherwise
*/
public abstract boolean isStrict();
/**
* Creates a new instance of an object using the most appropriate constructor based on the arguments.
*
* @param <T> the type of object
* @param clazz the class to instantiate
* @param args the constructor arguments
* @return the created object instance or null on failure when silent
*/
public abstract <T> T newInstance(Class<? extends T> clazz, Object... args);
/**
* Creates a new instance of an object using the most appropriate constructor based on the arguments.
*
* @param clazz the name of the class to instantiate resolved through this engine's class loader
* @param args the constructor arguments
* @return the created object instance or null on failure when silent
*/
public abstract Object newInstance(String clazz, Object... args);
/**
* Reads a JEXL source from a File.
*
* @param file the script file
* @return the source
*/
protected String readSource(final File file) {
if (file == null) {
throw new NullPointerException("source file is null");
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),
getCharset()))) {
return toString(reader);
} catch (final IOException xio) {
throw new JexlException(createInfo(file.toString(), 1, 1), "could not read source File", xio);
}
}
/**
* Reads a JEXL source from an URL.
*
* @param url the script url
* @return the source
*/
protected String readSource(final URL url) {
if (url == null) {
throw new NullPointerException("source URL is null");
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), getCharset()))) {
return toString(reader);
} catch (final IOException xio) {
throw new JexlException(createInfo(url.toString(), 1, 1), "could not read source URL", xio);
}
}
/**
* Sets the class loader used to discover classes in 'new' expressions.
* <p>This method is <em>not</em> thread safe; it may be called after JexlEngine
* initialization and allow scripts to use new classes definitions.</p>
*
* @param loader the class loader to use
*/
public abstract void setClassLoader(ClassLoader loader);
/**
* Assign properties of a bean using an expression.
* <p>
* If the JEXL engine is silent, errors will be logged through
* its logger as warning.
* </p>
*
* @param context the evaluation context
* @param bean the bean to set properties in
* @param expr the property expression
* @param value the value of the property
* @throws JexlException if there is an error parsing the expression or during evaluation
*/
public abstract void setProperty(JexlContext context, Object bean, String expr, Object value);
/**
* Assign properties of a bean using an expression.
* <p>
* jexl.set(myobject, "foo.bar", 10); should equate to
* myobject.getFoo().setBar(10); (or myobject.getFoo().put("bar", 10) )
* </p>
* <p>
* If the JEXL engine is silent, errors will be logged through its logger as warning.
* </p>
*
* @param bean the bean to set properties in
* @param expr the property expression
* @param value the value of the property
* @throws JexlException if there is an error parsing the expression or during evaluation
*/
public abstract void setProperty(Object bean, String expr, Object value);
}