Coverage Report - org.apache.commons.el.ExpressionEvaluatorImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ExpressionEvaluatorImpl
23%
25/106
11%
8/71
3.938
ExpressionEvaluatorImpl$JSTLExpression
54%
6/11
N/A
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  
 }