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