Java tutorial
/* * 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); } } // ------------------------------------- }