All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.taglibs.standard.lang.jstl.ELEvaluator Maven / Gradle / Ivy

/*
 * 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.taglibs.standard.lang.jstl;

import java.io.Reader;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.jsp.PageContext;

import org.apache.taglibs.standard.lang.jstl.parser.ELParser;
import org.apache.taglibs.standard.lang.jstl.parser.ParseException;
import org.apache.taglibs.standard.lang.jstl.parser.Token;
import org.apache.taglibs.standard.lang.jstl.parser.TokenMgrError;

/**
 * 

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 */ public class ELEvaluator { //------------------------------------- // Properties //------------------------------------- //------------------------------------- // Member variables //------------------------------------- /** * Name of configuration setting for maximum number of entries in the * cached expression string map */ private static final String EXPR_CACHE_PARAM = "org.apache.taglibs.standard.lang.jstl.exprCacheSize"; /** * Default maximum cache size */ private static final int MAX_SIZE = 100; /** * The mapping from expression String to its parsed form (String, * Expression, or ExpressionString) *

Using LRU Map with a maximum capacity to avoid out of bound map * growth. *

NOTE: use LinkedHashmap if a dependency on J2SE 1.4+ is ok */ static Map sCachedExpressionStrings = null; /** * 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); /** * The VariableResolver * */ VariableResolver mResolver; /** * Flag if the cache should be bypassed * */ boolean mBypassCache; /** * The PageContext * */ PageContext pageContext; //------------------------------------- /** * Constructor * * @param pResolver the object that should be used to resolve * variable names encountered in expressions. If null, all variable * references will resolve to null. */ public ELEvaluator(VariableResolver pResolver) { mResolver = pResolver; } //------------------------------------- /** * Enable cache bypass * * @param pBypassCache flag indicating cache should be bypassed */ public void setBypassCache(boolean pBypassCache) { mBypassCache = pBypassCache; } //------------------------------------- /** * Evaluates the given expression String * * @param pExpressionString the expression String to be evaluated * @param pContext the context passed to the VariableResolver for * resolving variable names * @param pExpectedType the type to which the evaluated expression * should be coerced * @return the expression String evaluated to the given expected * type */ public Object evaluate(String pExpressionString, Object pContext, Class pExpectedType, Map functions, String defaultPrefix) throws ELException { return evaluate(pExpressionString, pContext, pExpectedType, functions, defaultPrefix, sLogger); } //------------------------------------- /** * Evaluates the given expression string */ Object evaluate(String pExpressionString, Object pContext, Class pExpectedType, Map functions, String defaultPrefix, Logger pLogger) throws ELException { // Check for null expression strings if (pExpressionString == null) { throw new ELException (Constants.NULL_EXPRESSION_STRING); } // Set the PageContext; pageContext = (PageContext) pContext; // 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(pContext, mResolver, functions, defaultPrefix, pLogger); return convertToExpectedType(value, pExpectedType, pLogger); } else if (parsedValue instanceof ExpressionString) { // Evaluate the expression/string list and convert String strValue = ((ExpressionString) parsedValue).evaluate(pContext, mResolver, functions, defaultPrefix, 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 ""; } if (!(mBypassCache) && (sCachedExpressionStrings == null)) { createExpressionStringMap(); } // 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(); if (!mBypassCache) { 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 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; } } //------------------------------------- /** * Creates LRU map of expression strings. If context parameter * specifying cache size is present use that as the maximum size * of the LRU map otherwise use default. */ private synchronized void createExpressionStringMap() { if (sCachedExpressionStrings != null) { return; } final int maxSize; if ((pageContext != null) && (pageContext.getServletContext() != null)) { String value = pageContext.getServletContext().getInitParameter(EXPR_CACHE_PARAM); if (value != null) { maxSize = Integer.valueOf(value); } else { maxSize = MAX_SIZE; } } else { maxSize = MAX_SIZE; } // fall through if it couldn't find the parameter sCachedExpressionStrings = Collections.synchronizedMap(new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxSize; } }); } //------------------------------------- // 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 ""; } } //------------------------------------- }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy