/*
* FreeMarker: a tool that allows Java programs to generate HTML
* output using templates.
* Copyright (C) 1998-2004 Benjamin Geer
* Email: beroul@users.sourceforge.net
*
* 28 June 1999: Modified by Steve Chiu to ignore extra parentheses.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package freemarker.template.expression;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import freemarker.template.FastHash;
import freemarker.template.TemplateException;
import freemarker.template.TemplateWriteableHashModel;
import freemarker.template.compiler.ParseException;
/**
* A TemplateParser can use this class's static {@link #buildExpression} method
* to build a complete {@link Expression} or sub-expression from a <tt>List</tt>
* of {@link Expression}s.
*
* @version $Id: ExpressionBuilder.java 1123 2005-10-04 10:48:25Z run2000 $
*/
public final class ExpressionBuilder {
// Precedences for each type of operator
static final int PRECEDENCE_VARIABLE = 1;
static final int PRECEDENCE_NEGATION = 2;
static final int PRECEDENCE_MULTIPLICATION = 3;
static final int PRECEDENCE_ADDITION = 4;
static final int PRECEDENCE_COMPARISON = 5;
static final int PRECEDENCE_EQUALITY = 6;
static final int PRECEDENCE_AND = 7;
static final int PRECEDENCE_OR = 8;
// Indicates the range of precedence operators
static final int MIN_PRECEDENCE = 1;
static final int MAX_PRECEDENCE = 8;
/** An empty model used when evaluating constant expressions. */
static final TemplateWriteableHashModel emptyModel = new FastHash();
// Error messages.
private static final String MISSING_LEFT = "Missing expression to left of operator";
private static final String MISSING_RIGHT = "Missing expression to right of operator";
private static final String SYNTAX_ERROR = "Syntax error in expression";
private static final String PARSER_ERROR = "Parser error in expression";
/**
* Private constructor, indicating this class is not meant to be
* instantiated.
*/
private ExpressionBuilder() {
}
/**
* Builds an {@link Expression} or sub-{@link Expression}.
*
* @param elements
* a <code>List</code> of <code>ExpressionElements</code>.
* @return the complete <code>Expression</code> or sub-
* <code>Expression</code>.
* @throws ParseException
* the builder could not create a valid <code>Expression</code>
*/
public static Expression buildExpression(List elements) throws ParseException {
// In descending order of operator precendence, associate each kind
// of operator with its operands.
int level = MIN_PRECEDENCE;
while (elements.size() > 1 && level <= MAX_PRECEDENCE) {
associateOperators(elements, level);
level++;
}
// The result should be a one-element list containing a
// complete Expression.
if (elements.size() == 1) {
Object element = elements.get(0);
if (element instanceof Expression) {
Expression expression = (Expression) element;
if (expression.isComplete()) {
return expression;
} else {
// If the Expression isn't complete, it's
// because the parser gave us an operator we
// don't know about.
throw new ParseException(PARSER_ERROR);
}
} else {
throw new ParseException(SYNTAX_ERROR);
}
} else {
throw new ParseException(SYNTAX_ERROR);
}
}
/**
* Associates operators with their operands.
*
* @param elements
* a <code>List</code> of <code>ExpressionElements</code>.
* @param precedence
* the current precedence level.
* @throws ParseException
* the builder could not create a valid <code>Expression</code>
*/
private static void associateOperators(List elements, int precedence) throws ParseException {
ListIterator iterator = elements.listIterator();
// Loop through the elements.
while (iterator.hasNext()) {
Expression element = (Expression) iterator.next();
if (element instanceof Operator) {
// If we find one of the operators we're looking for, and
// it's incomplete, associate it with its operand(s).
int elementPrecedence = ((Operator) element).getPrecedence();
if (elementPrecedence == precedence) {
if (element instanceof Binary) {
Binary binary = (Binary) element;
if (!binary.isComplete()) {
// Skip back over the operator.
iterator.previous();
Expression left = getPreviousExpression(iterator);
// Skip forward over the operator.
iterator.next();
Expression right = getNextExpression(iterator);
setBinaryExpression(binary, left, right, iterator);
}
} else if (element instanceof Unary) {
Unary unary = (Unary) element;
if (!unary.isComplete()) {
// Associate the operator with the operand
// either to the left or to the right,
// depending on the operator's association
// type.
Expression target;
switch (unary.getAssociationType()) {
case Unary.PREFIX:
target = getNextExpression(iterator);
break;
case Unary.POSTFIX:
// Skip back over the operator.
iterator.previous();
target = getPreviousExpression(iterator);
// Skip forward over the operator.
iterator.next();
break;
default:
throw new ParseException(PARSER_ERROR);
}
setUnaryExpression(unary, target, iterator);
}
} else {
throw new ParseException(PARSER_ERROR);
}
}
}
}
}
/**
* Sets the given unary expression to the given value. The iterator lies
* just beyond the unary instruction. If the value cannot be set, an
* exception is raised. In addition, if the expression turns out to be a
* constant, the expression is replaced with a constant value.
*
* @param unary
* the unary operator to be set
* @param target
* the expression to set to the unary operator
* @param iterator
* the current position of the iterator within the given
* expression elements
* @throws ParseException
* the target could not be set to the given unary operator, or a
* constant expression could not be evaluated
*/
private static void setUnaryExpression(Unary unary, Expression target, ListIterator iterator) throws ParseException {
try {
unary.setTarget(target);
} catch (NullPointerException e) {
throw new ParseException(e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage(), e);
}
// Set the expression to the new value
try {
iterator.previous();
iterator.set(unary.resolveExpression());
} catch (TemplateException e) {
throw new ParseException(e.getMessage(), e);
} finally {
iterator.next();
}
}
/**
* Sets the given unary expression to the given left- and right-hand values.
* The iterator lies just beyond the unary instruction. If the value cannot
* be set, an exception is raised. In addition, if the expression turns out
* to be a constant, the expression is replaced with a constant value.
*
* @param binary
* the binary operator to be set
* @param left
* the left hand expression of the binary operator
* @param right
* the right hand expression of the binary operator
* @param iterator
* the current position of the iterator within the given
* expression elements
* @throws ParseException
* the target could not be set to the given unary operator, or a
* constant expression could not be evaluated
*/
private static void setBinaryExpression(Binary binary, Expression left, Expression right, ListIterator iterator) throws ParseException {
try {
binary.setLeft(left);
binary.setRight(right);
} catch (NullPointerException e) {
throw new ParseException(e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage(), e);
}
// Set the expression to the new value
try {
iterator.previous();
iterator.set(binary.resolveExpression());
} catch (TemplateException e) {
throw new ParseException(e.getMessage(), e);
} finally {
iterator.next();
}
}
/**
* Gets the previous {@link Expression} in the list, and removes it.
*
* @param iterator
* an <code>Iterator</code> for the <code>List</code> of
* <code>ExpressionElements</code>.
* @return the previous <code>Expression</code>.
* @throws ParseException
* the builder could not determine a valid
* <code>Expression</code>
*/
private static Expression getPreviousExpression(ListIterator iterator) throws ParseException {
if (!iterator.hasPrevious()) {
throw new ParseException(MISSING_LEFT);
}
Object element = iterator.previous();
iterator.remove();
if (element instanceof Expression) {
return (Expression) element;
} else {
throw new ParseException(MISSING_LEFT);
}
}
/**
* Gets the next {@link Expression} in the list, builds it if necessary, and
* removes it.
*
* @param iterator
* an <code>Iterator</code> for the <code>List</code> of
* <code>ExpressionElements</code>.
* @return the next <code>Expression</code>.
* @throws ParseException
* the builder could not create a valid <code>Expression</code>
*/
private static Expression getNextExpression(ListIterator iterator) throws ParseException {
if (!iterator.hasNext()) {
throw new ParseException(MISSING_RIGHT);
}
Object element = iterator.next();
iterator.remove();
if (element instanceof Expression) {
return (Expression) element;
} else {
throw new ParseException(MISSING_RIGHT);
}
}
/**
* Builds a complete {@link Variable}.
*
* @param elements
* a <code>List</code> of <code>ExpressionElements</code>.
* @return the complete <code>Variable</code>.
* @throws ParseException
* the builder could not create a valid <code>Variable</code>
*/
public static Variable buildVariable(List elements) throws ParseException {
Variable expression;
int size = elements.size();
// Associate each kind of operator with its operands.
if (size > 1) {
expression = associateVariableOperators(elements);
} else if (size == 1) {
expression = (Variable) elements.get(0);
} else {
throw new ParseException(SYNTAX_ERROR);
}
if (expression.isComplete()) {
return expression;
} else {
// If the Expression isn't complete, it's
// because the parser gave us an operator we
// don't know about.
throw new ParseException(PARSER_ERROR);
}
}
/**
* Associates {@link Variable} operators with their operands. This takes
* advantage of the fact that all {@link Variable} operators are postfix
* unary operators.
*
* @param elements
* a <code>List</code> of <code>ExpressionElements</code>.
* @return The correctly associated variable
* @throws ParseException
* something went wrong during the association
*/
private static Variable associateVariableOperators(List elements) throws ParseException {
Iterator iterator = elements.iterator();
Variable out;
Object element = iterator.next();
if (element instanceof Variable) {
out = (Variable) element;
} else {
throw new ParseException(PARSER_ERROR);
}
// Loop through the elements.
while (iterator.hasNext()) {
element = iterator.next();
// If we find one of the operators we're looking for
// associate it with its operand.
if (element instanceof Unary) {
Unary unary = (Unary) element;
try {
unary.setTarget(out);
} catch (NullPointerException e) {
throw new ParseException(e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage(), e);
}
try {
out = (Variable) unary.resolveExpression();
} catch (TemplateException e) {
throw new ParseException(e.getMessage(), e);
}
} else {
throw new ParseException(PARSER_ERROR);
}
}
return out;
}
}