Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ExpressionEvaluatorImpl |
|
| 3.9375;3.938 | ||||
ExpressionEvaluatorImpl$JSTLExpression |
|
| 3.9375;3.938 |
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 | 4 | 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 | 1 | static Map sCachedExpressionStrings = |
95 | Collections.synchronizedMap (new HashMap ()); | |
96 | ||
97 | /** The mapping from ExpectedType to Maps mapping literal String to | |
98 | parsed value **/ | |
99 | 1 | 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 | 1 | 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 | 0 | { |
124 | 0 | mBypassCache = pBypassCache; |
125 | 0 | } |
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 | 2 | final Object parsedExpression = parseExpressionString(expression); |
158 | 2 | if (parsedExpression instanceof Expression) { |
159 | 2 | return new JSTLExpression(this, (Expression)parsedExpression, expectedType, fMapper); |
160 | } else { | |
161 | // this had better be a string | |
162 | 0 | 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 | 2 | if (pExpressionString == null) { |
188 | 0 | throw new ELException |
189 | (Constants.NULL_EXPRESSION_STRING); | |
190 | } | |
191 | ||
192 | // Get the parsed version of the expression string | |
193 | 2 | Object parsedValue = parseExpressionString (pExpressionString); |
194 | 2 | 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 | 2 | 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 | 6 | if (parsedValue instanceof String) { |
220 | // Convert the String, and cache the conversion | |
221 | 0 | String strValue = (String) parsedValue; |
222 | 0 | return convertStaticValueToExpectedType (strValue, pExpectedType); |
223 | } | |
224 | ||
225 | 6 | else if (parsedValue instanceof Expression) { |
226 | // Evaluate the expression and convert | |
227 | 6 | Object value = |
228 | ((Expression) parsedValue).evaluate (pResolver, | |
229 | functions); | |
230 | 6 | return convertToExpectedType (value, pExpectedType); |
231 | } | |
232 | ||
233 | else { | |
234 | // This should never be reached | |
235 | 0 | 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 | 4 | if (pExpressionString.length () == 0) { |
252 | 0 | return ""; |
253 | } | |
254 | ||
255 | // See if it's in the cache | |
256 | 4 | Object ret = |
257 | mBypassCache ? | |
258 | null : | |
259 | sCachedExpressionStrings.get (pExpressionString); | |
260 | ||
261 | 4 | if (ret == null) { |
262 | // Parse the expression | |
263 | 2 | Reader r = new StringReader (pExpressionString); |
264 | 2 | ELParser parser = new ELParser (r); |
265 | try { | |
266 | 2 | ret = parser.ExpressionString (); |
267 | 2 | sCachedExpressionStrings.put (pExpressionString, ret); |
268 | } | |
269 | 0 | catch (ParseException exc) |
270 | { | |
271 | 0 | throw new ELException |
272 | (formatParseException (pExpressionString, | |
273 | exc)); | |
274 | } | |
275 | 0 | 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 | 0 | throw new ELException (exc.getMessage ()); |
282 | 2 | } |
283 | } | |
284 | 4 | 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 | 6 | 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 | 0 | if (pExpectedType == String.class || |
310 | pExpectedType == Object.class) { | |
311 | 0 | return pValue; |
312 | } | |
313 | ||
314 | // Find the cached value | |
315 | 0 | Map valueByString = getOrCreateExpectedTypeMap (pExpectedType); |
316 | 0 | if (!mBypassCache && |
317 | valueByString.containsKey (pValue)) { | |
318 | 0 | return valueByString.get (pValue); |
319 | } | |
320 | else { | |
321 | // Convert from a String | |
322 | 0 | Object ret = Coercions.coerce (pValue, pExpectedType); |
323 | 0 | valueByString.put (pValue, ret); |
324 | 0 | 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 | 0 | synchronized (sCachedExpectedTypes) { |
337 | 0 | Map ret = (Map) sCachedExpectedTypes.get (pExpectedType); |
338 | 0 | if (ret == null) { |
339 | 0 | ret = Collections.synchronizedMap (new HashMap ()); |
340 | 0 | sCachedExpectedTypes.put (pExpectedType, ret); |
341 | } | |
342 | 0 | return ret; |
343 | 0 | } |
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 | 0 | StringBuffer expectedBuf = new StringBuffer (); |
359 | 0 | int maxSize = 0; |
360 | 0 | boolean printedOne = false; |
361 | ||
362 | 0 | if (pExc.expectedTokenSequences == null) |
363 | 0 | return pExc.toString(); |
364 | ||
365 | 0 | for (int i = 0; i < pExc.expectedTokenSequences.length; i++) { |
366 | 0 | if (maxSize < pExc.expectedTokenSequences [i].length) { |
367 | 0 | maxSize = pExc.expectedTokenSequences [i].length; |
368 | } | |
369 | 0 | for (int j = 0; j < pExc.expectedTokenSequences[i].length; j++) { |
370 | 0 | if (printedOne) { |
371 | 0 | expectedBuf.append (", "); |
372 | } | |
373 | 0 | expectedBuf.append |
374 | (pExc.tokenImage [pExc.expectedTokenSequences [i] [j]]); | |
375 | 0 | printedOne = true; |
376 | } | |
377 | } | |
378 | 0 | String expected = expectedBuf.toString (); |
379 | ||
380 | // Generate the String of encountered tokens | |
381 | 0 | StringBuffer encounteredBuf = new StringBuffer (); |
382 | 0 | Token tok = pExc.currentToken.next; |
383 | 0 | for (int i = 0; i < maxSize; i++) { |
384 | 0 | if (i != 0) encounteredBuf.append (" "); |
385 | ||
386 | 0 | if (tok.kind == 0) { |
387 | 0 | encounteredBuf.append (pExc.tokenImage[0]); |
388 | 0 | break; |
389 | } | |
390 | 0 | encounteredBuf.append (addEscapes (tok.image)); |
391 | 0 | tok = tok.next; |
392 | } | |
393 | 0 | String encountered = encounteredBuf.toString (); |
394 | ||
395 | // Format the error message | |
396 | 0 | 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 | 0 | StringBuffer retval = new StringBuffer (); |
414 | char ch; | |
415 | 0 | for (int i = 0, length = str.length (); i < length; i++) { |
416 | 0 | switch (str.charAt (i)) { |
417 | case 0: | |
418 | 0 | continue; |
419 | case '\b': | |
420 | 0 | retval.append ("\\b"); |
421 | 0 | continue; |
422 | case '\t': | |
423 | 0 | retval.append ("\\t"); |
424 | 0 | continue; |
425 | case '\n': | |
426 | 0 | retval.append ("\\n"); |
427 | 0 | continue; |
428 | case '\f': | |
429 | 0 | retval.append ("\\f"); |
430 | 0 | continue; |
431 | case '\r': | |
432 | 0 | retval.append ("\\r"); |
433 | 0 | continue; |
434 | default: | |
435 | 0 | if ((ch = str.charAt (i)) < 0x20 || ch > 0x7e) { |
436 | 0 | String s = "0000" + Integer.toString (ch, 16); |
437 | 0 | retval.append ("\\u" + s.substring (s.length () - 4, s.length ())); |
438 | 0 | } |
439 | else { | |
440 | 0 | retval.append (ch); |
441 | } | |
442 | continue; | |
443 | } | |
444 | } | |
445 | 0 | 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 | 0 | Object val = parseExpressionString (pExpressionString); |
460 | 0 | if (val instanceof String) { |
461 | 0 | return (String) val; |
462 | } | |
463 | 0 | else if (val instanceof Expression) { |
464 | 0 | return "${" + ((Expression) val).getExpressionString () + "}"; |
465 | } | |
466 | 0 | else if (val instanceof ExpressionString) { |
467 | 0 | return ((ExpressionString) val).getExpressionString (); |
468 | } | |
469 | else { | |
470 | 0 | 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 | 2 | throws ELException { |
491 | 2 | this.evaluator = evaluator; |
492 | 2 | this.parsedExpression = expression.bindFunctions(fMapper); |
493 | 2 | this.expectedType = expectedType; |
494 | 2 | } |
495 | public JSTLExpression( | |
496 | final ExpressionEvaluatorImpl evaluator, | |
497 | final String expressionString, | |
498 | final Class expectedType, | |
499 | final FunctionMapper fMapper) | |
500 | 0 | throws ELException { |
501 | 0 | this.evaluator = evaluator; |
502 | 0 | this.parsedExpression = expressionString; |
503 | 0 | this.expectedType = expectedType; |
504 | 0 | } |
505 | ||
506 | public Object evaluate( VariableResolver vResolver ) | |
507 | throws ELException | |
508 | { | |
509 | 4 | return evaluator.evaluateParsedValue(this.parsedExpression, |
510 | this.expectedType, | |
511 | vResolver, | |
512 | null); | |
513 | } | |
514 | } | |
515 | ||
516 | //------------------------------------- | |
517 | ||
518 | } |