/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.myfaces.el;
import java.io.StringReader;
import java.util.List;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.ReferenceSyntaxException;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.VariableResolver;
import org.apache.myfaces.shared_impl.util.StringUtils;
import org.apache.commons.el.ArraySuffix;
import org.apache.commons.el.BinaryOperatorExpression;
import org.apache.commons.el.Coercions;
import org.apache.commons.el.ComplexValue;
import org.apache.commons.el.ConditionalExpression;
import org.apache.commons.el.Expression;
import org.apache.commons.el.ExpressionString;
import org.apache.commons.el.FunctionInvocation;
import org.apache.commons.el.Literal;
import org.apache.commons.el.Logger;
import org.apache.commons.el.NamedValue;
import org.apache.commons.el.PropertySuffix;
import org.apache.commons.el.UnaryOperatorExpression;
import org.apache.commons.el.ValueSuffix;
import org.apache.commons.el.parser.ELParser;
import org.apache.commons.el.parser.ParseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class to implement support functionality to "morph" JSP EL into JSF
* EL
*
* @author Anton Koinov (latest modification by $Author$)
* @version $Revision$ $Date$
*/
public class ELParserHelper
{
static final Log log = LogFactory.getLog(ELParserHelper.class);
public static final Logger LOGGER = new Logger(System.out);
private ELParserHelper()
{
// util class, do not instantiate
}
/**
* Gets the parsed form of the given expression string. Returns either an
* Expression or ExpressionString.
*/
public static Object parseExpression(String expressionString)
{
expressionString = toJspElExpression(expressionString);
ELParser parser = new ELParser(new StringReader(expressionString));
try
{
Object expression = parser.ExpressionString();
if (!(expression instanceof Expression)
&& !(expression instanceof ExpressionString))
{
throw new ReferenceSyntaxException("Invalid expression: '"
+ expressionString
+ "'. Parsed Expression of unexpected type "
+ expression.getClass().getName());
}
replaceSuffixes(expression);
return expression;
}
catch (ParseException e)
{
String msg = "Invalid expression: '" + expressionString + "'";
throw new ReferenceSyntaxException(msg, e);
}
}
/**
* Convert ValueBinding syntax #{ } to JSP EL syntax ${ }
*
* @param expressionString <code>ValueBinding</code> reference expression
*
* @return JSP EL compatible expression
*/
static String toJspElExpression(String expressionString)
{
StringBuffer sb = new StringBuffer(expressionString.length());
int remainsPos = 0;
for (int posOpenBrace = expressionString.indexOf('{'); posOpenBrace >= 0;
posOpenBrace = expressionString.indexOf('{', remainsPos))
{
if (posOpenBrace > 0)
{
if( posOpenBrace-1 > remainsPos )
sb.append(expressionString.substring(remainsPos, posOpenBrace - 1));
if (expressionString.charAt(posOpenBrace - 1) == '$')
{
sb.append("${'${'}");
remainsPos = posOpenBrace+1;
continue;
}
else if (expressionString.charAt(posOpenBrace - 1) == '#')
{
// // TODO: should use \\ as escape for \ always, not just when before #{
// // allow use of '\' as escape symbol for #{ (for compatibility with Sun's extended implementation)
// if (isEscaped(expressionString, posOpenBrace - 1))
// {
// escapes: {
// for (int i = sb.length() - 1; i >= 0; i--)
// {
// if (sb.charAt(i) != '\\')
// {
// sb.setLength(
// sb.length() - (sb.length() - i) / 2);
// break escapes;
// }
// }
// sb.setLength(sb.length() / 2);
// }
// sb.append("#{");
// }
// else
// {
sb.append("${");
int posCloseBrace = indexOfMatchingClosingBrace(expressionString, posOpenBrace);
sb.append(expressionString.substring(posOpenBrace + 1, posCloseBrace + 1));
remainsPos = posCloseBrace + 1;
continue;
// }
}else{
if( posOpenBrace > remainsPos )
sb.append( expressionString.charAt(posOpenBrace - 1) );
}
}
// Standalone brace
sb.append('{');
remainsPos = posOpenBrace + 1;
}
sb.append(expressionString.substring(remainsPos));
// Create a new String to shrink mem size since we are caching
return new String(sb.toString());
}
private static int findQuote(String expressionString, int start)
{
int indexofSingleQuote = expressionString.indexOf('\'', start);
int indexofDoubleQuote = expressionString.indexOf('"', start);
return StringUtils.minIndex(indexofSingleQuote, indexofDoubleQuote);
}
/**
* Return the index of the matching closing brace, skipping over quoted text
*
* @param expressionString string to search
* @param indexofOpeningBrace the location of opening brace to match
*
* @return the index of the matching closing brace
*
* @throws ReferenceSyntaxException if matching brace cannot be found
*/
private static int indexOfMatchingClosingBrace(String expressionString,
int indexofOpeningBrace)
{
int len = expressionString.length();
int i = indexofOpeningBrace + 1;
// Loop through quoted strings
for (;;)
{
if (i >= len)
{
throw new ReferenceSyntaxException(
"Missing closing brace. Expression: '" + expressionString
+ "'");
}
int indexofClosingBrace = expressionString.indexOf('}', i);
i = StringUtils.minIndex(indexofClosingBrace, findQuote(
expressionString, i));
if (i < 0)
{
// No delimiter found
throw new ReferenceSyntaxException(
"Missing closing brace. Expression: '" + expressionString
+ "'");
}
// 1. If quoted literal, find closing quote
if (i != indexofClosingBrace)
{
i = indexOfMatchingClosingQuote(expressionString, i) + 1;
if (i == 0)
{
// Note: if no match, i==0 because -1 + 1 = 0
throw new ReferenceSyntaxException(
"Missing closing quote. Expression: '"
+ expressionString + "'");
}
}
else
{
// Closing brace
return i;
}
}
}
/**
* Returns the index of the matching closing quote, skipping over escaped
* quotes
*
* @param expressionString string to scan
* @param indexOfOpeningQuote start from this position in the string
* @return -1 if no match, the index of closing quote otherwise
*/
private static int indexOfMatchingClosingQuote(String expressionString,
int indexOfOpeningQuote)
{
char quote = expressionString.charAt(indexOfOpeningQuote);
for (int i = expressionString.indexOf(quote, indexOfOpeningQuote + 1);
i >= 0; i = expressionString.indexOf(quote, i + 1))
{
if (!isEscaped(expressionString, i))
{
return i;
}
}
// No matching quote found
return -1;
}
private static boolean isEscaped(String expressionString, int i)
{
int escapeCharCount = 0;
while ((--i >= 0) && (expressionString.charAt(i) == '\\'))
{
escapeCharCount++;
}
return (escapeCharCount % 2) != 0;
}
/**
* Replaces all <code>ValueSuffix</code>es with custom implementation
* ValueSuffexes that use JSF <code>PropertyResolver</code> insted of JSP
* EL one.
*
* @param expression <code>Expression</code> or
* <code>ExpressionString</code> instance
* @param application <code>Application</code> instance to get
* <code>PropertyResolver</code> from
*/
private static void replaceSuffixes(Object expression)
{
if (expression instanceof Expression)
{
replaceSuffixes((Expression) expression);
}
else if (expression instanceof ExpressionString)
{
replaceSuffixes((ExpressionString) expression);
}
else
{
throw new IllegalStateException(
"Expression element of unknown class: "
+ expression.getClass().getName());
}
}
private static void replaceSuffixes(ExpressionString expressionString)
{
Object[] expressions = expressionString.getElements();
for (int i = 0, len = expressions.length; i < len; i++)
{
Object expression = expressions[i];
if (expression instanceof Expression)
{
replaceSuffixes((Expression) expression);
}
else if (expression instanceof ExpressionString)
{
replaceSuffixes((ExpressionString) expression);
}
else if (!(expression instanceof String))
{
throw new IllegalStateException(
"Expression element of unknown class: "
+ expression.getClass().getName());
}
// ignore Strings
}
}
static void replaceSuffixes(Expression expression)
{
if (expression instanceof BinaryOperatorExpression)
{
replaceSuffixes(((BinaryOperatorExpression) expression)
.getExpression());
}
else if (expression instanceof ComplexValue)
{
replaceSuffixes((ComplexValue) expression);
}
else if (expression instanceof ConditionalExpression)
{
ConditionalExpression conditionalExpression =
(ConditionalExpression) expression;
replaceSuffixes(conditionalExpression.getTrueBranch());
replaceSuffixes(conditionalExpression.getFalseBranch());
}
else if (expression instanceof UnaryOperatorExpression)
{
replaceSuffixes(((UnaryOperatorExpression) expression)
.getExpression());
}
// ignore the remaining expression types
else if (!(expression instanceof FunctionInvocation
|| expression instanceof Literal || expression instanceof NamedValue))
{
throw new IllegalStateException(
"Expression element of unknown class: "
+ expression.getClass().getName());
}
}
private static void replaceSuffixes(ComplexValue complexValue)
{
Application application = FacesContext.getCurrentInstance()
.getApplication();
List suffixes = complexValue.getSuffixes();
for (int i = 0, len = suffixes.size(); i < len; i++)
{
ValueSuffix suffix = (ValueSuffix) suffixes.get(i);
if (suffix instanceof PropertySuffix)
{
if (suffix instanceof MyPropertySuffix)
{
throw new IllegalStateException(
"Suffix is MyPropertySuffix and must not be");
}
suffixes.set(i, new MyPropertySuffix((PropertySuffix) suffix,
application));
}
else if (suffix instanceof ArraySuffix)
{
if (suffix instanceof MyArraySuffix)
{
throw new IllegalStateException(
"Suffix is MyArraySuffix and must not be");
}
suffixes.set(i, new MyArraySuffix((ArraySuffix) suffix,
application));
}
else
{
throw new IllegalStateException("Unknown suffix class: "
+ suffix.getClass().getName());
}
}
}
private static Integer coerceToIntegerWrapper(Object base, Object index)
throws EvaluationException, ELException
{
Integer integer = Coercions.coerceToInteger(index, LOGGER);
if (integer != null)
{
return integer;
}
throw new ReferenceSyntaxException(
"Cannot convert index to int for base " + base.getClass().getName()
+ " and index " + index);
}
/**
* Coerces <code>index</code> to Integer for array types, or returns
* <code>null</code> for non-array types.
*
* @param base Object for the base
* @param index Object for the index
* @return Integer a valid Integer index, or null if not an array type
*
* @throws ELException if exception occurs trying to coerce to Integer
* @throws EvaluationException if base is array type but cannot convert
* index to Integer
*/
public static Integer toIndex(Object base, Object index)
throws ELException, EvaluationException
{
if ((base instanceof List) || (base.getClass().isArray()))
{
return coerceToIntegerWrapper(base, index);
}
if (base instanceof UIComponent)
{
try
{
return coerceToIntegerWrapper(base, index);
}
catch (Throwable t)
{
// treat as simple property
return null;
}
}
// If not an array type
return null;
}
/**
* Override ArraySuffix.evaluate() to use our property resolver
*/
public static class MyArraySuffix extends ArraySuffix
{
private Application _application;
public MyArraySuffix(ArraySuffix arraySuffix, Application application)
{
super(arraySuffix.getIndex());
replaceSuffixes(getIndex());
_application = application;
}
/**
* Evaluates the expression in the given context, operating on the given
* value, using JSF property resolver.
*/
public Object evaluate(Object base, VariableResolver variableResolver,
FunctionMapper functions, Logger logger)
throws ELException
{
// Check for null value
if (base == null)
{
return null;
}
// Evaluate the index
Object indexVal = getIndex().evaluate(variableResolver, functions,
logger);
if (indexVal == null)
{
return null;
}
Integer index = toIndex(base, indexVal);
if (index == null)
{
return _application.getPropertyResolver().getValue(base,
indexVal);
}
else
{
return _application.getPropertyResolver().getValue(base,
index.intValue());
}
}
}
public static class MyPropertySuffix extends PropertySuffix
{
private Application _application;
public MyPropertySuffix(PropertySuffix propertySuffix,
Application application)
{
super(propertySuffix.getName());
_application = application;
}
/**
* Evaluates the expression in the given context, operating on the given
* value, using JSF property resolver.
*/
public Object evaluate(Object base, VariableResolver variableResolver,
FunctionMapper functions, Logger logger)
throws ELException
{
// Check for null value
if (base == null)
{
return null;
}
// Evaluate the index
String indexVal = getName();
if (indexVal == null)
{
return null;
}
Integer index = toIndex(base, indexVal);
if (index == null)
{
return _application.getPropertyResolver().getValue(base,
indexVal);
}
else
{
return _application.getPropertyResolver().getValue(base,
index.intValue());
}
}
}
}