- /*
- * 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 apache@apache.org.
- *
- * 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
- * <http://www.apache.org/>.
- *
- */
-
- package org.apache.commons.el;
-
- 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 javax.servlet.jsp.el.ExpressionEvaluator;
- import javax.servlet.jsp.el.ELException;
- import javax.servlet.jsp.el.VariableResolver;
- import javax.servlet.jsp.el.FunctionMapper;
- import org.apache.commons.el.parser.ELParser;
- import org.apache.commons.el.parser.ParseException;
- import org.apache.commons.el.parser.Token;
- import org.apache.commons.el.parser.TokenMgrError;
-
- /**
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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: luehe $
- **/
-
- 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 <code>parseExpression()</code>. The
- * <code>Expression</code> object returned must invoke the same
- * functions regardless of whether the mappings in the
- * provided <code>FunctionMapper</code> instance change between
- * calling <code>ExpressionEvaluator.parseExpression()</code>
- * and <code>Expression.evaluate()</code>.
- * @return The Expression object encapsulating the arguments.
- *
- * @exception ELException Thrown if parsing errors were found.
- **/
- public javax.servlet.jsp.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 (TokenMgrError exc) {
- // Note - this should never be reached, since the parser is
- // constructed to tokenize any input (illegal inputs get
- // parsed to <BADLY_ESCAPED_STRING_LITERAL> or
- // <ILLEGAL_CHARACTER>
- 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 ();
- Token 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 javax.servlet.jsp.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);
- }
- }
-
- //-------------------------------------
-
- }