001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1999 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: *
054: */
055:
056: package org.apache.commons.el;
057:
058: import java.io.Reader;
059: import java.io.StringReader;
060: import java.text.MessageFormat;
061: import java.util.Collections;
062: import java.util.HashMap;
063: import java.util.Map;
064: import javax.servlet.jsp.el.ExpressionEvaluator;
065: import javax.servlet.jsp.el.ELException;
066: import javax.servlet.jsp.el.VariableResolver;
067: import javax.servlet.jsp.el.FunctionMapper;
068: import org.apache.commons.el.parser.ELParser;
069: import org.apache.commons.el.parser.ParseException;
070: import org.apache.commons.el.parser.Token;
071: import org.apache.commons.el.parser.TokenMgrError;
072:
073: /**
074: *
075: * <p>This is the main class for evaluating expression Strings. An
076: * expression String is a String that may contain expressions of the
077: * form ${...}. Multiple expressions may appear in the same
078: * expression String. In such a case, the expression String's value
079: * is computed by concatenating the String values of those evaluated
080: * expressions and any intervening non-expression text, then
081: * converting the resulting String to the expected type using the
082: * PropertyEditor mechanism.
083: *
084: * <p>In the special case where the expression String is a single
085: * expression, the value of the expression String is determined by
086: * evaluating the expression, without any intervening conversion to a
087: * String.
088: *
089: * <p>The evaluator maintains a cache mapping expression Strings to
090: * their parsed results. For expression Strings containing no
091: * expression elements, it maintains a cache mapping
092: * ExpectedType/ExpressionString to parsed value, so that static
093: * expression Strings won't have to go through a conversion step every
094: * time they are used. All instances of the evaluator share the same
095: * cache. The cache may be bypassed by setting a flag on the
096: * evaluator's constructor.
097: *
098: * <p>The evaluator must be passed a VariableResolver in its
099: * constructor. The VariableResolver is used to resolve variable
100: * names encountered in expressions, and can also be used to implement
101: * "implicit objects" that are always present in the namespace.
102: * Different applications will have different policies for variable
103: * lookups and implicit objects - these differences can be
104: * encapsulated in the VariableResolver passed to the evaluator's
105: * constructor.
106: *
107: * <p>Most VariableResolvers will need to perform their resolution
108: * against some context. For example, a JSP environment needs a
109: * PageContext to resolve variables. The evaluate() method takes a
110: * generic Object context which is eventually passed to the
111: * VariableResolver - the VariableResolver is responsible for casting
112: * the context to the proper type.
113: *
114: * <p>Once an evaluator instance has been constructed, it may be used
115: * multiple times, and may be used by multiple simultaneous Threads.
116: * In other words, an evaluator instance is well-suited for use as a
117: * singleton.
118: *
119: * @author Nathan Abramson - Art Technology Group
120: * @author Shawn Bayern
121: * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: luehe $
122: **/
123:
124: public class ExpressionEvaluatorImpl extends ExpressionEvaluator {
125: //-------------------------------------
126: // Properties
127: //-------------------------------------
128:
129: //-------------------------------------
130: // Member variables
131: //-------------------------------------
132:
133: /** The mapping from expression String to its parsed form (String,
134: Expression, or ExpressionString) **/
135: static Map sCachedExpressionStrings = Collections
136: .synchronizedMap(new HashMap());
137:
138: /** The mapping from ExpectedType to Maps mapping literal String to
139: parsed value **/
140: static Map sCachedExpectedTypes = new HashMap();
141:
142: /** The static Logger **/
143: static Logger sLogger = new Logger(System.out);
144:
145: /** Flag if the cache should be bypassed **/
146: boolean mBypassCache;
147:
148: //-------------------------------------
149: /**
150: *
151: * Constructor
152: **/
153: public ExpressionEvaluatorImpl() {
154: }
155:
156: /**
157: *
158: * Constructor
159: *
160: * @param pBypassCache flag indicating if the cache should be
161: * bypassed
162: **/
163: public ExpressionEvaluatorImpl(boolean pBypassCache) {
164: mBypassCache = pBypassCache;
165: }
166:
167: //-------------------------------------
168: /**
169: *
170: * Evaluates the given expression String
171: *
172: * @param pExpressionString The expression to be evaluated.
173: * @param pExpectedType The expected type of the result of the evaluation
174: * @param pResolver A VariableResolver instance that can be used at
175: * runtime to resolve the name of implicit objects into Objects.
176: * @param functions A FunctionMapper to resolve functions found in
177: * the expression. It can be null, in which case no functions
178: * are supported for this invocation.
179: * @return the expression String evaluated to the given expected
180: * type
181: **/
182: public Object evaluate(String pExpressionString,
183: Class pExpectedType, VariableResolver pResolver,
184: FunctionMapper functions) throws ELException {
185: return evaluate(pExpressionString, pExpectedType, pResolver,
186: functions, sLogger);
187: }
188:
189: /**
190: *
191: * Prepare an expression for later evaluation. This method should perform
192: * syntactic validation of the expression; if in doing so it detects
193: * errors, it should raise an ELParseException.
194: *
195: * @param expression The expression to be evaluated.
196: * @param expectedType The expected type of the result of the evaluation
197: * @param fMapper A FunctionMapper to resolve functions found in
198: * the expression. It can be null, in which case no functions
199: * are supported for this invocation. The ExpressionEvaluator
200: * must not hold on to the FunctionMapper reference after
201: * returning from <code>parseExpression()</code>. The
202: * <code>Expression</code> object returned must invoke the same
203: * functions regardless of whether the mappings in the
204: * provided <code>FunctionMapper</code> instance change between
205: * calling <code>ExpressionEvaluator.parseExpression()</code>
206: * and <code>Expression.evaluate()</code>.
207: * @return The Expression object encapsulating the arguments.
208: *
209: * @exception ELException Thrown if parsing errors were found.
210: **/
211: public javax.servlet.jsp.el.Expression parseExpression(
212: String expression, Class expectedType,
213: FunctionMapper fMapper) throws ELException {
214: // Validate and then create an Expression object.
215: parseExpressionString(expression);
216:
217: // Create an Expression object that knows how to evaluate this.
218: return new JSTLExpression(this , expression, expectedType,
219: fMapper);
220: }
221:
222: //-------------------------------------
223: /**
224: *
225: * Evaluates the given expression string
226: **/
227: Object evaluate(String pExpressionString, Class pExpectedType,
228: VariableResolver pResolver, FunctionMapper functions,
229: Logger pLogger) throws ELException {
230: // Check for null expression strings
231: if (pExpressionString == null) {
232: throw new ELException(Constants.NULL_EXPRESSION_STRING);
233: }
234:
235: // Get the parsed version of the expression string
236: Object parsedValue = parseExpressionString(pExpressionString);
237:
238: // Evaluate differently based on the parsed type
239: if (parsedValue instanceof String) {
240: // Convert the String, and cache the conversion
241: String strValue = (String) parsedValue;
242: return convertStaticValueToExpectedType(strValue,
243: pExpectedType, pLogger);
244: }
245:
246: else if (parsedValue instanceof Expression) {
247: // Evaluate the expression and convert
248: Object value = ((Expression) parsedValue).evaluate(
249: pResolver, functions, pLogger);
250: return convertToExpectedType(value, pExpectedType, pLogger);
251: }
252:
253: else if (parsedValue instanceof ExpressionString) {
254: // Evaluate the expression/string list and convert
255: String strValue = ((ExpressionString) parsedValue)
256: .evaluate(pResolver, functions, pLogger);
257: return convertToExpectedType(strValue, pExpectedType,
258: pLogger);
259: }
260:
261: else {
262: // This should never be reached
263: return null;
264: }
265: }
266:
267: //-------------------------------------
268: /**
269: *
270: * Gets the parsed form of the given expression string. If the
271: * parsed form is cached (and caching is not bypassed), return the
272: * cached form, otherwise parse and cache the value. Returns either
273: * a String, Expression, or ExpressionString.
274: **/
275: public Object parseExpressionString(String pExpressionString)
276: throws ELException {
277: // See if it's an empty String
278: if (pExpressionString.length() == 0) {
279: return "";
280: }
281:
282: // See if it's in the cache
283: Object ret = mBypassCache ? null : sCachedExpressionStrings
284: .get(pExpressionString);
285:
286: if (ret == null) {
287: // Parse the expression
288: Reader r = new StringReader(pExpressionString);
289: ELParser parser = new ELParser(r);
290: try {
291: ret = parser.ExpressionString();
292: sCachedExpressionStrings.put(pExpressionString, ret);
293: } catch (ParseException exc) {
294: throw new ELException(formatParseException(
295: pExpressionString, exc));
296: } catch (TokenMgrError exc) {
297: // Note - this should never be reached, since the parser is
298: // constructed to tokenize any input (illegal inputs get
299: // parsed to <BADLY_ESCAPED_STRING_LITERAL> or
300: // <ILLEGAL_CHARACTER>
301: throw new ELException(exc.getMessage());
302: }
303: }
304: return ret;
305: }
306:
307: //-------------------------------------
308: /**
309: *
310: * Converts the given value to the specified expected type.
311: **/
312: Object convertToExpectedType(Object pValue, Class pExpectedType,
313: Logger pLogger) throws ELException {
314: return Coercions.coerce(pValue, pExpectedType, pLogger);
315: }
316:
317: //-------------------------------------
318: /**
319: *
320: * Converts the given String, specified as a static expression
321: * string, to the given expected type. The conversion is cached.
322: **/
323: Object convertStaticValueToExpectedType(String pValue,
324: Class pExpectedType, Logger pLogger) throws ELException {
325: // See if the value is already of the expected type
326: if (pExpectedType == String.class
327: || pExpectedType == Object.class) {
328: return pValue;
329: }
330:
331: // Find the cached value
332: Map valueByString = getOrCreateExpectedTypeMap(pExpectedType);
333: if (!mBypassCache && valueByString.containsKey(pValue)) {
334: return valueByString.get(pValue);
335: } else {
336: // Convert from a String
337: Object ret = Coercions.coerce(pValue, pExpectedType,
338: pLogger);
339: valueByString.put(pValue, ret);
340: return ret;
341: }
342: }
343:
344: //-------------------------------------
345: /**
346: *
347: * Creates or returns the Map that maps string literals to parsed
348: * values for the specified expected type.
349: **/
350: static Map getOrCreateExpectedTypeMap(Class pExpectedType) {
351: synchronized (sCachedExpectedTypes) {
352: Map ret = (Map) sCachedExpectedTypes.get(pExpectedType);
353: if (ret == null) {
354: ret = Collections.synchronizedMap(new HashMap());
355: sCachedExpectedTypes.put(pExpectedType, ret);
356: }
357: return ret;
358: }
359: }
360:
361: //-------------------------------------
362: // Formatting ParseException
363: //-------------------------------------
364: /**
365: *
366: * Formats a ParseException into an error message suitable for
367: * displaying on a web page
368: **/
369: static String formatParseException(String pExpressionString,
370: ParseException pExc) {
371: // Generate the String of expected tokens
372: StringBuffer expectedBuf = new StringBuffer();
373: int maxSize = 0;
374: boolean printedOne = false;
375:
376: if (pExc.expectedTokenSequences == null)
377: return pExc.toString();
378:
379: for (int i = 0; i < pExc.expectedTokenSequences.length; i++) {
380: if (maxSize < pExc.expectedTokenSequences[i].length) {
381: maxSize = pExc.expectedTokenSequences[i].length;
382: }
383: for (int j = 0; j < pExc.expectedTokenSequences[i].length; j++) {
384: if (printedOne) {
385: expectedBuf.append(", ");
386: }
387: expectedBuf
388: .append(pExc.tokenImage[pExc.expectedTokenSequences[i][j]]);
389: printedOne = true;
390: }
391: }
392: String expected = expectedBuf.toString();
393:
394: // Generate the String of encountered tokens
395: StringBuffer encounteredBuf = new StringBuffer();
396: Token tok = pExc.currentToken.next;
397: for (int i = 0; i < maxSize; i++) {
398: if (i != 0)
399: encounteredBuf.append(" ");
400: if (tok.kind == 0) {
401: encounteredBuf.append(pExc.tokenImage[0]);
402: break;
403: }
404: encounteredBuf.append(addEscapes(tok.image));
405: tok = tok.next;
406: }
407: String encountered = encounteredBuf.toString();
408:
409: // Format the error message
410: return MessageFormat.format(Constants.PARSE_EXCEPTION,
411: new Object[] { expected, encountered, });
412: }
413:
414: //-------------------------------------
415: /**
416: *
417: * Used to convert raw characters to their escaped version when
418: * these raw version cannot be used as part of an ASCII string
419: * literal.
420: **/
421: static String addEscapes(String str) {
422: StringBuffer retval = new StringBuffer();
423: char ch;
424: for (int i = 0; i < str.length(); i++) {
425: switch (str.charAt(i)) {
426: case 0:
427: continue;
428: case '\b':
429: retval.append("\\b");
430: continue;
431: case '\t':
432: retval.append("\\t");
433: continue;
434: case '\n':
435: retval.append("\\n");
436: continue;
437: case '\f':
438: retval.append("\\f");
439: continue;
440: case '\r':
441: retval.append("\\r");
442: continue;
443: default:
444: if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
445: String s = "0000" + Integer.toString(ch, 16);
446: retval.append("\\u"
447: + s.substring(s.length() - 4, s.length()));
448: } else {
449: retval.append(ch);
450: }
451: continue;
452: }
453: }
454: return retval.toString();
455: }
456:
457: //-------------------------------------
458: // Testing methods
459: //-------------------------------------
460: /**
461: *
462: * Parses the given expression string, then converts it back to a
463: * String in its canonical form. This is used to test parsing.
464: **/
465: public String parseAndRender(String pExpressionString)
466: throws ELException {
467: Object val = parseExpressionString(pExpressionString);
468: if (val instanceof String) {
469: return (String) val;
470: } else if (val instanceof Expression) {
471: return "${" + ((Expression) val).getExpressionString()
472: + "}";
473: } else if (val instanceof ExpressionString) {
474: return ((ExpressionString) val).getExpressionString();
475: } else {
476: return "";
477: }
478: }
479:
480: /**
481: * An object that encapsulates an expression to be evaluated by
482: * the JSTL evaluator.
483: */
484: private class JSTLExpression extends
485: javax.servlet.jsp.el.Expression {
486: private ExpressionEvaluatorImpl evaluator;
487: private String expression;
488: private Class expectedType;
489: private FunctionMapper fMapper;
490:
491: public JSTLExpression(ExpressionEvaluatorImpl evaluator,
492: String expression, Class expectedType,
493: FunctionMapper fMapper) {
494: this .evaluator = evaluator;
495: this .expression = expression;
496: this .expectedType = expectedType;
497: this .fMapper = fMapper;
498: }
499:
500: public Object evaluate(VariableResolver vResolver)
501: throws ELException {
502: return evaluator.evaluate(this .expression,
503: this .expectedType, vResolver, this .fMapper);
504: }
505: }
506:
507: //-------------------------------------
508:
509: }
|