org.jbpm.jpdl.el.impl.ExpressionEvaluatorImpl Maven / Gradle / Ivy
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact [email protected].
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.jbpm.jpdl.el.impl;
import java.io.Reader;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.jbpm.jpdl.el.ELException;
import org.jbpm.jpdl.el.ExpressionEvaluator;
import org.jbpm.jpdl.el.FunctionMapper;
import org.jbpm.jpdl.el.VariableResolver;
import org.jbpm.jpdl.el.parser.ELParser;
import org.jbpm.jpdl.el.parser.ELToken;
import org.jbpm.jpdl.el.parser.ELTokenMgrError;
import org.jbpm.jpdl.el.parser.ParseException;
/**
*
* This is the main class for evaluating expression Strings. An
* expression String is a String that may contain expressions of the
* form ${...}. Multiple expressions may appear in the same
* expression String. In such a case, the expression String's value
* is computed by concatenating the String values of those evaluated
* expressions and any intervening non-expression text, then
* converting the resulting String to the expected type using the
* PropertyEditor mechanism.
*
*
In the special case where the expression String is a single
* expression, the value of the expression String is determined by
* evaluating the expression, without any intervening conversion to a
* String.
*
*
The evaluator maintains a cache mapping expression Strings to
* their parsed results. For expression Strings containing no
* expression elements, it maintains a cache mapping
* ExpectedType/ExpressionString to parsed value, so that static
* expression Strings won't have to go through a conversion step every
* time they are used. All instances of the evaluator share the same
* cache. The cache may be bypassed by setting a flag on the
* evaluator's constructor.
*
*
The evaluator must be passed a VariableResolver in its
* constructor. The VariableResolver is used to resolve variable
* names encountered in expressions, and can also be used to implement
* "implicit objects" that are always present in the namespace.
* Different applications will have different policies for variable
* lookups and implicit objects - these differences can be
* encapsulated in the VariableResolver passed to the evaluator's
* constructor.
*
*
Most VariableResolvers will need to perform their resolution
* against some context. For example, a JSP environment needs a
* PageContext to resolve variables. The evaluate() method takes a
* generic Object context which is eventually passed to the
* VariableResolver - the VariableResolver is responsible for casting
* the context to the proper type.
*
*
Once an evaluator instance has been constructed, it may be used
* multiple times, and may be used by multiple simultaneous Threads.
* In other words, an evaluator instance is well-suited for use as a
* singleton.
*
* @author Nathan Abramson - Art Technology Group
* @author Shawn Bayern
* @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author$
**/
public class ExpressionEvaluatorImpl
extends ExpressionEvaluator
{
//-------------------------------------
// Properties
//-------------------------------------
//-------------------------------------
// Member variables
//-------------------------------------
/** The mapping from expression String to its parsed form (String,
Expression, or ExpressionString) **/
static Map sCachedExpressionStrings =
Collections.synchronizedMap (new HashMap ());
/** The mapping from ExpectedType to Maps mapping literal String to
parsed value **/
static Map sCachedExpectedTypes = new HashMap ();
/** The static Logger **/
static Logger sLogger = new Logger (System.out);
/** Flag if the cache should be bypassed **/
boolean mBypassCache;
//-------------------------------------
/**
*
* Constructor
**/
public ExpressionEvaluatorImpl () { }
/**
*
* Constructor
*
* @param pBypassCache flag indicating if the cache should be
* bypassed
**/
public ExpressionEvaluatorImpl (boolean pBypassCache)
{
mBypassCache = pBypassCache;
}
//-------------------------------------
/**
*
* Evaluates the given expression String
*
* @param pExpressionString The expression to be evaluated.
* @param pExpectedType The expected type of the result of the evaluation
* @param pResolver A VariableResolver instance that can be used at
* runtime to resolve the name of implicit objects into Objects.
* @param functions A FunctionMapper to resolve functions found in
* the expression. It can be null, in which case no functions
* are supported for this invocation.
* @return the expression String evaluated to the given expected
* type
**/
public Object evaluate (String pExpressionString,
Class pExpectedType,
VariableResolver pResolver,
FunctionMapper functions)
throws ELException
{
return evaluate (pExpressionString,
pExpectedType,
pResolver,
functions,
sLogger);
}
/**
*
* Prepare an expression for later evaluation. This method should perform
* syntactic validation of the expression; if in doing so it detects
* errors, it should raise an ELParseException.
*
* @param expression The expression to be evaluated.
* @param expectedType The expected type of the result of the evaluation
* @param fMapper A FunctionMapper to resolve functions found in
* the expression. It can be null, in which case no functions
* are supported for this invocation. The ExpressionEvaluator
* must not hold on to the FunctionMapper reference after
* returning from parseExpression()
. The
* Expression
object returned must invoke the same
* functions regardless of whether the mappings in the
* provided FunctionMapper
instance change between
* calling ExpressionEvaluator.parseExpression()
* and Expression.evaluate()
.
* @return The Expression object encapsulating the arguments.
*
* @exception ELException Thrown if parsing errors were found.
**/
public org.jbpm.jpdl.el.Expression parseExpression(String expression,
Class expectedType,
FunctionMapper fMapper)
throws ELException
{
// Validate and then create an Expression object.
parseExpressionString(expression);
// Create an Expression object that knows how to evaluate this.
return new JSTLExpression(this, expression, expectedType, fMapper);
}
//-------------------------------------
/**
*
* Evaluates the given expression string
**/
Object evaluate (String pExpressionString,
Class pExpectedType,
VariableResolver pResolver,
FunctionMapper functions,
Logger pLogger)
throws ELException
{
// Check for null expression strings
if (pExpressionString == null) {
throw new ELException
(Constants.NULL_EXPRESSION_STRING);
}
// Get the parsed version of the expression string
Object parsedValue = parseExpressionString (pExpressionString);
// Evaluate differently based on the parsed type
if (parsedValue instanceof String) {
// Convert the String, and cache the conversion
String strValue = (String) parsedValue;
return convertStaticValueToExpectedType (strValue,
pExpectedType,
pLogger);
}
else if (parsedValue instanceof Expression) {
// Evaluate the expression and convert
Object value =
((Expression) parsedValue).evaluate (pResolver,
functions,
pLogger);
return convertToExpectedType (value,
pExpectedType,
pLogger);
}
else if (parsedValue instanceof ExpressionString) {
// Evaluate the expression/string list and convert
String strValue =
((ExpressionString) parsedValue).evaluate (pResolver,
functions,
pLogger);
return convertToExpectedType (strValue,
pExpectedType,
pLogger);
}
else {
// This should never be reached
return null;
}
}
//-------------------------------------
/**
*
* Gets the parsed form of the given expression string. If the
* parsed form is cached (and caching is not bypassed), return the
* cached form, otherwise parse and cache the value. Returns either
* a String, Expression, or ExpressionString.
**/
public Object parseExpressionString (String pExpressionString)
throws ELException
{
// See if it's an empty String
if (pExpressionString.length () == 0) {
return "";
}
// See if it's in the cache
Object ret =
mBypassCache ?
null :
sCachedExpressionStrings.get (pExpressionString);
if (ret == null) {
// Parse the expression
Reader r = new StringReader (pExpressionString);
ELParser parser = new ELParser (r);
try {
ret = parser.ExpressionString ();
sCachedExpressionStrings.put (pExpressionString, ret);
}
catch (ParseException exc) {
throw new ELException
(formatParseException (pExpressionString,
exc));
}
catch (ELTokenMgrError exc) {
// Note - this should never be reached, since the parser is
// constructed to tokenize any input (illegal inputs get
// parsed to or
//
throw new ELException (exc.getMessage ());
}
}
return ret;
}
//-------------------------------------
/**
*
* Converts the given value to the specified expected type.
**/
Object convertToExpectedType (Object pValue,
Class pExpectedType,
Logger pLogger)
throws ELException
{
return Coercions.coerce (pValue,
pExpectedType,
pLogger);
}
//-------------------------------------
/**
*
* Converts the given String, specified as a static expression
* string, to the given expected type. The conversion is cached.
**/
Object convertStaticValueToExpectedType (String pValue,
Class pExpectedType,
Logger pLogger)
throws ELException
{
// See if the value is already of the expected type
if (pExpectedType == String.class ||
pExpectedType == Object.class) {
return pValue;
}
// Find the cached value
Map valueByString = getOrCreateExpectedTypeMap (pExpectedType);
if (!mBypassCache &&
valueByString.containsKey (pValue)) {
return valueByString.get (pValue);
}
else {
// Convert from a String
Object ret = Coercions.coerce (pValue, pExpectedType, pLogger);
valueByString.put (pValue, ret);
return ret;
}
}
//-------------------------------------
/**
*
* Creates or returns the Map that maps string literals to parsed
* values for the specified expected type.
**/
static Map getOrCreateExpectedTypeMap (Class pExpectedType)
{
synchronized (sCachedExpectedTypes) {
Map ret = (Map) sCachedExpectedTypes.get (pExpectedType);
if (ret == null) {
ret = Collections.synchronizedMap (new HashMap ());
sCachedExpectedTypes.put (pExpectedType, ret);
}
return ret;
}
}
//-------------------------------------
// Formatting ParseException
//-------------------------------------
/**
*
* Formats a ParseException into an error message suitable for
* displaying on a web page
**/
static String formatParseException (String pExpressionString,
ParseException pExc)
{
// Generate the String of expected tokens
StringBuffer expectedBuf = new StringBuffer ();
int maxSize = 0;
boolean printedOne = false;
if (pExc.expectedTokenSequences == null)
return pExc.toString();
for (int i = 0; i < pExc.expectedTokenSequences.length; i++) {
if (maxSize < pExc.expectedTokenSequences [i].length) {
maxSize = pExc.expectedTokenSequences [i].length;
}
for (int j = 0; j < pExc.expectedTokenSequences [i].length; j++) {
if (printedOne) {
expectedBuf.append (", ");
}
expectedBuf.append
(pExc.tokenImage [pExc.expectedTokenSequences [i] [j]]);
printedOne = true;
}
}
String expected = expectedBuf.toString ();
// Generate the String of encountered tokens
StringBuffer encounteredBuf = new StringBuffer ();
ELToken tok = pExc.currentToken.next;
for (int i = 0; i < maxSize; i++) {
if (i != 0) encounteredBuf.append (" ");
if (tok.kind == 0) {
encounteredBuf.append (pExc.tokenImage [0]);
break;
}
encounteredBuf.append (addEscapes (tok.image));
tok = tok.next;
}
String encountered = encounteredBuf.toString ();
// Format the error message
return MessageFormat.format
(Constants.PARSE_EXCEPTION,
new Object [] {
expected,
encountered,
});
}
//-------------------------------------
/**
*
* Used to convert raw characters to their escaped version when
* these raw version cannot be used as part of an ASCII string
* literal.
**/
static String addEscapes (String str)
{
StringBuffer retval = new StringBuffer ();
char ch;
for (int i = 0; i < str.length (); i++) {
switch (str.charAt (i)) {
case 0 :
continue;
case '\b':
retval.append ("\\b");
continue;
case '\t':
retval.append ("\\t");
continue;
case '\n':
retval.append ("\\n");
continue;
case '\f':
retval.append ("\\f");
continue;
case '\r':
retval.append ("\\r");
continue;
default:
if ((ch = str.charAt (i)) < 0x20 || ch > 0x7e) {
String s = "0000" + Integer.toString (ch, 16);
retval.append ("\\u" + s.substring (s.length () - 4, s.length ()));
}
else {
retval.append (ch);
}
continue;
}
}
return retval.toString ();
}
//-------------------------------------
// Testing methods
//-------------------------------------
/**
*
* Parses the given expression string, then converts it back to a
* String in its canonical form. This is used to test parsing.
**/
public String parseAndRender (String pExpressionString)
throws ELException
{
Object val = parseExpressionString (pExpressionString);
if (val instanceof String) {
return (String) val;
}
else if (val instanceof Expression) {
return "${" + ((Expression) val).getExpressionString () + "}";
}
else if (val instanceof ExpressionString) {
return ((ExpressionString) val).getExpressionString ();
}
else {
return "";
}
}
/**
* An object that encapsulates an expression to be evaluated by
* the JSTL evaluator.
*/
private class JSTLExpression
extends org.jbpm.jpdl.el.Expression
{
private ExpressionEvaluatorImpl evaluator;
private String expression;
private Class expectedType;
private FunctionMapper fMapper;
public JSTLExpression(ExpressionEvaluatorImpl evaluator, String expression,
Class expectedType, FunctionMapper fMapper)
{
this.evaluator = evaluator;
this.expression = expression;
this.expectedType = expectedType;
this.fMapper = fMapper;
}
public Object evaluate( VariableResolver vResolver )
throws ELException
{
return evaluator.evaluate(this.expression,
this.expectedType,
vResolver,
this.fMapper);
}
}
//-------------------------------------
}