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}