View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.el;
18  
19  import java.io.Reader;
20  import java.io.StringReader;
21  import java.text.MessageFormat;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import javax.servlet.jsp.el.ELException;
27  import javax.servlet.jsp.el.ExpressionEvaluator;
28  import javax.servlet.jsp.el.FunctionMapper;
29  import javax.servlet.jsp.el.VariableResolver;
30  
31  import org.apache.commons.el.parser.ELParser;
32  import org.apache.commons.el.parser.ParseException;
33  import org.apache.commons.el.parser.Token;
34  import org.apache.commons.el.parser.TokenMgrError;
35  
36  /**
37   *
38   * <p>This is the main class for evaluating expression Strings.  An
39   * expression String is a String that may contain expressions of the
40   * form ${...}.  Multiple expressions may appear in the same
41   * expression String.  In such a case, the expression String's value
42   * is computed by concatenating the String values of those evaluated
43   * expressions and any intervening non-expression text, then
44   * converting the resulting String to the expected type using the
45   * PropertyEditor mechanism.
46   *
47   * <p>In the special case where the expression String is a single
48   * expression, the value of the expression String is determined by
49   * evaluating the expression, without any intervening conversion to a
50   * String.
51   *
52   * <p>The evaluator maintains a cache mapping expression Strings to
53   * their parsed results.  For expression Strings containing no
54   * expression elements, it maintains a cache mapping
55   * ExpectedType/ExpressionString to parsed value, so that static
56   * expression Strings won't have to go through a conversion step every
57   * time they are used.  All instances of the evaluator share the same
58   * cache.  The cache may be bypassed by setting a flag on the
59   * evaluator's constructor.
60   *
61   * <p>The evaluator must be passed a VariableResolver in its
62   * constructor.  The VariableResolver is used to resolve variable
63   * names encountered in expressions, and can also be used to implement
64   * "implicit objects" that are always present in the namespace.
65   * Different applications will have different policies for variable
66   * lookups and implicit objects - these differences can be
67   * encapsulated in the VariableResolver passed to the evaluator's
68   * constructor.
69   *
70   * <p>Most VariableResolvers will need to perform their resolution
71   * against some context.  For example, a JSP environment needs a
72   * PageContext to resolve variables.  The evaluate() method takes a
73   * generic Object context which is eventually passed to the
74   * VariableResolver - the VariableResolver is responsible for casting
75   * the context to the proper type.
76   *
77   * <p>Once an evaluator instance has been constructed, it may be used
78   * multiple times, and may be used by multiple simultaneous Threads.
79   * In other words, an evaluator instance is well-suited for use as a
80   * singleton.
81   * 
82   * @author Nathan Abramson - Art Technology Group
83   * @author Shawn Bayern
84   * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: bayard $
85   **/
86  public class ExpressionEvaluatorImpl
87    extends ExpressionEvaluator
88  {
89    //-------------------------------------
90    // Statics
91    //-------------------------------------
92    /** The mapping from expression String to its parsed form (String,
93        Expression, or ExpressionString) **/
94    static Map sCachedExpressionStrings =
95      Collections.synchronizedMap (new HashMap ());
96  
97    /** The mapping from ExpectedType to Maps mapping literal String to
98        parsed value **/
99    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 }