Source code: org/apache/myfaces/el/ELParserHelper.java
1 /*
2 * Copyright 2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.apache.myfaces.el;
17
18 import java.io.StringReader;
19 import java.util.List;
20
21 import javax.faces.application.Application;
22 import javax.faces.component.UIComponent;
23 import javax.faces.context.FacesContext;
24 import javax.faces.el.EvaluationException;
25 import javax.faces.el.ReferenceSyntaxException;
26 import javax.servlet.jsp.el.ELException;
27 import javax.servlet.jsp.el.FunctionMapper;
28 import javax.servlet.jsp.el.VariableResolver;
29
30 import org.apache.myfaces.util.StringUtils;
31
32 import org.apache.commons.el.ArraySuffix;
33 import org.apache.commons.el.BinaryOperatorExpression;
34 import org.apache.commons.el.Coercions;
35 import org.apache.commons.el.ComplexValue;
36 import org.apache.commons.el.ConditionalExpression;
37 import org.apache.commons.el.Expression;
38 import org.apache.commons.el.ExpressionString;
39 import org.apache.commons.el.FunctionInvocation;
40 import org.apache.commons.el.Literal;
41 import org.apache.commons.el.Logger;
42 import org.apache.commons.el.NamedValue;
43 import org.apache.commons.el.PropertySuffix;
44 import org.apache.commons.el.UnaryOperatorExpression;
45 import org.apache.commons.el.ValueSuffix;
46 import org.apache.commons.el.parser.ELParser;
47 import org.apache.commons.el.parser.ParseException;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50
51
52 /**
53 * Utility class to implement support functionality to "morph" JSP EL into JSF
54 * EL
55 *
56 * @author Anton Koinov (latest modification by $Author: mbr $)
57 * @version $Revision: 231425 $ $Date: 2005-08-11 07:49:45 -0400 (Thu, 11 Aug 2005) $
58 */
59 public class ELParserHelper
60 {
61 static final Log log = LogFactory.getLog(ELParserHelper.class);
62 public static final Logger LOGGER = new Logger(System.out);
63
64 private ELParserHelper()
65 {
66 // util class, do not instantiate
67 }
68
69 /**
70 * Gets the parsed form of the given expression string. Returns either an
71 * Expression or ExpressionString.
72 */
73 public static Object parseExpression(String expressionString)
74 {
75 expressionString = toJspElExpression(expressionString);
76
77 ELParser parser = new ELParser(new StringReader(expressionString));
78 try
79 {
80 Object expression = parser.ExpressionString();
81 if (!(expression instanceof Expression)
82 && !(expression instanceof ExpressionString))
83 {
84 throw new ReferenceSyntaxException("Invalid expression: '"
85 + expressionString
86 + "'. Parsed Expression of unexpected type "
87 + expression.getClass().getName());
88 }
89
90 replaceSuffixes(expression);
91
92 return expression;
93 }
94 catch (ParseException e)
95 {
96 String msg = "Invalid expression: '" + expressionString + "'";
97 throw new ReferenceSyntaxException(msg, e);
98 }
99 }
100
101 /**
102 * Convert ValueBinding syntax #{ } to JSP EL syntax ${ }
103 *
104 * @param expressionString <code>ValueBinding</code> reference expression
105 *
106 * @return JSP EL compatible expression
107 */
108 static String toJspElExpression(String expressionString)
109 {
110 StringBuffer sb = new StringBuffer(expressionString.length());
111 int remainsPos = 0;
112
113 for (int posOpenBrace = expressionString.indexOf('{'); posOpenBrace >= 0;
114 posOpenBrace = expressionString.indexOf('{', remainsPos))
115 {
116 if (posOpenBrace > 0)
117 {
118 if( posOpenBrace-1 > remainsPos )
119 sb.append(expressionString.substring(remainsPos, posOpenBrace - 1));
120
121 if (expressionString.charAt(posOpenBrace - 1) == '$')
122 {
123 sb.append("${'${'}");
124 remainsPos = posOpenBrace+1;
125 continue;
126 }
127 else if (expressionString.charAt(posOpenBrace - 1) == '#')
128 {
129 // // TODO: should use \\ as escape for \ always, not just when before #{
130 // // allow use of '\' as escape symbol for #{ (for compatibility with Sun's extended implementation)
131 // if (isEscaped(expressionString, posOpenBrace - 1))
132 // {
133 // escapes: {
134 // for (int i = sb.length() - 1; i >= 0; i--)
135 // {
136 // if (sb.charAt(i) != '\\')
137 // {
138 // sb.setLength(
139 // sb.length() - (sb.length() - i) / 2);
140 // break escapes;
141 // }
142 // }
143 // sb.setLength(sb.length() / 2);
144 // }
145 // sb.append("#{");
146 // }
147 // else
148 // {
149 sb.append("${");
150 int posCloseBrace = indexOfMatchingClosingBrace(expressionString, posOpenBrace);
151 sb.append(expressionString.substring(posOpenBrace + 1, posCloseBrace + 1));
152 remainsPos = posCloseBrace + 1;
153 continue;
154 // }
155 }else{
156 if( posOpenBrace > remainsPos )
157 sb.append( expressionString.charAt(posOpenBrace - 1) );
158 }
159 }
160
161 // Standalone brace
162 sb.append('{');
163 remainsPos = posOpenBrace + 1;
164 }
165
166 sb.append(expressionString.substring(remainsPos));
167
168 // Create a new String to shrink mem size since we are caching
169 return new String(sb.toString());
170 }
171
172 private static int findQuote(String expressionString, int start)
173 {
174 int indexofSingleQuote = expressionString.indexOf('\'', start);
175 int indexofDoubleQuote = expressionString.indexOf('"', start);
176 return StringUtils.minIndex(indexofSingleQuote, indexofDoubleQuote);
177 }
178
179 /**
180 * Return the index of the matching closing brace, skipping over quoted text
181 *
182 * @param expressionString string to search
183 * @param indexofOpeningBrace the location of opening brace to match
184 *
185 * @return the index of the matching closing brace
186 *
187 * @throws ReferenceSyntaxException if matching brace cannot be found
188 */
189 private static int indexOfMatchingClosingBrace(String expressionString,
190 int indexofOpeningBrace)
191 {
192 int len = expressionString.length();
193 int i = indexofOpeningBrace + 1;
194
195 // Loop through quoted strings
196 for (;;)
197 {
198 if (i >= len)
199 {
200 throw new ReferenceSyntaxException(
201 "Missing closing brace. Expression: '" + expressionString
202 + "'");
203 }
204
205 int indexofClosingBrace = expressionString.indexOf('}', i);
206 i = StringUtils.minIndex(indexofClosingBrace, findQuote(
207 expressionString, i));
208
209 if (i < 0)
210 {
211 // No delimiter found
212 throw new ReferenceSyntaxException(
213 "Missing closing brace. Expression: '" + expressionString
214 + "'");
215 }
216
217 // 1. If quoted literal, find closing quote
218 if (i != indexofClosingBrace)
219 {
220 i = indexOfMatchingClosingQuote(expressionString, i) + 1;
221 if (i == 0)
222 {
223 // Note: if no match, i==0 because -1 + 1 = 0
224 throw new ReferenceSyntaxException(
225 "Missing closing quote. Expression: '"
226 + expressionString + "'");
227 }
228 }
229 else
230 {
231 // Closing brace
232 return i;
233 }
234 }
235 }
236
237 /**
238 * Returns the index of the matching closing quote, skipping over escaped
239 * quotes
240 *
241 * @param expressionString string to scan
242 * @param indexOfOpeningQuote start from this position in the string
243 * @return -1 if no match, the index of closing quote otherwise
244 */
245 private static int indexOfMatchingClosingQuote(String expressionString,
246 int indexOfOpeningQuote)
247 {
248 char quote = expressionString.charAt(indexOfOpeningQuote);
249 for (int i = expressionString.indexOf(quote, indexOfOpeningQuote + 1);
250 i >= 0; i = expressionString.indexOf(quote, i + 1))
251 {
252 if (!isEscaped(expressionString, i))
253 {
254 return i;
255 }
256 }
257
258 // No matching quote found
259 return -1;
260 }
261
262 private static boolean isEscaped(String expressionString, int i)
263 {
264 int escapeCharCount = 0;
265 while ((--i >= 0) && (expressionString.charAt(i) == '\\'))
266 {
267 escapeCharCount++;
268 }
269
270 return (escapeCharCount % 2) != 0;
271 }
272
273 /**
274 * Replaces all <code>ValueSuffix</code>es with custom implementation
275 * ValueSuffexes that use JSF <code>PropertyResolver</code> insted of JSP
276 * EL one.
277 *
278 * @param expression <code>Expression</code> or
279 * <code>ExpressionString</code> instance
280 * @param application <code>Application</code> instance to get
281 * <code>PropertyResolver</code> from
282 */
283 private static void replaceSuffixes(Object expression)
284 {
285 if (expression instanceof Expression)
286 {
287 replaceSuffixes((Expression) expression);
288 }
289 else if (expression instanceof ExpressionString)
290 {
291 replaceSuffixes((ExpressionString) expression);
292 }
293 else
294 {
295 throw new IllegalStateException(
296 "Expression element of unknown class: "
297 + expression.getClass().getName());
298 }
299 }
300
301 private static void replaceSuffixes(ExpressionString expressionString)
302 {
303 Object[] expressions = expressionString.getElements();
304 for (int i = 0, len = expressions.length; i < len; i++)
305 {
306 Object expression = expressions[i];
307 if (expression instanceof Expression)
308 {
309 replaceSuffixes((Expression) expression);
310 }
311 else if (expression instanceof ExpressionString)
312 {
313 replaceSuffixes((ExpressionString) expression);
314 }
315 else if (!(expression instanceof String))
316 {
317 throw new IllegalStateException(
318 "Expression element of unknown class: "
319 + expression.getClass().getName());
320 }
321 // ignore Strings
322 }
323 }
324
325 static void replaceSuffixes(Expression expression)
326 {
327 if (expression instanceof BinaryOperatorExpression)
328 {
329 replaceSuffixes(((BinaryOperatorExpression) expression)
330 .getExpression());
331 }
332 else if (expression instanceof ComplexValue)
333 {
334 replaceSuffixes((ComplexValue) expression);
335 }
336 else if (expression instanceof ConditionalExpression)
337 {
338 ConditionalExpression conditionalExpression =
339 (ConditionalExpression) expression;
340 replaceSuffixes(conditionalExpression.getTrueBranch());
341 replaceSuffixes(conditionalExpression.getFalseBranch());
342 }
343 else if (expression instanceof UnaryOperatorExpression)
344 {
345 replaceSuffixes(((UnaryOperatorExpression) expression)
346 .getExpression());
347 }
348
349 // ignore the remaining expression types
350 else if (!(expression instanceof FunctionInvocation
351 || expression instanceof Literal || expression instanceof NamedValue))
352 {
353 throw new IllegalStateException(
354 "Expression element of unknown class: "
355 + expression.getClass().getName());
356 }
357 }
358
359 private static void replaceSuffixes(ComplexValue complexValue)
360 {
361 Application application = FacesContext.getCurrentInstance()
362 .getApplication();
363
364 List suffixes = complexValue.getSuffixes();
365 for (int i = 0, len = suffixes.size(); i < len; i++)
366 {
367 ValueSuffix suffix = (ValueSuffix) suffixes.get(i);
368 if (suffix instanceof PropertySuffix)
369 {
370 if (suffix instanceof MyPropertySuffix)
371 {
372 throw new IllegalStateException(
373 "Suffix is MyPropertySuffix and must not be");
374 }
375
376 suffixes.set(i, new MyPropertySuffix((PropertySuffix) suffix,
377 application));
378 }
379 else if (suffix instanceof ArraySuffix)
380 {
381 if (suffix instanceof MyArraySuffix)
382 {
383 throw new IllegalStateException(
384 "Suffix is MyArraySuffix and must not be");
385 }
386
387 suffixes.set(i, new MyArraySuffix((ArraySuffix) suffix,
388 application));
389 }
390 else
391 {
392 throw new IllegalStateException("Unknown suffix class: "
393 + suffix.getClass().getName());
394 }
395 }
396 }
397
398 private static Integer coerceToIntegerWrapper(Object base, Object index)
399 throws EvaluationException, ELException
400 {
401 Integer integer = Coercions.coerceToInteger(index, LOGGER);
402 if (integer != null)
403 {
404 return integer;
405 }
406 throw new ReferenceSyntaxException(
407 "Cannot convert index to int for base " + base.getClass().getName()
408 + " and index " + index);
409 }
410
411 /**
412 * Coerces <code>index</code> to Integer for array types, or returns
413 * <code>null</code> for non-array types.
414 *
415 * @param base Object for the base
416 * @param index Object for the index
417 * @return Integer a valid Integer index, or null if not an array type
418 *
419 * @throws ELException if exception occurs trying to coerce to Integer
420 * @throws EvaluationException if base is array type but cannot convert
421 * index to Integer
422 */
423 public static Integer toIndex(Object base, Object index)
424 throws ELException, EvaluationException
425 {
426 if ((base instanceof List) || (base.getClass().isArray()))
427 {
428 return coerceToIntegerWrapper(base, index);
429 }
430 if (base instanceof UIComponent)
431 {
432 try
433 {
434 return coerceToIntegerWrapper(base, index);
435 }
436 catch (Throwable t)
437 {
438 // treat as simple property
439 return null;
440 }
441 }
442
443 // If not an array type
444 return null;
445 }
446
447 /**
448 * Override ArraySuffix.evaluate() to use our property resolver
449 */
450 public static class MyArraySuffix extends ArraySuffix
451 {
452 private Application _application;
453
454 public MyArraySuffix(ArraySuffix arraySuffix, Application application)
455 {
456 super(arraySuffix.getIndex());
457 replaceSuffixes(getIndex());
458 _application = application;
459 }
460
461 /**
462 * Evaluates the expression in the given context, operating on the given
463 * value, using JSF property resolver.
464 */
465 public Object evaluate(Object base, VariableResolver variableResolver,
466 FunctionMapper functions, Logger logger)
467 throws ELException
468 {
469 // Check for null value
470 if (base == null)
471 {
472 return null;
473 }
474
475 // Evaluate the index
476 Object indexVal = getIndex().evaluate(variableResolver, functions,
477 logger);
478 if (indexVal == null)
479 {
480 return null;
481 }
482
483 Integer index = toIndex(base, indexVal);
484 if (index == null)
485 {
486 return _application.getPropertyResolver().getValue(base,
487 indexVal);
488 }
489 else
490 {
491 return _application.getPropertyResolver().getValue(base,
492 index.intValue());
493 }
494 }
495 }
496
497 public static class MyPropertySuffix extends PropertySuffix
498 {
499 private Application _application;
500
501 public MyPropertySuffix(PropertySuffix propertySuffix,
502 Application application)
503 {
504 super(propertySuffix.getName());
505 _application = application;
506 }
507
508 /**
509 * Evaluates the expression in the given context, operating on the given
510 * value, using JSF property resolver.
511 */
512 public Object evaluate(Object base, VariableResolver variableResolver,
513 FunctionMapper functions, Logger logger)
514 throws ELException
515 {
516 // Check for null value
517 if (base == null)
518 {
519 return null;
520 }
521
522 // Evaluate the index
523 String indexVal = getName();
524 if (indexVal == null)
525 {
526 return null;
527 }
528
529 Integer index = toIndex(base, indexVal);
530 if (index == null)
531 {
532 return _application.getPropertyResolver().getValue(base,
533 indexVal);
534 }
535 else
536 {
537 return _application.getPropertyResolver().getValue(base,
538 index.intValue());
539 }
540 }
541 }
542 }