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 }