aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser
diff options
context:
space:
mode:
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser')
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java128
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java165
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java237
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java54
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java245
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java526
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java90
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java123
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java69
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java47
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java158
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java109
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java177
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java372
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java53
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java86
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java96
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java110
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java87
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java90
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java91
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java96
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java88
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java126
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java305
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java216
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java70
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java224
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java122
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java296
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java30
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java112
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java165
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java459
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java99
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java81
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java50
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java94
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java96
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java103
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java115
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java55
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java1165
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java304
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java364
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java67
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java122
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java94
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java89
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java70
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java69
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java68
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java83
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java123
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java120
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java362
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java5
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java105
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java84
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java539
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java232
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java162
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java24
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java332
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java151
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java128
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java133
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java87
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java138
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java149
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java650
75 files changed, 12338 insertions, 0 deletions
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java
new file mode 100644
index 00000000..9e7ee389
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java
@@ -0,0 +1,128 @@
+package org.apache.velocity.runtime.parser;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/* Generated By:JavaCC: Do not edit this line. CharStream.java Version 2.1 */
+
+/**
+ * This interface describes a character stream that maintains line and
+ * column number positions of the characters. It also has the capability
+ * to backup the stream to some extent. An implementation of this
+ * interface is used in the TokenManager implementation generated by
+ * JavaCCParser.
+ *
+ * All the methods except backup can be implemented in any fashion. backup
+ * needs to be implemented correctly for the correct operation of the lexer.
+ * Rest of the methods are all used to get information like line number,
+ * column number and the String that constitutes a token and are not used
+ * by the lexer. Hence their implementation won't affect the generated lexer's
+ * operation.
+ */
+
+public interface CharStream
+{
+ /**
+ * Returns the next character from the selected input. The method
+ * of selecting the input is the responsibility of the class
+ * implementing this interface. Can throw any java.io.IOException.
+ * @return read char
+ * @throws java.io.IOException
+ */
+ char readChar() throws java.io.IOException;
+
+ /**
+ * Returns the column number of the last character for current token (being
+ * matched after the last call to BeginTOken).
+ * @return ending column number
+ */
+ int getEndColumn();
+
+ /**
+ * Returns the line number of the last character for current token (being
+ * matched after the last call to BeginTOken).
+ * @return ending line number
+ */
+ int getEndLine();
+
+ /**
+ * Returns the column number of the first character for current token (being
+ * matched after the last call to BeginTOken).
+ * @return starting column number
+ */
+ int getBeginColumn();
+
+ /**
+ * Returns the line number of the first character for current token (being
+ * matched after the last call to BeginTOken).
+ * @return starting line number
+ */
+ int getBeginLine();
+
+ /**
+ * Backs up the input stream by amount steps. Lexer calls this method if it
+ * had already read some characters, but could not use them to match a
+ * (longer) token. So, they will be used again as the prefix of the next
+ * token and it is the implemetation's responsibility to do this right.
+ * @param amount
+ */
+ void backup(int amount);
+
+ /**
+ * Returns the next character that marks the beginning of the next token.
+ * All characters must remain in the buffer between two successive calls
+ * to this method to implement backup correctly.
+ * @return next token start char
+ * @throws java.io.IOException
+ */
+ char BeginToken() throws java.io.IOException;
+
+ /**
+ * Returns a string made up of characters from the marked token beginning
+ * to the current buffer position. Implementations have the choice of returning
+ * anything that they want to. For example, for efficiency, one might decide
+ * to just return null, which is a valid implementation.
+ * @return token image
+ */
+ String GetImage();
+
+ /**
+ * <p>Returns an array of characters that make up the suffix of length 'len' for
+ * the currently matched token. This is used to build up the matched string
+ * for use in actions in the case of MORE. A simple and inefficient
+ * implementation of this is as follows :</p>
+ * <pre><code>
+ * {
+ * String t = GetImage();
+ * return t.substring(t.length() - len, t.length()).toCharArray();
+ * }
+ * </code></pre>
+ * @param len suffix len
+ * @return suffix
+ */
+ char[] GetSuffix(int len);
+
+ /**
+ * The lexer calls this function to indicate that it is done with the stream
+ * and hence implementations can free any resources held by this class.
+ * Again, the body of this function can be just empty and it will not
+ * affect the lexer's operation.
+ */
+ void Done();
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java
new file mode 100644
index 00000000..c6738cd5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java
@@ -0,0 +1,165 @@
+package org.apache.velocity.runtime.parser;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+import org.apache.velocity.util.introspection.Info;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * <p>Track location in template files during rendering by populating the slf4j MDC tags <code>file</code>, <code>line</code> and <code>column</code>.</p>
+ * <p>An MDC-aware logger can then use this info to display the template location in the message</p>
+ * <p>For instance with webapp-slf4j-logger, it's enough to use <code>%file</code>, <code>%line</code> and <code>%column</code> in the logger format string.</p>
+ * <p>Since this feature can have a performance impact, it has to be enabled in <code>velocity.properties</code> using:</p>
+ * <pre><code>runtime.log.track_location = true</code></pre>
+ * <p>(typically in a development environment)</p>
+ *
+ * @author Claude Brisson
+ * @version $Id:$
+ * @since 2.2
+ */
+
+public class LogContext
+{
+ protected static Logger logger = LoggerFactory.getLogger("rendering");
+
+ public static final String MDC_FILE = "file";
+ public static final String MDC_LINE = "line";
+ public static final String MDC_COLUMN = "column";
+
+ private boolean trackLocation;
+
+ public LogContext(boolean trackLocation)
+ {
+ this.trackLocation = trackLocation;
+ }
+
+ private static ThreadLocal<Deque<StackElement>> contextStack = new ThreadLocal<Deque<StackElement>>()
+ {
+ @Override
+ public Deque<StackElement> initialValue()
+ {
+ return new ArrayDeque<>();
+ }
+ };
+
+ private static class StackElement
+ {
+ protected StackElement(SimpleNode src, Info info)
+ {
+ this.src = src;
+ this.info = info;
+ }
+
+ protected SimpleNode src;
+ protected int count = 1;
+ protected Info info;
+ }
+
+ public void pushLogContext(SimpleNode src, Info info)
+ {
+ if (!trackLocation)
+ {
+ return;
+ }
+ Deque<StackElement> stack = contextStack.get();
+ StackElement last = stack.peek();
+ if (last != null && last.src == src)
+ {
+ ++last.count;
+ }
+ else
+ {
+ stack.push(new StackElement(src, info));
+ setLogContext(info);
+ }
+ }
+
+ public void popLogContext()
+ {
+ if (!trackLocation)
+ {
+ return;
+ }
+ Deque<StackElement> stack = contextStack.get();
+ StackElement last = stack.peek();
+ if (last == null)
+ {
+ logger.error("log context is already empty");
+ return;
+ }
+ if (--last.count == 0)
+ {
+ stack.pop();
+ last = stack.peek();
+ if (last == null)
+ {
+ clearLogContext();
+ }
+ else
+ {
+ setLogContext(last.info);
+ }
+ }
+ }
+
+ private void setLogContext(Info info)
+ {
+ MDC.put(MDC_FILE, info.getTemplateName());
+ MDC.put(MDC_LINE, String.valueOf(info.getLine()));
+ MDC.put(MDC_COLUMN, String.valueOf(info.getColumn()));
+ }
+
+ private void clearLogContext()
+ {
+ MDC.remove(MDC_FILE);
+ MDC.remove(MDC_LINE);
+ MDC.remove(MDC_COLUMN);
+ }
+
+ private static final String STACKTRACE_LINE = " %s at %s[line %d, column %d]";
+
+ public String[] getStackTrace()
+ {
+ if (!trackLocation)
+ {
+ return null;
+ }
+ Deque<StackElement> stack = contextStack.get();
+ List<String> levels = new ArrayList<>();
+ for (StackElement level : stack)
+ {
+ String line = String.format(STACKTRACE_LINE,
+ level.src.literal(),
+ level.info.getTemplateName(),
+ level.info.getLine(),
+ level.info.getColumn());
+ levels.add(line);
+ }
+ return levels.size() > 0 ? levels.toArray(new String[levels.size()]) : null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java
new file mode 100644
index 00000000..4a8d5769
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java
@@ -0,0 +1,237 @@
+package org.apache.velocity.runtime.parser;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * It is intended to be caught and typically will be rethrown
+ * as a ParseErrorException.
+ *
+ * <p>You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+ private static final long serialVersionUID = -309603325673449381L;
+
+ /**
+ * <p>This constructor is used by the method "generateParseException"
+ * in the generated parser. Calling this constructor generates
+ * a new object of this type with the fields "currentToken",
+ * "expectedTokenSequences", and "tokenImage" set. The boolean
+ * flag "specialConstructor" is also set to true to indicate that
+ * this constructor was used to create this object.
+ * This constructor calls its super class with the empty string
+ * to force the "toString" method of parent class "Throwable" to
+ * print the error message in the form:</p>
+ * <pre>
+ * ParseException: &lt;result of getMessage&gt;
+ * </pre>
+ * @param currentTokenVal
+ * @param expectedTokenSequencesVal
+ * @param tokenImageVal
+ */
+ public ParseException(Token currentTokenVal,
+ int[][] expectedTokenSequencesVal,
+ String[] tokenImageVal
+ )
+ {
+ super("");
+ specialConstructor = true;
+ currentToken = currentTokenVal;
+ expectedTokenSequences = expectedTokenSequencesVal;
+ tokenImage = tokenImageVal;
+ }
+
+ /**
+ * The following constructors are for use by you for whatever
+ * purpose you can think of. Constructing the exception in this
+ * manner makes the exception behave in the normal way - i.e., as
+ * documented in the class "Throwable". The fields "errorToken",
+ * "expectedTokenSequences", and "tokenImage" do not contain
+ * relevant information. The JavaCC generated code does not use
+ * these constructors.
+ */
+ public ParseException() {
+ super();
+ specialConstructor = false;
+ }
+
+ /**
+ * The following constructors are for use by you for whatever
+ * purpose you can think of. Constructing the exception in this
+ * manner makes the exception behave in the normal way - i.e., as
+ * documented in the class "Throwable". The fields "errorToken",
+ * "expectedTokenSequences", and "tokenImage" do not contain
+ * relevant information. The JavaCC generated code does not use
+ * these constructors.
+ * @param message
+ */
+ public ParseException(String message) {
+ super(message);
+ specialConstructor = false;
+ }
+
+ /**
+ * This variable determines which constructor was used to create
+ * this object and thereby affects the semantics of the
+ * "getMessage" method (see below).
+ */
+ protected boolean specialConstructor;
+
+ /**
+ * This is the last token that has been consumed successfully. If
+ * this object has been created due to a parse error, the token
+ * followng this token will (therefore) be the first error token.
+ */
+ public Token currentToken;
+
+ /**
+ * Each entry in this array is an array of integers. Each array
+ * of integers represents a sequence of tokens (by their ordinal
+ * values) that is expected at this point of the parse.
+ */
+ public int[][] expectedTokenSequences;
+
+ /**
+ * This is a reference to the "tokenImage" array of the generated
+ * parser within which the parse error occurred. This array is
+ * defined in the generated ...Constants interface.
+ */
+ public String[] tokenImage;
+
+ /**
+ * This method has the standard behavior when this object has been
+ * created using the standard constructors. Otherwise, it uses
+ * "currentToken" and "expectedTokenSequences" to generate a parse
+ * error message and returns it. If this object has been created
+ * due to a parse error, and you do not catch it (it gets thrown
+ * from the parser), then this method is called during the printing
+ * of the final stack trace, and hence the correct error message
+ * gets displayed.
+ * @return message
+ */
+ @Override
+ public String getMessage() {
+ if (!specialConstructor) {
+ return super.getMessage();
+ }
+ String expected = "";
+ int maxSize = 0;
+ for (int[] expectedTokenSequence : expectedTokenSequences)
+ {
+ if (maxSize < expectedTokenSequence.length)
+ {
+ maxSize = expectedTokenSequence.length;
+ }
+ for (int i : expectedTokenSequence)
+ {
+ expected += tokenImage[i] + " ";
+ }
+ if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0)
+ {
+ expected += "...";
+ }
+ expected += eol + " ";
+ }
+ String retval = "Encountered \"";
+ Token tok = currentToken.next;
+ for (int i = 0; i < maxSize; i++) {
+ if (i != 0) retval += " ";
+ if (tok.kind == 0) {
+ retval += tokenImage[0];
+ break;
+ }
+ retval += add_escapes(tok.image);
+ tok = tok.next;
+ }
+ retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+ retval += "." + eol;
+ if (expectedTokenSequences.length == 1) {
+ retval += "Was expecting:" + eol + " ";
+ } else {
+ retval += "Was expecting one of:" + eol + " ";
+ }
+ retval += expected;
+ return retval;
+ }
+
+ /**
+ * The end of line string for this machine.
+ */
+ protected String eol = System.lineSeparator();
+
+ /**
+ * Used to convert raw characters to their escaped version
+ * when these raw version cannot be used as part of an ASCII
+ * string literal.
+ * @param str raw characters
+ * @return escaped string
+ */
+ protected String add_escapes(String str) {
+ StringBuilder retval = new StringBuilder();
+ char ch;
+ for (int i = 0; i < str.length(); i++) {
+ switch (str.charAt(i))
+ {
+ case 0 :
+ continue;
+ case '\b':
+ retval.append("\\b");
+ continue;
+ case '\t':
+ retval.append("\\t");
+ continue;
+ case '\n':
+ retval.append("\\n");
+ continue;
+ case '\f':
+ retval.append("\\f");
+ continue;
+ case '\r':
+ retval.append("\\r");
+ continue;
+ case '\"':
+ retval.append("\\\"");
+ continue;
+ case '\'':
+ retval.append("\\\'");
+ continue;
+ case '\\':
+ retval.append("\\\\");
+ continue;
+ default:
+ if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+ String s = "0000" + Integer.toString(ch, 16);
+ retval.append("\\u").append(s.substring(s.length() - 4, s.length()));
+ } else {
+ retval.append(ch);
+ }
+ }
+ }
+ return retval.toString();
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java
new file mode 100644
index 00000000..73bc0e9b
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java
@@ -0,0 +1,54 @@
+package org.apache.velocity.runtime.parser;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.Template;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.directive.Directive;
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+
+import java.io.Reader;
+
+public interface Parser
+{
+ RuntimeServices getRuntimeServices();
+ SimpleNode parse(Reader reader, Template template) throws ParseException;
+ void resetCurrentTemplate();
+ Template getCurrentTemplate();
+ Token getToken(int index);
+ boolean isDirective(String macro);
+ Directive getDirective(String directive);
+ void ReInit(CharStream stream);
+
+ char dollar();
+ char hash();
+ char at();
+ char asterisk();
+
+ default String lineComment()
+ {
+ return String.valueOf(hash()) + hash();
+ }
+
+ default String blockComment()
+ {
+ return String.valueOf(hash()) + asterisk();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java
new file mode 100644
index 00000000..acb20a97
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java
@@ -0,0 +1,245 @@
+package org.apache.velocity.runtime.parser;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.exception.ExtendedParseException;
+import org.apache.velocity.util.StringUtils;
+
+/**
+ * This is an extension of the ParseException, which also takes a
+ * template name.
+ *
+ * @see org.apache.velocity.runtime.parser.ParseException
+ *
+ * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class TemplateParseException
+ extends ParseException
+ implements ExtendedParseException
+{
+ private static final long serialVersionUID = -3146323135623083918L;
+
+ /**
+ * This is the name of the template which contains the parsing error, or
+ * null if not defined.
+ */
+ private final String templateName;
+
+ /**
+ * This constructor is used to add a template name
+ * to info cribbed from a ParseException generated in the parser.
+ * @param currentTokenVal
+ * @param expectedTokenSequencesVal
+ * @param tokenImageVal
+ * @param templateNameVal
+ */
+ public TemplateParseException(Token currentTokenVal, int [][] expectedTokenSequencesVal, String [] tokenImageVal,
+ String templateNameVal)
+ {
+ super(currentTokenVal, expectedTokenSequencesVal, tokenImageVal);
+ this.templateName = templateNameVal;
+ }
+
+ /**
+ * <p>This constructor is used by the method "generateParseException"
+ * in the generated parser. Calling this constructor generates
+ * a new object of this type with the fields "currentToken",
+ * "expectedTokenSequences", and "tokenImage" set. The boolean
+ * flag "specialConstructor" is also set to true to indicate that
+ * this constructor was used to create this object.
+ * This constructor calls its super class with the empty string
+ * to force the "toString" method of parent class "Throwable" to
+ * print the error message in the form:</p>
+ * <pre>
+ * ParseException: &lt;result of getMessage&gt;
+ * </pre>
+ * @param currentTokenVal
+ * @param expectedTokenSequencesVal
+ * @param tokenImageVal
+ */
+ public TemplateParseException(Token currentTokenVal, int [][] expectedTokenSequencesVal, String [] tokenImageVal)
+ {
+ super(currentTokenVal, expectedTokenSequencesVal, tokenImageVal);
+ templateName = "*unset*";
+ }
+
+ /**
+ * The following constructors are for use by you for whatever
+ * purpose you can think of. Constructing the exception in this
+ * manner makes the exception behave in the normal way - i.e., as
+ * documented in the class "Throwable". The fields "errorToken",
+ * "expectedTokenSequences", and "tokenImage" do not contain
+ * relevant information. The JavaCC generated code does not use
+ * these constructors.
+ */
+ public TemplateParseException()
+ {
+ super();
+ templateName = "*unset*";
+ }
+
+ /**
+ * Creates a new TemplateParseException object.
+ *
+ * @param message TODO: DOCUMENT ME!
+ */
+ public TemplateParseException(String message)
+ {
+ super(message);
+ templateName = "*unset*";
+ }
+
+ /**
+ * returns the Template name where this exception occurred.
+ * @return The Template name where this exception occurred.
+ */
+ @Override
+ public String getTemplateName()
+ {
+ return templateName;
+ }
+
+ /**
+ * returns the line number where this exception occurred.
+ * @return The line number where this exception occurred.
+ */
+ @Override
+ public int getLineNumber()
+ {
+ if ((currentToken != null) && (currentToken.next != null))
+ {
+ return currentToken.next.beginLine;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * returns the column number where this exception occurred.
+ * @return The column number where this exception occurred.
+ */
+ @Override
+ public int getColumnNumber()
+ {
+ if ((currentToken != null) && (currentToken.next != null))
+ {
+ return currentToken.next.beginColumn;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * This method has the standard behavior when this object has been
+ * created using the standard constructors. Otherwise, it uses
+ * "currentToken" and "expectedTokenSequences" to generate a parse
+ * error message and returns it. If this object has been created
+ * due to a parse error, and you do not catch it (it gets thrown
+ * from the parser), then this method is called during the printing
+ * of the final stack trace, and hence the correct error message
+ * gets displayed.
+ * @return The error message.
+ */
+ @Override
+ public String getMessage()
+ {
+ if (!specialConstructor)
+ {
+ StringBuilder sb = new StringBuilder(super.getMessage());
+ appendTemplateInfo(sb);
+ return sb.toString();
+ }
+
+ int maxSize = 0;
+
+ StringBuilder expected = new StringBuilder();
+
+ for (int[] expectedTokenSequence : expectedTokenSequences)
+ {
+ if (maxSize < expectedTokenSequence.length)
+ {
+ maxSize = expectedTokenSequence.length;
+ }
+
+ for (int i : expectedTokenSequence)
+ {
+ expected.append(tokenImage[i]).append(" ");
+ }
+
+ if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0)
+ {
+ expected.append("...");
+ }
+
+ expected.append(eol).append(" ");
+ }
+
+ StringBuilder retval = new StringBuilder("Encountered \"");
+ Token tok = currentToken.next;
+
+ for (int i = 0; i < maxSize; i++)
+ {
+ if (i != 0)
+ {
+ retval.append(" ");
+ }
+
+ if (tok.kind == 0)
+ {
+ retval.append(tokenImage[0]);
+ break;
+ }
+
+ retval.append(add_escapes(tok.image));
+ tok = tok.next;
+ }
+
+ retval.append("\" at ");
+ appendTemplateInfo(retval);
+
+ if (expectedTokenSequences.length == 1)
+ {
+ retval.append("Was expecting:").append(eol).append(" ");
+ }
+ else
+ {
+ retval.append("Was expecting one of:").append(eol).append(" ");
+ }
+
+ // avoid JDK 1.3 StringBuffer.append(Object o) vs 1.4 StringBuffer.append(StringBuffer sb) gotcha.
+ retval.append(expected.toString());
+ return retval.toString();
+ }
+
+ /**
+ * @param sb
+ */
+ protected void appendTemplateInfo(final StringBuilder sb)
+ {
+ sb.append(StringUtils.formatFileString(getTemplateName(), getLineNumber(), getColumnNumber()));
+ sb.append(eol);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java
new file mode 100644
index 00000000..c5133749
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java
@@ -0,0 +1,526 @@
+package org.apache.velocity.runtime.parser;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * <p>An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).</p>
+ *
+ * <p>NOTE : This class was originally an ASCII_CharStream autogenerated
+ * by Javacc. It was then modified via changing class name with appropriate
+ * fixes for CTORS, and mods to readChar().</p>
+ *
+ * <p>This is safe because we *always* use Reader with this class, and never a
+ * InputStream. This guarantees that we have a correct stream of 16-bit
+ * chars - all encoding transformations have been done elsewhere, so we
+ * believe that there is no risk in doing this. Time will tell :)</p>
+ *
+ */
+public final class VelocityCharStream
+implements CharStream
+{
+ public static final boolean staticFlag = false;
+ int bufsize;
+ private int nextBufExpand;
+ int available;
+ int tokenBegin;
+
+ public int bufpos = -1;
+ private int bufline[];
+ private int bufcolumn[];
+
+ private int column = 0;
+ private int line = 1;
+
+ private boolean prevCharIsCR = false;
+ private boolean prevCharIsLF = false;
+
+ private java.io.Reader inputStream;
+
+ private char[] buffer;
+ private int maxNextCharInd = 0;
+ private int inBuf = 0;
+
+ /* CB - to properly handle EOF *inside* javacc lexer,
+ * we send a 'file separator' ascii char *just before* EOF
+ * (see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text)
+ */
+ private boolean beforeEOF = false;
+ private static char END_OF_FILE = '\u001C';
+
+ private void ExpandBuff(boolean wrapAround)
+ {
+ char[] newbuffer = new char[bufsize + nextBufExpand];
+ int newbufline[] = new int[bufsize + nextBufExpand];
+ int newbufcolumn[] = new int[bufsize + nextBufExpand];
+
+ try
+ {
+ if (wrapAround)
+ {
+ System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+ System.arraycopy(buffer, 0, newbuffer,
+ bufsize - tokenBegin, bufpos);
+ buffer = newbuffer;
+
+ System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+ System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+ bufline = newbufline;
+
+ System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+ System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+ bufcolumn = newbufcolumn;
+
+ maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+ }
+ else
+ {
+ System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+ buffer = newbuffer;
+
+ System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+ bufline = newbufline;
+
+ System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+ bufcolumn = newbufcolumn;
+
+ maxNextCharInd = (bufpos -= tokenBegin);
+ }
+ }
+ catch (Throwable t)
+ {
+ throw new Error(t.getMessage());
+ }
+
+
+ bufsize += nextBufExpand;
+ nextBufExpand = bufsize;
+ available = bufsize;
+ tokenBegin = 0;
+ }
+
+ private void FillBuff() throws java.io.IOException
+ {
+ if (maxNextCharInd == available)
+ {
+ if (available == bufsize)
+ {
+ if (tokenBegin > nextBufExpand)
+ {
+ bufpos = maxNextCharInd = 0;
+ available = tokenBegin;
+ }
+ else if (tokenBegin < 0)
+ {
+ bufpos = maxNextCharInd = 0;
+ }
+ else
+ {
+ ExpandBuff(false);
+ }
+ }
+ else if (available > tokenBegin)
+ {
+ available = bufsize;
+ }
+ else if ((tokenBegin - available) < nextBufExpand)
+ {
+ ExpandBuff(true);
+ }
+ else
+ {
+ available = tokenBegin;
+ }
+ }
+
+ int i;
+ try
+ {
+ if ((i = inputStream.read(buffer, maxNextCharInd,
+ available - maxNextCharInd)) == -1)
+ {
+ if (beforeEOF)
+ {
+ inputStream.close();
+ throw new java.io.IOException();
+ }
+ buffer[maxNextCharInd++] = END_OF_FILE;
+ beforeEOF = true;
+ }
+ else
+ {
+ maxNextCharInd += i;
+ }
+ }
+ catch(java.io.IOException e)
+ {
+ --bufpos;
+ backup(0);
+ if (tokenBegin == -1)
+ {
+ tokenBegin = bufpos;
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#BeginToken()
+ */
+ @Override
+ public final char BeginToken() throws java.io.IOException
+ {
+ tokenBegin = -1;
+ char c = readChar();
+ tokenBegin = bufpos;
+
+ return c;
+ }
+
+ private void UpdateLineColumn(char c)
+ {
+ column++;
+
+ if (prevCharIsLF)
+ {
+ prevCharIsLF = false;
+ line += (column = 1);
+ }
+ else if (prevCharIsCR)
+ {
+ prevCharIsCR = false;
+ if (c == '\n')
+ {
+ prevCharIsLF = true;
+ }
+ else
+ {
+ line += (column = 1);
+ }
+ }
+
+ switch (c)
+ {
+ case '\r' :
+ prevCharIsCR = true;
+ break;
+ case '\n' :
+ prevCharIsLF = true;
+ break;
+ case '\t' :
+ column--;
+ column += (8 - (column & 07));
+ break;
+ default :
+ break;
+ }
+
+ bufline[bufpos] = line;
+ bufcolumn[bufpos] = column;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#readChar()
+ */
+ @Override
+ public final char readChar() throws java.io.IOException
+ {
+ if (inBuf > 0)
+ {
+ --inBuf;
+
+ /*
+ * was : return (char)((char)0xff & buffer[(bufpos == bufsize - 1) ? (bufpos = 0) : ++bufpos]);
+ */
+ return buffer[(bufpos == bufsize - 1) ? (bufpos = 0) : ++bufpos];
+ }
+
+ if (++bufpos >= maxNextCharInd)
+ {
+ FillBuff();
+ }
+
+ /*
+ * was : char c = (char)((char)0xff & buffer[bufpos]);
+ */
+ char c = buffer[bufpos];
+
+ UpdateLineColumn(c);
+ return (c);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#getEndColumn()
+ */
+ @Override
+ public final int getEndColumn()
+ {
+ return bufcolumn[bufpos];
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#getEndLine()
+ */
+ @Override
+ public final int getEndLine()
+ {
+ return bufline[bufpos];
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#getBeginColumn()
+ */
+ @Override
+ public final int getBeginColumn()
+ {
+ return bufcolumn[tokenBegin];
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#getBeginLine()
+ */
+ @Override
+ public final int getBeginLine()
+ {
+ return bufline[tokenBegin];
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#backup(int)
+ */
+ @Override
+ public final void backup(int amount)
+ {
+
+ inBuf += amount;
+ if ((bufpos -= amount) < 0)
+ bufpos += bufsize;
+ }
+
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ * @param buffersize
+ */
+ public VelocityCharStream(java.io.Reader dstream, int startline,
+ int startcolumn, int buffersize)
+ {
+ inputStream = dstream;
+ line = startline;
+ column = startcolumn - 1;
+
+ available = bufsize = nextBufExpand = buffersize;
+ buffer = new char[buffersize];
+ bufline = new int[buffersize];
+ bufcolumn = new int[buffersize];
+ }
+
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ */
+ public VelocityCharStream(java.io.Reader dstream, int startline,
+ int startcolumn)
+ {
+ this(dstream, startline, startcolumn, 4096);
+ }
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ * @param buffersize
+ */
+ public void ReInit(java.io.Reader dstream, int startline,
+ int startcolumn, int buffersize)
+ {
+ inputStream = dstream;
+ line = startline;
+ column = startcolumn - 1;
+
+ if (buffer == null || buffersize != buffer.length)
+ {
+ available = bufsize = nextBufExpand = buffersize;
+ buffer = new char[buffersize];
+ bufline = new int[buffersize];
+ bufcolumn = new int[buffersize];
+ }
+ prevCharIsLF = prevCharIsCR = false;
+ tokenBegin = inBuf = maxNextCharInd = 0;
+ bufpos = -1;
+ beforeEOF = false;
+ }
+
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ */
+ public void ReInit(java.io.Reader dstream, int startline,
+ int startcolumn)
+ {
+ ReInit(dstream, startline, startcolumn, 4096);
+ }
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ * @param buffersize
+ */
+ public VelocityCharStream(java.io.InputStream dstream, int startline,
+ int startcolumn, int buffersize)
+ {
+ this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+ }
+
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ */
+ public VelocityCharStream(java.io.InputStream dstream, int startline,
+ int startcolumn)
+ {
+ this(dstream, startline, startcolumn, 4096);
+ }
+
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ * @param buffersize
+ */
+ public void ReInit(java.io.InputStream dstream, int startline,
+ int startcolumn, int buffersize)
+ {
+ ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+ }
+ /**
+ * @param dstream
+ * @param startline
+ * @param startcolumn
+ */
+ public void ReInit(java.io.InputStream dstream, int startline,
+ int startcolumn)
+ {
+ ReInit(dstream, startline, startcolumn, 4096);
+ }
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#GetImage()
+ */
+ @Override
+ public final String GetImage()
+ {
+ if (bufpos >= tokenBegin)
+ {
+ return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+ }
+ else
+ {
+ return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+ new String(buffer, 0, bufpos + 1);
+ }
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#GetSuffix(int)
+ */
+ @Override
+ public final char[] GetSuffix(int len)
+ {
+ char[] ret = new char[len];
+
+ if ((bufpos + 1) >= len)
+ {
+ System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+ }
+ else
+ {
+ System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+ len - bufpos - 1);
+ System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+ }
+
+ return ret;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.CharStream#Done()
+ */
+ @Override
+ public void Done()
+ {
+ buffer = null;
+ bufline = null;
+ bufcolumn = null;
+ }
+
+ /**
+ * Method to adjust line and column numbers for the start of a token.<BR>
+ * @param newLine
+ * @param newCol
+ */
+ public void adjustBeginLineColumn(int newLine, int newCol)
+ {
+ int start = tokenBegin;
+ int len;
+
+ if (bufpos >= tokenBegin)
+ {
+ len = bufpos - tokenBegin + inBuf + 1;
+ }
+ else
+ {
+ len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+ }
+
+ int i = 0, j = 0, k = 0;
+ int nextColDiff = 0, columnDiff = 0;
+
+ while (i < len &&
+ bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+ {
+ bufline[j] = newLine;
+ nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+ bufcolumn[j] = newCol + columnDiff;
+ columnDiff = nextColDiff;
+ i++;
+ }
+
+ if (i < len)
+ {
+ bufline[j] = newLine++;
+ bufcolumn[j] = newCol + columnDiff;
+
+ while (i++ < len)
+ {
+ if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+ bufline[j] = newLine++;
+ else
+ bufline[j] = newLine;
+ }
+ }
+
+ line = bufline[j];
+ column = bufcolumn[j];
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java
new file mode 100644
index 00000000..119c4865
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java
@@ -0,0 +1,90 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+
+/**
+ * Handles number addition of nodes.<br><br>
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTAddNode extends ASTMathNode
+{
+ /**
+ * @param id
+ */
+ public ASTAddNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTAddNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ protected Object handleSpecial(Object left, Object right, InternalContextAdapter context)
+ {
+ // check for strings, but don't coerce
+ String lstr = DuckType.asString(left, false);
+ String rstr = DuckType.asString(right, false);
+ if (lstr != null || rstr != null)
+ {
+ if (lstr == null)
+ {
+ lstr = left != null ? left.toString() : jjtGetChild(0).literal();
+ }
+ else if (rstr == null)
+ {
+ rstr = right != null ? right.toString() : jjtGetChild(1).literal();
+ }
+ return lstr.concat(rstr);
+ }
+ return null;
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "+";
+ }
+
+ @Override
+ public Number perform(Number left, Number right, InternalContextAdapter context)
+ {
+ return MathUtils.add(left, right);
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java
new file mode 100644
index 00000000..2338d40f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java
@@ -0,0 +1,123 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTAndNode extends ASTLogicalOperator
+{
+ /**
+ * @param id
+ */
+ public ASTAndNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTAndNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "&&";
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * Returns the value of the expression.
+ * Since the value of the expression is simply the boolean
+ * result of evaluate(), lets return that.
+ * @param context
+ * @return The value of the expression.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return evaluate(context) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /**
+ * logical and :
+ * <pre>
+ * null &amp;&amp; right = false
+ * left &amp;&amp; null = false
+ * null &amp;&amp; null = false
+ * </pre>
+ * @param context
+ * @return True if both sides are true.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ Node left = jjtGetChild(0);
+ Node right = jjtGetChild(1);
+
+ /*
+ * null == false
+ */
+ if (left == null || right == null)
+ {
+ return false;
+ }
+
+ /*
+ * short circuit the test. Don't eval the RHS if the LHS is false
+ */
+
+ if( left.evaluate( context ) )
+ {
+ if ( right.evaluate( context ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java
new file mode 100644
index 00000000..2cc2973d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java
@@ -0,0 +1,69 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTAssignment extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTAssignment(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTAssignment(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java
new file mode 100644
index 00000000..8b776eaf
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java
@@ -0,0 +1,47 @@
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+public abstract class ASTBinaryOperator extends SimpleNode
+{
+ public ASTBinaryOperator(int id)
+ {
+ super(id);
+ }
+
+ public ASTBinaryOperator(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ /**
+ * get the string representing the mathematical operator
+ * @return operator string
+ */
+ public abstract String getLiteralOperator();
+
+ @Override
+ public String literal()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(jjtGetChild(0).literal());
+ builder.append(' ');
+ builder.append(getLiteralOperator());
+ builder.append(' ');
+ return builder.toString();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java
new file mode 100644
index 00000000..c8358ed5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java
@@ -0,0 +1,158 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.io.IOException;
+import java.io.Writer;
+
+
+/**
+ *
+ */
+public class ASTBlock extends SimpleNode
+{
+ private String prefix = "";
+ private String postfix = "";
+
+ // used during parsing
+ public boolean endsWithNewline = false;
+
+ /*
+ * '#' and '$' prefix characters eaten by javacc MORE mode, prefixing the '#' ending the block
+ */
+ private String morePostfix = "";
+
+ /**
+ * @param id
+ */
+ public ASTBlock(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTBlock(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ /**
+ * set indentation prefix
+ * @param prefix
+ */
+ public void setPrefix(String prefix)
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * get indentation prefix
+ * @return indentation prefix
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ public void setMorePostfix(String morePosffix)
+ {
+ this.morePostfix = morePosffix;
+ }
+
+ /**
+ * set indentation postfix
+ * @param postfix
+ */
+ public void setPostfix(String postfix)
+ {
+ this.postfix = postfix;
+ }
+
+ /**
+ * get indentation postfix
+ * @return indentation prefix
+ */
+ public String getPostfix()
+ {
+ return postfix;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException, MethodInvocationException,
+ ResourceNotFoundException, ParseErrorException
+ {
+ SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+
+ if (spaceGobbling == SpaceGobbling.NONE)
+ {
+ writer.write(prefix);
+ }
+
+ int i, k = jjtGetNumChildren();
+
+ for (i = 0; i < k; i++)
+ jjtGetChild(i).render(context, writer);
+
+ if (morePostfix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+ {
+ writer.write(postfix);
+ }
+
+ writer.write(morePostfix);
+
+ return true;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java
new file mode 100644
index 00000000..b2d3ba2a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java
@@ -0,0 +1,109 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Represents all comments...
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTComment extends SimpleNode
+{
+ private static final char[] ZILCH = "".toCharArray();
+
+ private char[] carr;
+
+ /**
+ * @param id
+ */
+ public ASTComment(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTComment(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * We need to make sure we catch any of the dreaded MORE tokens.
+ * @param context
+ * @param data
+ * @return The data object.
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ {
+ Token t = getFirstToken();
+
+ int loc1 = t.image.indexOf(parser.lineComment());
+ int loc2 = t.image.indexOf(parser.blockComment());
+
+ if (loc1 == -1 && loc2 == -1)
+ {
+ carr = ZILCH;
+ }
+ else
+ {
+ carr = t.image.substring(0, (loc1 == -1) ? loc2 : loc1).toCharArray();
+ }
+
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException, MethodInvocationException, ParseErrorException, ResourceNotFoundException
+ {
+ writer.write(carr);
+ return true;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java
new file mode 100644
index 00000000..721aa23d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java
@@ -0,0 +1,177 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+import org.apache.velocity.util.StringUtils;
+
+/**
+ * Numeric comparison support<br><br>
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author Nathan Bubna
+ */
+public abstract class ASTComparisonNode extends ASTBinaryOperator
+{
+ /**
+ * @param id
+ */
+ public ASTComparisonNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTComparisonNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException
+ {
+ Object left = jjtGetChild(0).value(context);
+ Object right = jjtGetChild(1).value(context);
+
+ if (left == null || right == null)
+ {
+ return compareNull(left, right);
+ }
+ Boolean result = compareNumbers(left, right);
+ if (result == null)
+ {
+ result = compareNonNumber(left, right);
+ }
+ return result;
+ }
+
+ /**
+ * Always false by default, != and == subclasses must override this.
+ * @param left
+ * @param right
+ * @return comparison result
+ */
+ public boolean compareNull(Object left, Object right)
+ {
+ // if either side is null, log and bail
+ String msg = (left == null ? "Left" : "Right")
+ + " side ("
+ + jjtGetChild( (left == null? 0 : 1) ).literal()
+ + ") of comparison operation has null value at "
+ + StringUtils.formatFileString(this);
+ if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
+ {
+ throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace());
+ }
+ log.error(msg);
+ return false;
+ }
+
+ /**
+ * compare numbers
+ * @param left
+ * @param right
+ * @return comparison result
+ */
+ public Boolean compareNumbers(Object left, Object right)
+ {
+ try
+ {
+ left = DuckType.asNumber(left);
+ }
+ catch (NumberFormatException nfe) {}
+ try
+ {
+ right = DuckType.asNumber(right);
+ }
+ catch (NumberFormatException nfe) {}
+
+ // only compare Numbers
+ if (left instanceof Number && right instanceof Number)
+ {
+ return numberTest(MathUtils.compare((Number)left, (Number)right));
+ }
+ return null;
+ }
+
+ /**
+ * get the string representing the mathematical operator
+ * @return operator string
+ */
+ @Override
+ public abstract String getLiteralOperator();
+
+ /**
+ * performs the actual comparison
+ * @param compareResult
+ * @return comparison result
+ */
+ public abstract boolean numberTest(int compareResult);
+
+ public boolean compareNonNumber(Object left, Object right)
+ {
+ // by default, log and bail
+ String msg = (right instanceof Number ? "Left" : "Right")
+ + " side of comparison operation is not a number at "
+ + StringUtils.formatFileString(this);
+ if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
+ {
+ throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace());
+ }
+ log.error(msg);
+ return false;
+ }
+
+ private String getLiteral(boolean left)
+ {
+ return jjtGetChild(left ? 0 : 1).literal();
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context) throws MethodInvocationException
+ {
+ return evaluate(context);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java
new file mode 100644
index 00000000..925edf45
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java
@@ -0,0 +1,372 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+import org.apache.velocity.runtime.directive.BlockMacro;
+import org.apache.velocity.runtime.directive.Directive;
+import org.apache.velocity.runtime.directive.RuntimeMacro;
+import org.apache.velocity.runtime.parser.ParseException;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.StandardParserConstants;
+import org.apache.velocity.runtime.parser.Token;
+import org.apache.velocity.util.introspection.Info;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This class is responsible for handling the pluggable
+ * directives in VTL.
+ *
+ * For example : #foreach()
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:kav@kav.dk">Kasper Nielsen</a>
+ * @version $Id$
+ */
+public class ASTDirective extends SimpleNode
+{
+ private Directive directive = null;
+ private String directiveName = "";
+ private boolean isDirective;
+ private boolean isInitialized;
+
+ private String prefix = "";
+ private String postfix = "";
+
+ /*
+ * '#' and '$' prefix characters eaten by javacc MORE mode
+ */
+ private String morePrefix = "";
+
+ /**
+ * @param id
+ */
+ public ASTDirective(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTDirective(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public synchronized Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ Token t;
+
+ /* method is synchronized to avoid concurrent directive initialization **/
+
+ if (!isInitialized)
+ {
+ super.init( context, data );
+
+ /*
+ * handle '$' and '#' chars prefix
+ */
+ t = getFirstToken();
+ int pos = -1;
+ while (t != null && (pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar())) == -1)
+ {
+ t = t.next;
+ }
+ if (t != null && pos > 0)
+ {
+ morePrefix = t.image.substring(0, pos);
+ }
+
+ /*
+ * only do things that are not context dependent
+ */
+
+ if (parser.isDirective( directiveName ))
+ {
+ isDirective = true;
+
+ try
+ {
+ directive = parser.getDirective( directiveName )
+ .getClass().newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ throw new VelocityException(
+ "Couldn't initialize directive of class " +
+ parser.getDirective(directiveName).getClass().getName(),
+ e, rsvc.getLogContext().getStackTrace());
+ }
+
+ t = getFirstToken();
+ if (t.kind == StandardParserConstants.WHITESPACE) t = t.next;
+ directive.setLocation(t.beginLine, t.beginColumn, getTemplate());
+ directive.init(rsvc, context, this);
+ }
+ else if( directiveName.startsWith(String.valueOf(rsvc.getParserConfiguration().getAtChar())) )
+ {
+ if( this.jjtGetNumChildren() > 0 )
+ {
+ // block macro call (normal macro call but has AST body)
+ directiveName = directiveName.substring(1);
+
+ directive = new BlockMacro();
+ directive.setLocation(getLine(), getColumn(), getTemplate());
+
+ try
+ {
+ ((BlockMacro)directive).init( rsvc, directiveName, context, this );
+ }
+ catch (TemplateInitException die)
+ {
+ throw new TemplateInitException(die.getMessage(),
+ (ParseException) die.getCause(),
+ rsvc.getLogContext().getStackTrace(),
+ die.getTemplateName(),
+ die.getColumnNumber() + getColumn(),
+ die.getLineNumber() + getLine());
+ }
+ isDirective = true;
+ }
+ else
+ {
+ // this is a fake block macro call without a body. e.g. #@foo
+ // just render as it is
+ isDirective = false;
+ }
+ }
+ else
+ {
+ /*
+ Create a new RuntimeMacro
+ */
+ directive = new RuntimeMacro();
+ directive.setLocation(getLine(), getColumn(), getTemplate());
+
+ /*
+ Initialize it
+ */
+ try
+ {
+ ((RuntimeMacro)directive).init( rsvc, directiveName, context, this );
+ }
+
+ /*
+ correct the line/column number if an exception is caught
+ */
+ catch (TemplateInitException die)
+ {
+ throw new TemplateInitException(die.getMessage(),
+ (ParseException) die.getCause(),
+ rsvc.getLogContext().getStackTrace(),
+ die.getTemplateName(),
+ die.getColumnNumber() + getColumn(),
+ die.getLineNumber() + getLine());
+ }
+ isDirective = true;
+
+ }
+
+ isInitialized = true;
+
+ saveTokenImages();
+ cleanupParserAndTokens();
+ }
+
+ if (morePrefix.length() == 0 && rsvc.getSpaceGobbling() == SpaceGobbling.STRUCTURED && isInitialized && isDirective && directive.getType() == Directive.BLOCK)
+ {
+ NodeUtils.fixIndentation(this, prefix);
+ }
+
+ return data;
+ }
+
+ /**
+ * set indentation prefix
+ * @param prefix
+ */
+ public void setPrefix(String prefix)
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * get indentation prefix
+ * @return indentation prefix
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ /**
+ * set indentation postfix
+ * @param postfix
+ */
+ public void setPostfix(String postfix)
+ {
+ this.postfix = postfix;
+ }
+
+ /**
+ * get indentation postfix
+ * @return indentation prefix
+ */
+ public String getPostfix()
+ {
+ return postfix;
+ }
+
+ /**
+ * more prefix getter
+ * @return more prefix
+ */
+ public String getMorePrefix()
+ {
+ return morePrefix;
+ }
+
+ public int getDirectiveType()
+ {
+ return directive.getType();
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException,MethodInvocationException, ResourceNotFoundException, ParseErrorException
+ {
+ SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+ /*
+ * normal processing
+ */
+
+ if (isDirective)
+ {
+ if (morePrefix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+ {
+ writer.write(prefix);
+ }
+
+ writer.write(morePrefix);
+
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, new Info(getTemplateName(), getLine(), getColumn()));
+ directive.render(context, writer, this);
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+
+ if (morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE)
+ {
+ writer.write(postfix);
+ }
+ }
+ else
+ {
+ writer.write(prefix);
+ writer.write(morePrefix);
+ writer.write(rsvc.getParserConfiguration().getHashChar());
+ writer.write(directiveName);
+ writer.write(postfix);
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets the directive name. Used by the parser. This keeps us from having to
+ * dig it out of the token stream and gives the parse the change to override.
+ * @param str
+ */
+ public void setDirectiveName( String str )
+ {
+ directiveName = str;
+ }
+
+ /**
+ * Gets the name of this directive.
+ * @return The name of this directive.
+ */
+ public String getDirectiveName()
+ {
+ return directiveName;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ASTDirective [" + super.toString() + ", directiveName="
+ + directiveName + "]";
+ }
+
+ /**
+ * Returns the string "#<i>directive_name</i>(...)". Arguments literals are not rendered. This method is only
+ * used for displaying the VTL stacktrace when a rendering error is encountered when runtime.log.track_location is true.
+ * @return
+ */
+ @Override
+ public String literal()
+ {
+ if (literal != null)
+ {
+ return literal;
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append('#').append(getDirectiveName()).append("(...)");
+
+ return literal = builder.toString();
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java
new file mode 100644
index 00000000..66f74266
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java
@@ -0,0 +1,53 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+public class ASTDirectiveAssign extends SimpleNode
+{
+
+ public ASTDirectiveAssign(int i)
+ {
+ super(i);
+ }
+
+ public ASTDirectiveAssign(Parser p, int i)
+ {
+ super(p, i);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ saveTokenImages();
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java
new file mode 100644
index 00000000..e070a78a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java
@@ -0,0 +1,86 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MathException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles number division of nodes<br><br>
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTDivNode extends ASTMathNode
+{
+ /**
+ * @param id
+ */
+ public ASTDivNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTDivNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "/";
+ }
+
+ @Override
+ public Number perform(Number left, Number right, InternalContextAdapter context)
+ {
+ /*
+ * check for divide by 0
+ */
+ if (MathUtils.isZero(right))
+ {
+ String msg = "Right side of division operation is zero. Must be non-zero. "
+ + getLocation(context);
+ if (strictMode)
+ {
+ log.error(msg);
+ throw new MathException(msg, rsvc.getLogContext().getStackTrace());
+ }
+ else
+ {
+ log.debug(msg);
+ return null;
+ }
+ }
+ return MathUtils.divide(left, right);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java
new file mode 100644
index 00000000..5a2411bb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java
@@ -0,0 +1,96 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+
+/**
+ * Handles <code>arg1 == arg2</code>
+ *
+ * This operator requires that the LHS and RHS are both of the
+ * same Class, both numbers or both coerce-able to strings.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author Nathan Bubna
+ */
+public class ASTEQNode extends ASTComparisonNode
+{
+ public ASTEQNode(int id)
+ {
+ super(id);
+ }
+
+ public ASTEQNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public boolean compareNull(Object left, Object right)
+ {
+ // at least one is null, see if other is null or acts as a null
+ return left == right || DuckType.asNull(left == null ? right : left);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "==";
+ }
+
+ @Override
+ public boolean numberTest(int compareResult)
+ {
+ return compareResult == 0;
+ }
+
+ @Override
+ public boolean compareNonNumber(Object left, Object right)
+ {
+ /*
+ * if both are not null, then assume that if one class
+ * is a subclass of the other that we should use the equals operator
+ */
+ if (left.getClass().isAssignableFrom(right.getClass()) ||
+ right.getClass().isAssignableFrom(left.getClass()))
+ {
+ return left.equals(right);
+ }
+
+ // coerce to string, remember getAsString() methods may return null
+ left = DuckType.asString(left);
+ right = DuckType.asString(right);
+ if (left == right)
+ {
+ return true;
+ }
+ else if (left == null || right == null)
+ {
+ return false;
+ }
+ else
+ {
+ return left.equals(right);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java
new file mode 100644
index 00000000..78334c33
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java
@@ -0,0 +1,110 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This class is responsible for handling the ElseIf VTL control statement.
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+*/
+public class ASTElseIfStatement extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTElseIfStatement(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTElseIfStatement(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * An ASTElseStatement is true if the expression
+ * it contains evaluates to true. Expressions know
+ * how to evaluate themselves, so we do that
+ * here and return the value back to ASTIfStatement
+ * where this node was originally asked to evaluate
+ * itself.
+ * @param context
+ * @return True if all children are true.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public boolean evaluate (InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return jjtGetChild(0).evaluate(context);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException,MethodInvocationException,
+ ResourceNotFoundException, ParseErrorException
+ {
+ return jjtGetChild(1).render( context, writer );
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java
new file mode 100644
index 00000000..378501cd
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java
@@ -0,0 +1,87 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * This class is responsible for handling the Else VTL control statement.
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTElseStatement extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTElseStatement(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTElseStatement(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * An ASTElseStatement always evaluates to
+ * true. Basically behaves like an #if(true).
+ * @param context
+ * @return Always true.
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return true;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java
new file mode 100644
index 00000000..67539ec0
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java
@@ -0,0 +1,90 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This class is responsible for handling Escapes
+ * in VTL.
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTEscape extends SimpleNode
+{
+ /** Used by the parser */
+ public String val;
+ private char[] ctext;
+
+ /**
+ * @param id
+ */
+ public ASTEscape(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTEscape(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ {
+ ctext = val.toCharArray();
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException
+ {
+ writer.write(ctext);
+ return true;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java
new file mode 100644
index 00000000..3a7ba3a7
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java
@@ -0,0 +1,91 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This class is responsible for handling EscapedDirectives
+ * in VTL.
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTEscapedDirective extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTEscapedDirective(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTEscapedDirective(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException
+ {
+ writer.write(firstImage);
+ return true;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ saveTokenImages();
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java
new file mode 100644
index 00000000..6b11698a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java
@@ -0,0 +1,96 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTExpression extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTExpression(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTExpression(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return jjtGetChild(0).evaluate(context);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return jjtGetChild(0).value(context);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ saveTokenImages();
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ @Override
+ public String literal()
+ {
+ return jjtGetChild(0).literal();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java
new file mode 100644
index 00000000..aed9d6f5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java
@@ -0,0 +1,88 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTFalse extends SimpleNode
+{
+ private static Boolean value = Boolean.FALSE;
+
+ /**
+ * @param id
+ */
+ public ASTFalse(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTFalse(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return false;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ {
+ return value;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java
new file mode 100644
index 00000000..1f13c30a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java
@@ -0,0 +1,126 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.math.BigDecimal;
+
+
+/**
+ * Handles floating point numbers. The value will be either a Double
+ * or a BigDecimal.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @since 1.5
+ */
+public class ASTFloatingPointLiteral extends SimpleNode
+{
+
+ // This may be of type Double or BigDecimal
+ private Number value = null;
+
+ /**
+ * @param id
+ */
+ public ASTFloatingPointLiteral(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTFloatingPointLiteral(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * Initialization method - doesn't do much but do the object
+ * creation. We only need to do it once.
+ * @param context
+ * @param data
+ * @return The data object.
+ * @throws TemplateInitException
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ /*
+ * init the tree correctly
+ */
+
+ super.init( context, data );
+
+ /*
+ * Determine the size of the item and make it a Double or BigDecimal as appropriate.
+ */
+ String str = getFirstToken().image;
+ try
+ {
+ value = Double.valueOf( str );
+
+ } catch ( NumberFormatException E1 )
+ {
+
+ // if there's still an Exception it will propqgate out
+ value = new BigDecimal( str );
+
+ }
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ {
+ return value;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || !MathUtils.isZero(value);
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java
new file mode 100644
index 00000000..62be6c76
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java
@@ -0,0 +1,51 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles arg1 &gt;= arg2<br><br>
+ */
+public class ASTGENode extends ASTComparisonNode
+{
+ public ASTGENode(int id)
+ {
+ super(id);
+ }
+
+ public ASTGENode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return ">=";
+ }
+
+ @Override
+ public boolean numberTest(int compareResult)
+ {
+ return compareResult >= 0;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java
new file mode 100644
index 00000000..2eddc6f7
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java
@@ -0,0 +1,51 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles arg1 &gt; arg2<br><br>
+ */
+public class ASTGTNode extends ASTComparisonNode
+{
+ public ASTGTNode(int id)
+ {
+ super(id);
+ }
+
+ public ASTGTNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return ">";
+ }
+
+ @Override
+ public boolean numberTest(int compareResult)
+ {
+ return compareResult == 1;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
new file mode 100644
index 00000000..3cb762a4
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
@@ -0,0 +1,305 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.app.event.EventHandlerUtil;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.introspection.Info;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+import org.apache.velocity.util.introspection.VelPropertyGet;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * ASTIdentifier.java
+ *
+ * Method support for identifiers : $foo
+ *
+ * mainly used by ASTReference
+ *
+ * Introspection is now moved to 'just in time' or at render / execution
+ * time. There are many reasons why this has to be done, but the
+ * primary two are thread safety, to remove any context-derived
+ * information from class member variables.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTIdentifier extends SimpleNode
+{
+ private String identifier = "";
+
+ /**
+ * This is really immutable after the init, so keep one for this node
+ */
+ protected Info uberInfo;
+
+ /**
+ * Indicates if we are running in strict reference mode.
+ */
+ protected boolean strictRef = false;
+
+ /**
+ * @param id
+ */
+ public ASTIdentifier(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTIdentifier(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * Identifier getter
+ * @return identifier
+ */
+ public String getIdentifier()
+ {
+ return identifier;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * simple init - don't do anything that is context specific.
+ * just get what we need from the AST, which is static.
+ * @param context
+ * @param data
+ * @return The data object.
+ * @throws TemplateInitException
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ super.init(context, data);
+
+ identifier = rsvc.useStringInterning() ? getFirstToken().image.intern() : getFirstToken().image;
+
+ uberInfo = new Info(getTemplateName(), getLine(), getColumn());
+
+ strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
+
+ saveTokenImages();
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object execute(Object o, InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+
+ VelPropertyGet vg = null;
+
+ try
+ {
+ /*
+ * first, see if we have this information cached.
+ */
+
+ IntrospectionCacheData icd = context.icacheGet(this);
+ Class<?> clazz = o instanceof Class<?> ? (Class<?>)o : o.getClass();
+
+ /*
+ * if we have the cache data and the class of the object we are
+ * invoked with is the same as that in the cache, then we must
+ * be all right. The last 'variable' is the method name, and
+ * that is fixed in the template :)
+ */
+
+ if ( icd != null && (icd.contextData == clazz) )
+ {
+ vg = (VelPropertyGet) icd.thingy;
+ }
+ else
+ {
+ /*
+ * otherwise, do the introspection, and cache it. Use the
+ * uberspector
+ */
+
+ vg = rsvc.getUberspect().getPropertyGet(o, identifier, uberInfo);
+
+ if (vg != null && vg.isCacheable())
+ {
+ icd = new IntrospectionCacheData();
+ icd.contextData = clazz;
+ icd.thingy = vg;
+ context.icachePut(this,icd);
+ }
+ }
+ }
+
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "ASTIdentifier.execute() : identifier = "+identifier;
+ log.error(msg, e);
+ throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
+ }
+
+ /*
+ * we have no getter... punt...
+ */
+
+ if (vg == null)
+ {
+ if (strictRef)
+ {
+ throw new MethodInvocationException("Object '" + o.getClass().getName() +
+ "' does not contain property '" + identifier + "'",
+ null, rsvc.getLogContext().getStackTrace(), identifier,
+ uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /*
+ * now try and execute. If we get a MIE, throw that
+ * as the app wants to get these. If not, log and punt.
+ */
+ try
+ {
+ return vg.invoke(o);
+ }
+ catch(InvocationTargetException ite)
+ {
+ /*
+ * if we have an event cartridge, see if it wants to veto
+ * also, let non-Exception Throwables go...
+ */
+
+ Throwable t = ite.getTargetException();
+ if (t instanceof Exception)
+ {
+ try
+ {
+ return EventHandlerUtil.methodException(rsvc, context, o.getClass(), vg.getMethodName(),
+ (Exception) t, uberInfo);
+ }
+
+ /*
+ * If the event handler throws an exception, then wrap it
+ * in a MethodInvocationException.
+ */
+ catch( Exception e )
+ {
+ throw new MethodInvocationException(
+ "Invocation of method '" + vg.getMethodName() + "'"
+ + " in " + o.getClass()
+ + " threw exception "
+ + ite.getTargetException().toString(),
+ ite.getTargetException(), rsvc.getLogContext().getStackTrace(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn());
+ }
+ }
+ else
+ {
+ /*
+ * no event cartridge to override. Just throw
+ */
+
+ throw new MethodInvocationException(
+ "Invocation of method '" + vg.getMethodName() + "'"
+ + " in " + o.getClass()
+ + " threw exception "
+ + ite.getTargetException().toString(),
+ ite.getTargetException(), rsvc.getLogContext().getStackTrace(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn());
+
+
+ }
+ }
+ catch(IllegalArgumentException iae)
+ {
+ return null;
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "ASTIdentifier() : exception invoking method "
+ + "for identifier '" + identifier + "' in "
+ + o.getClass();
+ log.error(msg, e);
+ throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+ }
+
+ /**
+ * Returns the string ".<i>identifier</i>". This method is only used for displaying the VTL stacktrace
+ * when a rendering error is encountered when runtime.log.track_location is true.
+ * @return
+ */
+ @Override
+ public String literal()
+ {
+ if (literal != null)
+ {
+ return literal;
+ }
+ return literal = '.' + getIdentifier();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java
new file mode 100644
index 00000000..b1b1bb66
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java
@@ -0,0 +1,216 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+
+import java.io.IOException;
+import java.io.Writer;
+
+
+/**
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTIfStatement extends SimpleNode
+{
+ private String prefix = "";
+ private String postfix = "";
+
+ /*
+ * '#' and '$' prefix characters eaten by javacc MORE mode
+ */
+ private String morePrefix = "";
+
+ /**
+ * @param id
+ */
+ public ASTIfStatement(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTIfStatement(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+
+ /*
+ * handle '$' and '#' chars prefix
+ */
+ Token t = getFirstToken();
+ int pos = -1;
+ while (t != null && (pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar())) == -1)
+ {
+ t = t.next;
+ }
+ if (t != null && pos > 0)
+ {
+ morePrefix = t.image.substring(0, pos);
+ }
+
+ /* handle structured space gobbling */
+ if (rsvc.getSpaceGobbling() == SpaceGobbling.STRUCTURED && postfix.length() > 0)
+ {
+ NodeUtils.fixIndentation(this, prefix);
+ }
+
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ /**
+ * set indentation prefix
+ * @param prefix
+ */
+ public void setPrefix(String prefix)
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * get indentation prefix
+ * @return prefix
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ /**
+ * set indentation postfix
+ * @param postfix
+ */
+ public void setPostfix(String postfix)
+ {
+ this.postfix = postfix;
+ }
+
+ /**
+ * get indentation postfix
+ * @return postfix
+ */
+ public String getPostfix()
+ {
+ return postfix;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException,MethodInvocationException,
+ ResourceNotFoundException, ParseErrorException
+ {
+ SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+
+ if (morePrefix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+ {
+ writer.write(prefix);
+ }
+
+ writer.write(morePrefix);
+
+ /*
+ * Check if the #if(expression) construct evaluates to true:
+ */
+ if (jjtGetChild(0).evaluate(context))
+ {
+ jjtGetChild(1).render(context, writer);
+ }
+ else
+ {
+ int totalNodes = jjtGetNumChildren();
+
+ /*
+ * Now check the remaining nodes left in the
+ * if construct. The nodes are either elseif
+ * nodes or else nodes. Each of these node
+ * types knows how to evaluate themselves. If
+ * a node evaluates to true then the node will
+ * render itself and this method will return
+ * as there is nothing left to do.
+ */
+ for (int i = 2; i < totalNodes; i++)
+ {
+ if (jjtGetChild(i).evaluate(context))
+ {
+ jjtGetChild(i).render(context, writer);
+ break;
+ }
+ }
+ }
+
+ if (morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE)
+ {
+ writer.write(postfix);
+ }
+
+ /*
+ * This is reached without rendering anything (other than potential suffix/prefix) when an ASTIfStatement
+ * consists of an if/elseif sequence where none of the nodes evaluate to true.
+ */
+
+ return true;
+ }
+
+ /**
+ * @param context
+ * @param visitor
+ */
+ public void process( InternalContextAdapter context, ParserVisitor visitor)
+ {
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java
new file mode 100644
index 00000000..37f88b2b
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java
@@ -0,0 +1,70 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTIncludeStatement extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTIncludeStatement(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTIncludeStatement(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java
new file mode 100644
index 00000000..ff8576fe
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java
@@ -0,0 +1,224 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.ClassUtils;
+import org.apache.velocity.util.StringUtils;
+import org.apache.velocity.util.introspection.VelMethod;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * This node is responsible for the bracket notation at the end of
+ * a reference, e.g., $foo[1]
+ */
+
+public class ASTIndex extends SimpleNode
+{
+ private static final String methodName = "get";
+
+ /**
+ * Indicates if we are running in strict reference mode.
+ */
+ protected boolean strictRef = false;
+
+ /**
+ * @param i
+ */
+ public ASTIndex(int i)
+ {
+ super(i);
+ }
+
+ /**
+ * @param p
+ * @param i
+ */
+ public ASTIndex(Parser p, int i)
+ {
+ super(p, i);
+ }
+
+ /**
+ * @param context
+ * @param data
+ * @return data
+ * @throws TemplateInitException
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ super.init(context, data);
+ strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
+ cleanupParserAndTokens();
+ return data;
+ }
+
+ private final static Object[] noParams = {};
+ private final static Class<?>[] noTypes = {};
+
+ /**
+ * If argument is an Integer and negative, then return (o.size() - argument).
+ * Otherwise return the original argument. We use this to calculate the true
+ * index of a negative index e.g., $foo[-1]. If no size() method is found on the
+ * 'o' object, then we throw an VelocityException.
+ * @param argument
+ * @param o
+ * @param context Used to access the method cache.
+ * @param node ASTNode used for error reporting.
+ * @return found object
+ */
+ public static Object adjMinusIndexArg(Object argument, Object o,
+ InternalContextAdapter context, SimpleNode node)
+ {
+ if (argument instanceof Integer && (Integer) argument < 0)
+ {
+ // The index value is a negative number, $foo[-1], so we want to actually
+ // Index [size - value], so try and call the size method.
+ VelMethod method = ClassUtils.getMethod("size", noParams, noTypes,
+ o, context, node, false);
+ if (method == null)
+ {
+ // The object doesn't have a size method, so there is no notion of "at the end"
+ throw new VelocityException(
+ "A 'size()' method required for negative value "
+ + (Integer) argument + " does not exist for class '"
+ + o.getClass().getName() + "' at " + StringUtils.formatFileString(node),
+ null, node.getRuntimeServices().getLogContext().getStackTrace());
+ }
+
+ Object size = null;
+ try
+ {
+ size = method.invoke(o, noParams);
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Error trying to calls the 'size()' method on '"
+ + o.getClass().getName() + "' at " + StringUtils.formatFileString(node), e,
+ node.getRuntimeServices().getLogContext().getStackTrace());
+ }
+
+ int sizeint = 0;
+ try
+ {
+ sizeint = (Integer) size;
+ }
+ catch (ClassCastException e)
+ {
+ // If size() doesn't return an Integer we want to report a pretty error
+ throw new VelocityException("Method 'size()' on class '"
+ + o.getClass().getName() + "' returned '" + size.getClass().getName()
+ + "' when Integer was expected at " + StringUtils.formatFileString(node),
+ null, node.getRuntimeServices().getLogContext().getStackTrace());
+ }
+
+ argument = sizeint + (Integer) argument;
+ }
+
+ // Nothing to do, return the original argument
+ return argument;
+ }
+
+ /**
+ * @param o
+ * @param context
+ * @return object value
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object execute(Object o, InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ Object argument = jjtGetChild(0).value(context);
+ // If negative, turn -1 into size - 1
+ argument = adjMinusIndexArg(argument, o, context, this);
+ Object [] params = {argument};
+ Class<?>[] paramClasses = {argument == null ? null : argument.getClass()};
+
+ VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses,
+ o, context, this, strictRef);
+
+ if (method == null) return null;
+
+ try
+ {
+ /*
+ * get the returned object. It may be null, and that is
+ * valid for something declared with a void return type.
+ * Since the caller is expecting something to be returned,
+ * as long as things are peachy, we can return an empty
+ * String so ASTReference() correctly figures out that
+ * all is well.
+ */
+ Object obj = method.invoke(o, params);
+
+ if (obj == null)
+ {
+ if( method.getReturnType() == Void.TYPE)
+ {
+ return "";
+ }
+ }
+
+ return obj;
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch( Exception e )
+ {
+ String msg = "Error invoking method 'get("
+ + (argument == null ? "null" : argument.getClass().getName())
+ + ")' in " + o.getClass().getName()
+ + " at " + StringUtils.formatFileString(this);
+ log.error(msg, e);
+ throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java
new file mode 100644
index 00000000..56e2c3ba
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java
@@ -0,0 +1,122 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.math.BigInteger;
+
+/**
+ * Handles integer numbers. The value will be either an Integer, a Long, or a BigInteger.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @since 1.5
+ */
+public class ASTIntegerLiteral extends SimpleNode
+{
+
+ // This may be of type Integer, Long or BigInteger
+ private Number value = null;
+
+ /**
+ * @param id
+ */
+ public ASTIntegerLiteral(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTIntegerLiteral(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ /*
+ * init the tree correctly
+ */
+
+ super.init( context, data );
+
+ /*
+ * Determine the size of the item and make it an Integer, Long, or BigInteger as appropriate.
+ */
+ String str = getFirstToken().image;
+ try
+ {
+ value = Integer.valueOf( str );
+ }
+ catch ( NumberFormatException E1 )
+ {
+ try
+ {
+ value = Long.valueOf( str );
+ }
+ catch ( NumberFormatException E2 )
+ {
+ // if there's still an Exception it will propagate out
+ value = new BigInteger( str );
+ }
+ }
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ {
+ return value;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || !MathUtils.isZero(value);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java
new file mode 100644
index 00000000..5135ff92
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java
@@ -0,0 +1,296 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+import org.apache.velocity.util.StringUtils;
+
+import java.util.AbstractList;
+import java.util.Iterator;
+import java.util.ListIterator;
+
+/**
+ * handles the range 'operator' [ n .. m ]
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ */
+public class ASTIntegerRange extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTIntegerRange(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTIntegerRange(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ public static class IntegerRange extends AbstractList<Integer>
+ {
+ public class RangeIterator implements ListIterator<Integer>
+ {
+ private int value;
+
+ public RangeIterator()
+ {
+ value = left - delta;
+ }
+
+ public RangeIterator(int startIndex)
+ {
+ value = left + (startIndex - 1) * delta;
+ }
+
+ @Override
+ public Integer next()
+ {
+ value += delta;
+ return value;
+ }
+
+ @Override
+ public boolean hasPrevious()
+ {
+ return value != left - delta;
+ }
+
+ @Override
+ public Integer previous()
+ {
+ value -= delta;
+ return value;
+ }
+
+ @Override
+ public int nextIndex()
+ {
+ return (value + delta - left) * delta;
+ }
+
+ @Override
+ public int previousIndex()
+ {
+ return (value - delta - left) * delta;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException("integer range is read only");
+ }
+
+ @Override
+ public void set(Integer integer)
+ {
+ throw new UnsupportedOperationException("integer range is read only");
+ }
+
+ @Override
+ public void add(Integer integer)
+ {
+ throw new UnsupportedOperationException("integer range is read only");
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return value != right;
+ }
+ }
+
+ private int left;
+ private int right;
+ private int delta;
+
+ public IntegerRange(int left, int right, int delta)
+ {
+ this.left = left;
+ this.right = right;
+ this.delta = delta;
+ }
+
+ @Override
+ public Iterator<Integer> iterator()
+ {
+ return new RangeIterator();
+ }
+
+ @Override
+ public Integer get(int index)
+ {
+ int ret = left + delta * index;
+ if (delta > 0 && ret > right || delta < 0 && ret < right)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ return ret;
+ }
+
+ @Override
+ public int indexOf(Object o)
+ {
+ int v = DuckType.asNumber(o).intValue();
+ v -= left;
+ v *= delta;
+ return v >= 0 && v < size() ? v : -1;
+ }
+
+ @Override
+ public int lastIndexOf(Object o)
+ {
+ return indexOf(o);
+ }
+
+ @Override
+ public ListIterator<Integer> listIterator()
+ {
+ return new RangeIterator();
+ }
+
+ @Override
+ public ListIterator<Integer> listIterator(int index)
+ {
+ return new RangeIterator(index);
+ }
+
+ @Override
+ public int size()
+ {
+ return Math.abs(right - left) + 1;
+ }
+ }
+
+ /**
+ * does the real work. Creates an Vector of Integers with the
+ * right value range
+ *
+ * @param context app context used if Left or Right of .. is a ref
+ * @return Object array of Integers
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ /*
+ * get the two range ends
+ */
+
+ Object left = jjtGetChild(0).value( context );
+ Object right = jjtGetChild(1).value( context );
+
+ /*
+ * if either is null, lets log and bail
+ */
+
+ if (left == null || right == null)
+ {
+ log.error((left == null ? "Left" : "Right")
+ + " side of range operator [n..m] has null value."
+ + " Operation not possible. "
+ + StringUtils.formatFileString(this));
+ return null;
+ }
+
+ /*
+ * if not a Number, try to convert
+ */
+
+ try
+ {
+ left = DuckType.asNumber(left);
+ }
+ catch (NumberFormatException nfe) {}
+
+ try
+ {
+ right = DuckType.asNumber(right);
+ }
+ catch (NumberFormatException nfe) {}
+
+ /*
+ * if still not a Number, nothing we can do
+ */
+
+ if ( !( left instanceof Number ) || !( right instanceof Number ))
+ {
+ log.error((!(left instanceof Number) ? "Left" : "Right")
+ + " side of range operator is not convertible to a Number. "
+ + StringUtils.formatFileString(this));
+ return null;
+ }
+
+ /*
+ * get the two integer values of the ends of the range
+ */
+
+ int l = ((Number) left).intValue() ;
+ int r = ((Number) right).intValue();
+
+ /*
+ * Determine whether the increment is positive or negative.
+ */
+
+ int delta = ( l >= r ) ? -1 : 1;
+
+ /*
+ * Return the corresponding integer range
+ */
+
+ return new IntegerRange(l, r, delta);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java
new file mode 100644
index 00000000..ff3c2e8e
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java
@@ -0,0 +1,51 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles arg1 &lt;= arg2<br><br>
+ */
+public class ASTLENode extends ASTComparisonNode
+{
+ public ASTLENode(int id)
+ {
+ super(id);
+ }
+
+ public ASTLENode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "<=";
+ }
+
+ @Override
+ public boolean numberTest(int compareResult)
+ {
+ return compareResult <= 0;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java
new file mode 100644
index 00000000..a4a43465
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java
@@ -0,0 +1,51 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles arg1 &lt; arg2<br><br>
+ */
+public class ASTLTNode extends ASTComparisonNode
+{
+ public ASTLTNode(int id)
+ {
+ super(id);
+ }
+
+ public ASTLTNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "<";
+ }
+
+ @Override
+ public boolean numberTest(int compareResult)
+ {
+ return compareResult == -1;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java
new file mode 100644
index 00000000..60180460
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java
@@ -0,0 +1,30 @@
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+public abstract class ASTLogicalOperator extends ASTBinaryOperator
+{
+ public ASTLogicalOperator(int id)
+ {
+ super(id);
+ }
+
+ public ASTLogicalOperator(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java
new file mode 100644
index 00000000..c259b2fb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java
@@ -0,0 +1,112 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * AST Node for creating a map / dictionary.
+ *
+ * This class was originally generated from Parser.jjt.
+ *
+ * @version $Id$
+ * @since 1.5
+ */
+public class ASTMap extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTMap(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTMap(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ int size = jjtGetNumChildren();
+
+ Map<Object, Object> objectMap = new LinkedHashMap<>();
+
+ for (int i = 0; i < size; i += 2)
+ {
+ SimpleNode keyNode = (SimpleNode) jjtGetChild(i);
+ SimpleNode valueNode = (SimpleNode) jjtGetChild(i+1);
+
+ Object key = (keyNode == null ? null : keyNode.value(context));
+ Object value = (valueNode == null ? null : valueNode.value(context));
+
+ objectMap.put(key, value);
+ }
+
+ return objectMap;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || children != null && children.length > 0;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java
new file mode 100755
index 00000000..e298bc0f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java
@@ -0,0 +1,165 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MathException;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+
+/**
+ * Helps handle math<br><br>
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author Nathan Bubna
+ * @version $Id: ASTMathNode.java 517553 2007-03-13 06:09:58Z wglass $
+ */
+public abstract class ASTMathNode extends ASTBinaryOperator
+{
+ protected boolean strictMode = false;
+
+ public ASTMathNode(int id)
+ {
+ super(id);
+ }
+
+ public ASTMathNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ super.init(context, data);
+ strictMode = rsvc.getBoolean(RuntimeConstants.STRICT_MATH, false);
+ cleanupParserAndTokens();
+ return data;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * gets the two args and performs the operation on them
+ *
+ * @param context
+ * @return result or null
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object value(InternalContextAdapter context) throws MethodInvocationException
+ {
+ Object left = jjtGetChild(0).value(context);
+ Object right = jjtGetChild(1).value(context);
+
+ /*
+ * should we do anything special here?
+ */
+ Object special = handleSpecial(left, right, context);
+ if (special != null)
+ {
+ return special;
+ }
+
+ // coerce to Number type, if possible
+ try
+ {
+ left = DuckType.asNumber(left);
+ }
+ catch (NumberFormatException nfe) {}
+ try
+ {
+ right = DuckType.asNumber(right);
+ }
+ catch (NumberFormatException nfe) {}
+
+ /*
+ * if not a Number, not much we can do
+ */
+ if (!(left instanceof Number) || !(right instanceof Number))
+ {
+ boolean wrongright = (left instanceof Number);
+ boolean wrongtype = wrongright ? right != null : left != null;
+ String msg = (wrongright ? "Right" : "Left")
+ + " side of math operation ("
+ + jjtGetChild(wrongright ? 1 : 0).literal() + ") "
+ + (wrongtype ? "is not a Number. " : "has a null value. ")
+ + getLocation(context);
+ if (strictMode)
+ {
+ log.error(msg);
+ throw new MathException(msg, rsvc.getLogContext().getStackTrace());
+ }
+ else
+ {
+ log.debug(msg);
+ return null;
+ }
+ }
+
+ return perform((Number)left, (Number)right, context);
+ }
+
+ /**
+ * Extension hook to allow special behavior by subclasses
+ * If this method returns a non-null value, that is returned,
+ * rather than the result of the math operation.
+ * @param left
+ * @param right
+ * @param context
+ * @return special value
+ * @see ASTAddNode#handleSpecial
+ */
+ protected Object handleSpecial(Object left, Object right, InternalContextAdapter context)
+ {
+ // do nothing, this is an extension hook
+ return null;
+ }
+
+ /**
+ * Performs the math operation represented by this node.
+ * @param left
+ * @param right
+ * @param context
+ * @return computed value
+ * @see ASTAddNode#perform(Number, Number, InternalContextAdapter)
+ */
+ public abstract Number perform(Number left, Number right, InternalContextAdapter context);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java
new file mode 100644
index 00000000..5696b6bb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java
@@ -0,0 +1,459 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.app.event.EventHandlerUtil;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.directive.StopCommand;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.ClassUtils;
+import org.apache.velocity.util.introspection.Info;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+import org.apache.velocity.util.introspection.VelMethod;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * ASTMethod.java
+ *
+ * Method support for references : $foo.method()
+ *
+ * NOTE :
+ *
+ * introspection is now done at render time.
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTMethod extends SimpleNode
+{
+ /**
+ * An empty immutable <code>Class</code> array.
+ */
+ private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+
+ private String methodName = "";
+ private int paramCount = 0;
+ private boolean logOnInvalid = true;
+
+ protected Info uberInfo;
+
+ /**
+ * Indicates if we are running in strict reference mode.
+ */
+ protected boolean strictRef = false;
+
+ /**
+ * @param id
+ */
+ public ASTMethod(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTMethod(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * simple init - init our subtree and get what we can from
+ * the AST
+ * @param context
+ * @param data
+ * @return The init result
+ * @throws TemplateInitException
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ super.init( context, data );
+
+ /*
+ * make an uberinfo - saves new's later on
+ */
+
+ uberInfo = new Info(getTemplateName(),
+ getLine(),getColumn());
+ /*
+ * this is about all we can do
+ */
+
+ methodName = getFirstToken().image;
+ paramCount = jjtGetNumChildren() - 1;
+
+ strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
+ logOnInvalid = rsvc.getBoolean(RuntimeConstants.RUNTIME_LOG_METHOD_CALL_LOG_INVALID, true);
+
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * invokes the method. Returns null if a problem, the
+ * actual return if the method returns something, or
+ * an empty string "" if the method returns void
+ * @param o
+ * @param context
+ * @return Result or null.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object execute(Object o, InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+
+ /*
+ * new strategy (strategery!) for introspection. Since we want
+ * to be thread- as well as context-safe, we *must* do it now,
+ * at execution time. There can be no in-node caching,
+ * but if we are careful, we can do it in the context.
+ */
+ Object [] params = new Object[paramCount];
+
+ /*
+ * sadly, we do need recalc the values of the args, as this can
+ * change from visit to visit
+ */
+ final Class<?>[] paramClasses =
+ paramCount > 0 ? new Class[paramCount] : EMPTY_CLASS_ARRAY;
+
+ for (int j = 0; j < paramCount; j++)
+ {
+ params[j] = jjtGetChild(j + 1).value(context);
+ if (params[j] != null)
+ {
+ paramClasses[j] = params[j].getClass();
+ }
+ }
+
+ VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses,
+ o, context, this, strictRef);
+
+ // warn if method wasn't found (if strictRef is true, then ClassUtils did throw an exception)
+ if (o != null && method == null && logOnInvalid)
+ {
+ StringBuilder plist = new StringBuilder();
+ for (int i = 0; i < params.length; i++)
+ {
+ Class<?> param = paramClasses[i];
+ plist.append(param == null ? "null" : param.getName());
+ if (i < params.length - 1)
+ plist.append(", ");
+ }
+ log.debug("Object '{}' does not contain method {}({}) (or several ambiguous methods) at {}[line {}, column {}]", o.getClass().getName(), methodName, plist, getTemplateName(), getLine(), getColumn());
+ }
+
+ /*
+ * The parent class (typically ASTReference) uses the icache entry
+ * under 'this' key to distinguish a valid null result from a non-existent method.
+ * So update this dummy cache value if necessary.
+ */
+ IntrospectionCacheData prevICD = context.icacheGet(this);
+ if (method == null)
+ {
+ if (prevICD != null)
+ {
+ context.icachePut(this, null);
+ }
+ return null;
+ }
+ else if (prevICD == null)
+ {
+ context.icachePut(this, new IntrospectionCacheData()); // no need to fill in its members
+ }
+
+ try
+ {
+ /*
+ * get the returned object. It may be null, and that is
+ * valid for something declared with a void return type.
+ * Since the caller is expecting something to be returned,
+ * as long as things are peachy, we can return an empty
+ * String so ASTReference() correctly figures out that
+ * all is well.
+ */
+
+ Object obj = method.invoke(o, params);
+
+ if (obj == null)
+ {
+ if( method.getReturnType() == Void.TYPE)
+ {
+ return "";
+ }
+ }
+
+ return obj;
+ }
+ catch( InvocationTargetException ite )
+ {
+ return handleInvocationException(o, context, ite.getTargetException());
+ }
+
+ /* Can also be thrown by method invocation */
+ catch( IllegalArgumentException t )
+ {
+ return handleInvocationException(o, context, t);
+ }
+
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch( Exception e )
+ {
+ String msg = "ASTMethod.execute() : exception invoking method '"
+ + methodName + "' in " + o.getClass();
+ log.error(msg, e);
+ throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+ }
+
+ private Object handleInvocationException(Object o, InternalContextAdapter context, Throwable t)
+ {
+ /*
+ * Errors should not be wrapped
+ */
+ if (t instanceof Error)
+ {
+ throw (Error)t;
+ }
+ /*
+ * We let StopCommands go up to the directive they are for/from
+ */
+ else if (t instanceof StopCommand)
+ {
+ throw (StopCommand)t;
+ }
+
+ /*
+ * In the event that the invocation of the method
+ * itself throws an exception, we want to catch that
+ * wrap it, and throw. We don't log here as we want to figure
+ * out which reference threw the exception, so do that
+ * above
+ */
+ else if (t instanceof Exception)
+ {
+ try
+ {
+ return EventHandlerUtil.methodException( rsvc, context, o.getClass(), methodName, (Exception) t, uberInfo );
+ }
+
+ /*
+ * If the event handler throws an exception, then wrap it
+ * in a MethodInvocationException. Don't pass through RuntimeExceptions like other
+ * similar catchall code blocks.
+ */
+ catch( Exception e )
+ {
+ throw new MethodInvocationException(
+ "Invocation of method '"
+ + methodName + "' in " + o.getClass()
+ + " threw exception "
+ + e.toString(),
+ e, rsvc.getLogContext().getStackTrace(), methodName, getTemplateName(), this.getLine(), this.getColumn());
+ }
+ }
+
+ /*
+ * let non-Exception Throwables go...
+ */
+ else
+ {
+ /*
+ * no event cartridge to override. Just throw
+ */
+
+ throw new MethodInvocationException(
+ "Invocation of method '"
+ + methodName + "' in " + o.getClass()
+ + " threw exception "
+ + t.toString(),
+ t, rsvc.getLogContext().getStackTrace(), methodName, getTemplateName(), this.getLine(), this.getColumn());
+ }
+ }
+
+ /**
+ * Internal class used as key for method cache. Combines
+ * ASTMethod fields with array of parameter classes. Has
+ * public access (and complete constructor) for unit test
+ * purposes.
+ * @since 1.5
+ */
+ public static class MethodCacheKey
+ {
+ /**
+ * method name
+ */
+ private final String methodName;
+
+ /**
+ * parameters classes
+ */
+ private final Class<?>[] params;
+
+ /**
+ * whether the target object is of Class type
+ * (meaning we're searching either for methods
+ * of Class, or for static methods of the class
+ * this Class objects refers to)
+ * @since 2.2
+ */
+ private boolean classObject;
+
+ public MethodCacheKey(String methodName, Class<?>[] params, boolean classObject)
+ {
+ /*
+ * Should never be initialized with nulls, but to be safe we refuse
+ * to accept them.
+ */
+ this.methodName = (methodName != null) ? methodName : StringUtils.EMPTY;
+ this.params = (params != null) ? params : EMPTY_CLASS_ARRAY;
+ this.classObject = classObject;
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o)
+ {
+ /*
+ * note we skip the null test for methodName and params
+ * due to the earlier test in the constructor
+ */
+ if (o instanceof MethodCacheKey)
+ {
+ final MethodCacheKey other = (MethodCacheKey) o;
+ if (params.length == other.params.length &&
+ methodName.equals(other.methodName) &&
+ classObject == other.classObject)
+ {
+ for (int i = 0; i < params.length; ++i)
+ {
+ if (params[i] == null)
+ {
+ if (params[i] != other.params[i])
+ {
+ return false;
+ }
+ }
+ else if (!params[i].equals(other.params[i]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ int result = 17;
+
+ /*
+ * note we skip the null test for methodName and params
+ * due to the earlier test in the constructor
+ */
+ for (Class<?> param : params)
+ {
+ if (param != null)
+ {
+ result = result * 37 + param.hashCode();
+ }
+ }
+
+ result = result * 37 + methodName.hashCode();
+
+ return result;
+ }
+ }
+
+ /**
+ * @return Returns the methodName.
+ * @since 1.5
+ */
+ public String getMethodName()
+ {
+ return methodName;
+ }
+
+ /**
+ * Returns the string ".<i>method_name</i>(...)". Arguments literals are not rendered. This method is only
+ * used for displaying the VTL stacktrace when a rendering error is encountered when runtime.log.track_location is true.
+ * @return
+ */
+ @Override
+ public String literal()
+ {
+ if (literal != null)
+ {
+ return literal;
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append('.').append(getMethodName()).append("(...)");
+
+ return literal = builder.toString();
+ }
+
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java
new file mode 100644
index 00000000..d9de7911
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java
@@ -0,0 +1,99 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MathException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles modulus division<br><br>
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTModNode extends ASTMathNode
+{
+ /**
+ * @param id
+ */
+ public ASTModNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTModNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public Number perform(Number left, Number right, InternalContextAdapter context)
+ {
+ /*
+ * check for divide / modulo by 0
+ */
+ if (MathUtils.isZero(right))
+ {
+ String msg = "Right side of modulus operation is zero. Must be non-zero. "
+ + getLocation(context);
+ if (strictMode)
+ {
+ log.error(msg);
+ throw new MathException(msg, rsvc.getLogContext().getStackTrace());
+ }
+ else
+ {
+ log.debug(msg);
+ return null;
+ }
+ }
+ return MathUtils.modulo(left, right);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "%";
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java
new file mode 100644
index 00000000..67bad83e
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java
@@ -0,0 +1,81 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles multiplication<br><br>
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTMulNode extends ASTMathNode
+{
+ /**
+ * @param id
+ */
+ public ASTMulNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTMulNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public Number perform(Number left, Number right, InternalContextAdapter context)
+ {
+ return MathUtils.multiply(left, right);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "*";
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java
new file mode 100644
index 00000000..3f78f5ab
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java
@@ -0,0 +1,50 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles <code>arg1 != arg2</code> by negating evaluation of ASTEQNode
+ */
+public class ASTNENode extends ASTEQNode
+{
+ public ASTNENode(int id)
+ {
+ super(id);
+ }
+
+ public ASTNENode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException
+ {
+ return !super.evaluate(context);
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java
new file mode 100644
index 00000000..842a2925
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java
@@ -0,0 +1,94 @@
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MathException;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.util.DuckType;
+
+public class ASTNegateNode extends SimpleNode
+{
+ protected boolean strictMode = false;
+
+ public ASTNegateNode(int i)
+ {
+ super(i);
+ }
+
+ public ASTNegateNode(Parser p, int i)
+ {
+ super(p, i);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ super.init(context, data);
+ /* save a literal image now (needed in case of error) */
+ strictMode = rsvc.getBoolean(RuntimeConstants.STRICT_MATH, false);
+ cleanupParserAndTokens();
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return jjtGetChild(0).evaluate(context);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ Object value = jjtGetChild(0).value( context );
+ try
+ {
+ value = DuckType.asNumber(value);
+ }
+ catch (NumberFormatException nfe) {}
+ if (!(value instanceof Number))
+ {
+ String msg = "Argument of unary negate (" +
+ jjtGetChild(0).literal() +
+ ") " +
+ (value == null ? "has a null value." : "is not a Number.");
+ if (strictMode)
+ {
+ log.error(msg);
+ throw new MathException(msg, rsvc.getLogContext().getStackTrace());
+ }
+ else
+ {
+ log.debug(msg);
+ return null;
+ }
+ }
+ return MathUtils.negate((Number) value);
+ }
+ @Override
+ public String literal()
+ {
+ return "-" + jjtGetChild(0).literal();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java
new file mode 100644
index 00000000..310a3536
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java
@@ -0,0 +1,96 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTNotNode extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTNotNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTNotNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return !jjtGetChild(0).evaluate(context);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return (jjtGetChild(0).evaluate( context ) ? Boolean.FALSE : Boolean.TRUE) ;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ @Override
+ public String literal()
+ {
+ return "!" + jjtGetChild(0).literal();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java
new file mode 100644
index 00000000..16d1b236
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java
@@ -0,0 +1,103 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.Parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+public class ASTObjectArray extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTObjectArray(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTObjectArray(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ int size = jjtGetNumChildren();
+
+ // since we know the amount of elements, initialize arraylist with proper size
+ List<Object> objectArray = new ArrayList<>(size);
+
+ for (int i = 0; i < size; i++)
+ {
+ objectArray.add(jjtGetChild(i).value(context));
+ }
+
+ return objectArray;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || children != null && children.length > 0;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java
new file mode 100644
index 00000000..c019e452
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java
@@ -0,0 +1,115 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+*/
+public class ASTOrNode extends ASTLogicalOperator
+{
+ /**
+ * @param id
+ */
+ public ASTOrNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTOrNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "||";
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * Returns the value of the expression.
+ * Since the value of the expression is simply the boolean
+ * result of evaluate(), lets return that.
+ * @param context
+ * @return The Expression value.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object value(InternalContextAdapter context )
+ throws MethodInvocationException
+ {
+ return evaluate(context) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /**
+ * the logical or :
+ * <pre>
+ * left || null -&gt; left
+ * null || right -&gt; right
+ * null || null -&gt; false
+ * left || right -&gt; left || right
+ * </pre>
+ * @param context
+ * @return The evaluation result.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ Node left = jjtGetChild(0);
+ Node right = jjtGetChild(1);
+
+ /*
+ * if the left is not null and true, then true
+ */
+
+ if (left != null && left.evaluate( context ) )
+ return true;
+
+ /*
+ * same for right
+ */
+
+ return right != null && right.evaluate(context);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java
new file mode 100644
index 00000000..1c004286
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java
@@ -0,0 +1,55 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+
+
+/**
+ *
+ */
+public class ASTParameters extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTParameters(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTParameters(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java
new file mode 100644
index 00000000..d4ddda9d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java
@@ -0,0 +1,1165 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.app.event.EventHandlerUtil;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.io.Filter;
+import org.apache.velocity.runtime.Renderable;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.directive.Block.Reference;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+import org.apache.velocity.util.ClassUtils;
+import org.apache.velocity.util.DuckType;
+import org.apache.velocity.util.StringUtils;
+import org.apache.velocity.util.introspection.Info;
+import org.apache.velocity.util.introspection.VelMethod;
+import org.apache.velocity.util.introspection.VelPropertySet;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Deque;
+
+/**
+ * This class is responsible for handling the references in
+ * VTL ($foo).
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
+ * @version $Id$
+*/
+public class ASTReference extends SimpleNode
+{
+ /* Reference types */
+ private static final int NORMAL_REFERENCE = 1;
+ private static final int FORMAL_REFERENCE = 2;
+ private static final int QUIET_REFERENCE = 3;
+ private static final int RUNT = 4;
+
+ private int referenceType;
+ private String nullString;
+ private String alternateNullStringKey;
+ private String rootString;
+ private boolean escaped = false;
+ private boolean computableReference = true;
+ private boolean logOnNull = true;
+ private boolean lookupAlternateLiteral = false;
+ private String escPrefix = "";
+ private String morePrefix = "";
+ private String identifier = "";
+
+ private boolean checkEmpty;
+
+ /**
+ * Indicates if we are running in strict reference mode.
+ */
+ public boolean strictRef = false;
+
+ /**
+ * non null Indicates if we are setting an index reference e.g, $foo[2], which basically
+ * means that the last syntax of the reference are brackets.
+ */
+ private ASTIndex astIndex = null;
+
+ /**
+ * non null Indicates that an alternate value has been provided
+ */
+ private ASTExpression astAlternateValue = null;
+
+ /**
+ * Indicates if we are using modified escape behavior in strict mode.
+ * mainly we allow \$abc -&gt; to render as $abc
+ */
+ public boolean strictEscape = false;
+
+ private int numChildren = 0;
+
+ /**
+ * Whether to trigger an event for invalid quiet references
+ * @since 2.2
+ */
+ private boolean warnInvalidQuietReferences = false;
+
+ /**
+ * Whether to trigger an event for invalid null references, that is when a value
+ * is present in the context or parent object but is null
+ * @since 2.2
+ */
+ private boolean warnInvalidNullReferences = false;
+
+ /**
+ * Whether to trigger an event for invalid tested references - as in #if($foo)
+ * @since 2.2
+ */
+ private boolean warnInvalidTestedReferences = false;
+
+ protected Info uberInfo;
+
+ /**
+ * @param id
+ */
+ public ASTReference(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTReference(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ super.init(context, data);
+
+ strictEscape = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, false);
+ strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
+ lookupAlternateLiteral = rsvc.getBoolean(RuntimeConstants.VM_ENABLE_BC_MODE, false);
+
+ /*
+ * the only thing we can do in init() is getRoot()
+ * as that is template based, not context based,
+ * so it's thread- and context-safe
+ */
+
+ rootString = rsvc.useStringInterning() ? getRoot().intern() : getRoot();
+ if (lookupAlternateLiteral)
+ {
+ /* cache alternate null tring key */
+ alternateNullStringKey = ".literal." + nullString;
+ }
+
+ numChildren = jjtGetNumChildren();
+
+ // This is an expensive call, so get it now.
+ literal();
+
+ /*
+ * and if appropriate...
+ */
+ if (numChildren > 0 )
+ {
+ Node lastNode = jjtGetChild(numChildren-1);
+ if (lastNode instanceof ASTIndex)
+ {
+ /*
+ * only used in SetValue, where alternate value is forbidden
+ */
+ astIndex = (ASTIndex) lastNode;
+ }
+ else if (lastNode instanceof ASTExpression)
+ {
+ astAlternateValue = (ASTExpression) lastNode;
+ --numChildren;
+ }
+ else
+ {
+ identifier = lastNode.getFirstTokenImage();
+ }
+ }
+
+
+ /*
+ * make an uberinfo - saves new's later on
+ */
+ uberInfo = new Info(getTemplateName(), getLine(),getColumn());
+
+ /*
+ * track whether we log invalid references
+ */
+ logOnNull =
+ rsvc.getBoolean(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, true);
+
+ /*
+ * whether to check for emptiness when evaluatingnumChildren
+ */
+ checkEmpty =
+ rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true);
+
+ /* invalid references special cases */
+
+ warnInvalidQuietReferences =
+ rsvc.getBoolean(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES_QUIET, false);
+ warnInvalidNullReferences =
+ rsvc.getBoolean(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES_NULL, false);
+ warnInvalidTestedReferences =
+ rsvc.getBoolean(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES_TESTED, false);
+
+
+ /*
+ * In the case we are referencing a variable with #if($foo) or
+ * #if( ! $foo) then we allow variables to be undefined and we
+ * set strictRef to false so that if the variable is undefined
+ * an exception is not thrown.
+ */
+ if (strictRef && numChildren == 0)
+ {
+ logOnNull = false; // Strict mode allows nulls
+
+ Node node = this.jjtGetParent();
+ if (node instanceof ASTNotNode // #if( ! $foo)
+ || node instanceof ASTExpression // #if( $foo )
+ || node instanceof ASTOrNode // #if( $foo || ...
+ || node instanceof ASTAndNode) // #if( $foo && ...
+ {
+ // Now scan up tree to see if we are in an If statement
+ while (node != null)
+ {
+ if (node instanceof ASTIfStatement)
+ {
+ strictRef = false;
+ break;
+ }
+ node = node.jjtGetParent();
+ }
+ }
+ }
+ saveTokenImages();
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * Returns the 'root string', the reference key
+ * @return the root string.
+ */
+ public String getRootString()
+ {
+ return rootString;
+ }
+
+ /**
+ * gets an Object that 'is' the value of the reference
+ *
+ * @param o Object parameter, unused per se, but non-null by convention inside an #if/#elseif evaluation
+ * @param context context used to generate value
+ * @return The execution result.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public Object execute(Object o, InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+
+ /*
+ * The only case where 'o' is not null is when this method is called by evaluate().
+ * Its value is not used, but it is a convention meant to allow statements like
+ * #if($invalidReference) *not* to trigger an invalid reference event.
+ * Statements like #if($invalidReference.prop) should *still* trigger an invalid reference event.
+ * Statements like #if($validReference.invalidProp) should not.
+ */
+ boolean onlyTestingReference = (o != null);
+
+ if (referenceType == RUNT)
+ return null;
+
+ /*
+ * get the root object from the context
+ */
+
+ Object result = getRootVariableValue(context);
+
+ if (result == null && !strictRef)
+ {
+ /*
+ * do not trigger an invalid reference if the reference is present, but with a null value
+ * don't either for a quiet reference or inside an #if/#elseif evaluation context
+ */
+ if ((referenceType != QUIET_REFERENCE || warnInvalidQuietReferences) &&
+ (numChildren > 0 ||
+ (!context.containsKey(rootString) || warnInvalidNullReferences) &&
+ (!onlyTestingReference || warnInvalidTestedReferences)))
+ {
+ result = EventHandlerUtil.invalidGetMethod(rsvc, context,
+ rsvc.getParserConfiguration().getDollarChar() + rootString, null, null, uberInfo);
+ }
+
+ if (astAlternateValue != null && (!DuckType.asBoolean(result, true)))
+ {
+ result = astAlternateValue.value(context);
+ }
+
+ return result;
+ }
+
+ /*
+ * Iteratively work 'down' (it's flat...) the reference
+ * to get the value, but check to make sure that
+ * every result along the path is valid. For example:
+ *
+ * $hashtable.Customer.Name
+ *
+ * The $hashtable may be valid, but there is no key
+ * 'Customer' in the hashtable so we want to stop
+ * when we find a null value and return the null
+ * so the error gets logged.
+ */
+
+ try
+ {
+ Object previousResult = result;
+ int failedChild = -1;
+
+ for (int i = 0; i < numChildren; i++)
+ {
+ if (strictRef && result == null)
+ {
+ /*
+ * At this point we know that an attempt is about to be made
+ * to call a method or property on a null value.
+ */
+ String name = jjtGetChild(i).getFirstTokenImage();
+ throw new VelocityException("Attempted to access '"
+ + name + "' on a null value at "
+ + StringUtils.formatFileString(uberInfo.getTemplateName(),
+ + jjtGetChild(i).getLine(), jjtGetChild(i).getColumn()),
+ null, rsvc.getLogContext().getStackTrace());
+ }
+ previousResult = result;
+ result = jjtGetChild(i).execute(result,context);
+ if (result == null && !strictRef) // If strict and null then well catch this
+ // next time through the loop
+ {
+ failedChild = i;
+ break;
+ }
+ }
+
+ if (result == null)
+ {
+ if (failedChild == -1)
+ {
+ /*
+ * do not trigger an invalid reference if the reference is present, but with a null value
+ * don't either for a quiet reference,
+ * or inside an #if/#elseif evaluation context when there's no child
+ */
+ if ((!context.containsKey(rootString) || warnInvalidNullReferences) &&
+ (referenceType != QUIET_REFERENCE || warnInvalidQuietReferences) &&
+ (!onlyTestingReference || warnInvalidTestedReferences || numChildren > 0))
+ {
+ result = EventHandlerUtil.invalidGetMethod(rsvc, context,
+ rsvc.getParserConfiguration().getDollarChar() + rootString, previousResult, null, uberInfo);
+ }
+ }
+ else
+ {
+ Node child = jjtGetChild(failedChild);
+ // do not call bad reference handler if the getter is present
+ // (it means the getter has been called and returned null)
+ // do not either for a quiet reference or if the *last* child failed while testing the reference
+ Object getter = context.icacheGet(child);
+ if ((getter == null || warnInvalidNullReferences) &&
+ (referenceType != QUIET_REFERENCE || warnInvalidQuietReferences) &&
+ (!onlyTestingReference || warnInvalidTestedReferences || failedChild < numChildren - 1))
+ {
+ StringBuilder name = new StringBuilder(String.valueOf(rsvc.getParserConfiguration().getDollarChar())).append(rootString);
+ for (int i = 0; i <= failedChild; i++)
+ {
+ Node node = jjtGetChild(i);
+ if (node instanceof ASTMethod)
+ {
+ name.append(".").append(((ASTMethod) node).getMethodName()).append("()");
+ }
+ else
+ {
+ name.append(".").append(node.getFirstTokenImage());
+ }
+ }
+
+ if (child instanceof ASTMethod)
+ {
+ String methodName = ((ASTMethod) jjtGetChild(failedChild)).getMethodName();
+ result = EventHandlerUtil.invalidMethod(rsvc, context,
+ name.toString(), previousResult, methodName, uberInfo);
+ }
+ else
+ {
+ String property = jjtGetChild(failedChild).getFirstTokenImage();
+ result = EventHandlerUtil.invalidGetMethod(rsvc, context,
+ name.toString(), previousResult, property, uberInfo);
+ }
+ }
+ }
+ }
+
+ // Check alternate value at the end of the evaluation
+ if (astAlternateValue != null && (!DuckType.asBoolean(result, true)))
+ {
+ result = astAlternateValue.value(context);
+ }
+
+ return result;
+ }
+ catch(MethodInvocationException mie)
+ {
+ mie.setReferenceName(rootString);
+ throw mie;
+ }
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+ }
+
+ /**
+ * gets the value of the reference and outputs it to the
+ * writer.
+ *
+ * @param context context of data to use in getting value
+ * @param writer writer to render to
+ * @return True if rendering was successful.
+ * @throws IOException
+ * @throws MethodInvocationException
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer) throws IOException,
+ MethodInvocationException
+ {
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+
+ if (referenceType == RUNT)
+ {
+ writer.write(literal);
+ return true;
+ }
+
+ Object value = null;
+ if (escaped && strictEscape)
+ {
+ /*
+ * If we are in strict mode and the variable is escaped, then don't bother to
+ * retrieve the value since we won't use it. And if the var is not defined
+ * it will throw an exception. Set value to TRUE to fall through below with
+ * simply printing $foo, and not \$foo
+ */
+ value = Boolean.TRUE;
+ }
+ else
+ {
+ value = execute(null, context);
+ }
+
+ String localNullString = null;
+
+ /*
+ * if this reference is escaped (\$foo) then we want to do one of two things: 1) if this is
+ * a reference in the context, then we want to print $foo 2) if not, then \$foo (its
+ * considered schmoo, not VTL)
+ */
+
+ if (escaped)
+ {
+ localNullString = getNullString(context);
+
+ if (value == null)
+ {
+ writer.write(escPrefix);
+ writer.write("\\");
+ writer.write(localNullString);
+ }
+ else
+ {
+ writer.write(escPrefix);
+ writer.write(localNullString);
+ }
+ return true;
+ }
+
+ /*
+ * the normal processing
+ *
+ * if we have an event cartridge, get a new value object
+ */
+
+ value = EventHandlerUtil.referenceInsert(rsvc, context, literal, value);
+
+ String toString = null;
+ if (value != null)
+ {
+ if (value instanceof Renderable)
+ {
+ Renderable renderable = (Renderable)value;
+ try
+ {
+ writer.write(escPrefix);
+ writer.write(morePrefix);
+ if (renderable.render(context,writer))
+ {
+ return true;
+ }
+ }
+ catch(RuntimeException e)
+ {
+ // We commonly get here when an error occurs within a block reference.
+ // We want to log where the reference is at so that a developer can easily
+ // know where the offending call is located. This can be seen
+ // as another element of the error stack we report to log.
+ log.error("Exception rendering "
+ + ((renderable instanceof Reference)? "block ":"Renderable ")
+ + rootString + " at " + StringUtils.formatFileString(this));
+ throw e;
+ }
+ }
+
+ toString = DuckType.asString(value);
+ }
+
+ if (value == null || toString == null)
+ {
+ if (strictRef)
+ {
+ if (referenceType != QUIET_REFERENCE)
+ {
+ log.error("Prepend the reference with '$!' e.g., $!{}" +
+ " if you want Velocity to ignore the reference when it evaluates to null",
+ literal().substring(1));
+ if (value == null)
+ {
+ throw new VelocityException("Reference " + literal()
+ + " evaluated to null when attempting to render at "
+ + StringUtils.formatFileString(this)
+ , null, rsvc.getLogContext().getStackTrace());
+ }
+ else // toString == null
+ {
+ // This will probably rarely happen, but when it does we want to
+ // inform the user that toString == null so they don't pull there
+ // hair out wondering why Velocity thinks the value is null.
+ throw new VelocityException("Reference " + literal()
+ + " evaluated to object " + value.getClass().getName()
+ + " whose toString() method returned null at "
+ + StringUtils.formatFileString(this)
+ , null, rsvc.getLogContext().getStackTrace());
+ }
+ }
+ return true;
+ }
+
+ /*
+ * write prefix twice, because it's schmoo, so the \ don't escape each
+ * other...
+ */
+ localNullString = getNullString(context);
+ if (!strictEscape)
+ {
+ // If in strict escape mode then we only print escape once.
+ // Yea, I know.. brittle stuff
+ writer.write(escPrefix);
+ }
+ writer.write(escPrefix);
+ writer.write(morePrefix);
+ writer.write(localNullString);
+
+ if (logOnNull && referenceType != QUIET_REFERENCE)
+ {
+ log.debug("Null reference [template '{}', line {}, column {}]: {} cannot be resolved.",
+ getTemplateName(), this.getLine(), this.getColumn(), this.literal());
+ }
+ }
+ else
+ {
+ /*
+ * non-null processing
+ */
+ writer.write(escPrefix);
+ writer.write(morePrefix);
+ if (writer instanceof Filter)
+ {
+ ((Filter)writer).writeReference(toString);
+ }
+ else
+ {
+ writer.write(toString);
+ }
+
+ }
+ return true;
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+ }
+
+ /**
+ * This method helps to implement the "render literal if null" functionality.
+ *
+ * VelocimacroProxy saves references to macro arguments (AST nodes) so that if we have a macro
+ * #foobar($a $b) then there is key "$a.literal" which points to the literal presentation of the
+ * argument provided to variable $a. If the value of $a is null, we render the string that was
+ * provided as the argument.
+ *
+ * @param context
+ * @return
+ */
+ private String getNullString(InternalContextAdapter context)
+ {
+ String ret = nullString;
+
+ if (lookupAlternateLiteral)
+ {
+ Deque<String> alternateLiteralsStack = (Deque<String>)context.get(alternateNullStringKey);
+ if (alternateLiteralsStack != null && alternateLiteralsStack.size() > 0)
+ {
+ ret = alternateLiteralsStack.peekFirst();
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Computes boolean value of this reference
+ * Returns the actual value of reference return type
+ * boolean, and 'true' if value is not null
+ *
+ * @param context context to compute value with
+ * @return True if evaluation was ok.
+ * @throws MethodInvocationException
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ Object value = execute(this, context); // non-null object as first parameter by convention for 'evaluate'
+ if (value == null)
+ {
+ return false;
+ }
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+ return DuckType.asBoolean(value, checkEmpty);
+ }
+ catch(Exception e)
+ {
+ throw new VelocityException("Reference evaluation threw an exception at "
+ + StringUtils.formatFileString(this), e, rsvc.getLogContext().getStackTrace());
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return (computableReference ? execute(null, context) : null);
+ }
+
+
+ /**
+ * Utility class to handle nulls when printing a class type
+ * @param clazz
+ * @return class name, or the string "null"
+ */
+ public static String printClass(Class<?> clazz)
+ {
+ return clazz == null ? "null" : clazz.getName();
+ }
+
+
+ /**
+ * Sets the value of a complex reference (something like $foo.bar)
+ * Currently used by ASTSetReference()
+ *
+ * @see ASTSetDirective
+ *
+ * @param context context object containing this reference
+ * @param value Object to set as value
+ * @return true if successful, false otherwise
+ * @throws MethodInvocationException
+ */
+ public boolean setValue(InternalContextAdapter context, Object value)
+ throws MethodInvocationException
+ {
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+
+ if (astAlternateValue != null)
+ {
+ log.error("reference set cannot have a default value {}",
+ StringUtils.formatFileString(uberInfo));
+ return false;
+ }
+
+ if (numChildren == 0)
+ {
+ context.put(rootString, value);
+ return true;
+ }
+
+ /*
+ * The rootOfIntrospection is the object we will
+ * retrieve from the Context. This is the base
+ * object we will apply reflection to.
+ */
+
+ Object result = getRootVariableValue(context);
+
+ if (result == null)
+ {
+ log.error("reference set is not a valid reference at {}",
+ StringUtils.formatFileString(uberInfo));
+ return false;
+ }
+
+ /*
+ * How many child nodes do we have?
+ */
+
+ for (int i = 0; i < numChildren - 1; i++)
+ {
+ result = jjtGetChild(i).execute(result, context);
+
+ if (result == null)
+ {
+ if (strictRef)
+ {
+ String name = jjtGetChild(i+1).getFirstTokenImage();
+ throw new MethodInvocationException("Attempted to access '"
+ + name + "' on a null value", null, rsvc.getLogContext().getStackTrace(), name, uberInfo.getTemplateName(),
+ jjtGetChild(i+1).getLine(), jjtGetChild(i+1).getColumn());
+ }
+
+ log.error("reference set is not a valid reference at {}",
+ StringUtils.formatFileString(uberInfo));
+ return false;
+ }
+ }
+
+ if (astIndex != null)
+ {
+ // If astIndex is not null then we are actually setting an index reference,
+ // something of the form $foo[1] =, or in general any reference that ends with
+ // the brackets. This means that we need to call a more general method
+ // of the form set(Integer, <something>), or put(Object, <something), where
+ // the first parameter is the index value and the second is the LHS of the set.
+
+ Object argument = astIndex.jjtGetChild(0).value(context);
+ // If negative, turn -1 into (size - 1)
+ argument = ASTIndex.adjMinusIndexArg(argument, result, context, astIndex);
+ Object [] params = {argument, value};
+ Class<?>[] paramClasses = {params[0] == null ? null : params[0].getClass(),
+ params[1] == null ? null : params[1].getClass()};
+
+ String methodName = "set";
+ VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses,
+ result, context, astIndex, false);
+
+ if (method == null)
+ {
+ // If we can't find a 'set' method, lets try 'put', This warrents a little
+ // investigation performance wise... if the user is using the hash
+ // form $foo["blaa"], then it may be expensive to first try and fail on 'set'
+ // then go to 'put'? The problem is that getMethod will try the cache, then
+ // perform introspection on 'result' for 'set'
+ methodName = "put";
+ method = ClassUtils.getMethod(methodName, params, paramClasses,
+ result, context, astIndex, false);
+ }
+
+ if (method == null)
+ {
+ // couldn't find set or put method, so bail
+ if (strictRef)
+ {
+ throw new VelocityException(
+ "Found neither a 'set' or 'put' method with param types '("
+ + printClass(paramClasses[0]) + "," + printClass(paramClasses[1])
+ + ")' on class '" + result.getClass().getName()
+ + "' at " + StringUtils.formatFileString(astIndex)
+ , null, rsvc.getLogContext().getStackTrace());
+ }
+ return false;
+ }
+
+ try
+ {
+ method.invoke(result, params);
+ }
+ catch(RuntimeException e)
+ {
+ // Kludge since invoke throws Exception, pass up Runtimes
+ throw e;
+ }
+ catch(Exception e)
+ {
+ throw new MethodInvocationException(
+ "Exception calling method '"
+ + methodName + "("
+ + printClass(paramClasses[0]) + "," + printClass(paramClasses[1])
+ + ")' in " + result.getClass(),
+ e.getCause(), rsvc.getLogContext().getStackTrace(), identifier, astIndex.getTemplateName(), astIndex.getLine(),
+ astIndex.getColumn());
+ }
+
+ return true;
+ }
+
+
+ /*
+ * We support two ways of setting the value in a #set($ref.foo = $value ):
+ * 1) ref.setFoo( value )
+ * 2) ref,put("foo", value ) to parallel the get() map introspection
+ */
+
+ try
+ {
+ VelPropertySet vs =
+ rsvc.getUberspect().getPropertySet(result, identifier,
+ value, uberInfo);
+
+ if (vs == null)
+ {
+ if (strictRef)
+ {
+ throw new MethodInvocationException("Object '" + result.getClass().getName() +
+ "' does not contain property '" + identifier + "'", null, rsvc.getLogContext().getStackTrace(), identifier,
+ uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ vs.invoke(result, value);
+ }
+ catch(InvocationTargetException ite)
+ {
+ /*
+ * this is possible
+ */
+
+ throw new MethodInvocationException(
+ "ASTReference: Invocation of method '"
+ + identifier + "' in " + result.getClass()
+ + " threw exception "
+ + ite.getTargetException().toString(),
+ ite.getTargetException(), rsvc.getLogContext().getStackTrace(), identifier, getTemplateName(), this.getLine(), this.getColumn());
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ /*
+ * maybe a security exception?
+ */
+ String msg = "ASTReference setValue(): exception: " + e
+ + " template at " + StringUtils.formatFileString(uberInfo);
+ log.error(msg, e);
+ throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
+ }
+
+ return true;
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ }
+ }
+
+ private String getRoot()
+ {
+ Token t = getFirstToken();
+
+ /*
+ * we have a special case where something like
+ * $(\\)*!, where the user want's to see something
+ * like $!blargh in the output, but the ! prevents it from showing.
+ * I think that at this point, this isn't a reference.
+ */
+
+ /* so, see if we have "\\!" */
+
+ int slashbang = t.image.indexOf("\\!");
+
+ if (slashbang != -1)
+ {
+ if (strictEscape)
+ {
+ // If we are in strict escape mode, then we consider this type of
+ // pattern a non-reference, and we print it out as schmoo...
+ nullString = literal();
+ escaped = true;
+ return nullString;
+ }
+
+ /*
+ * lets do all the work here. I would argue that if this occurs,
+ * it's not a reference at all, so preceding \ characters in front
+ * of the $ are just schmoo. So we just do the escape processing
+ * trick (even | odd) and move on. This kind of breaks the rule
+ * pattern of $ and # but '!' really tosses a wrench into things.
+ */
+
+ /*
+ * count the escapes: even # -> not escaped, odd -> escaped
+ */
+
+ int i = 0;
+ int len = t.image.length();
+
+ i = t.image.indexOf(rsvc.getParserConfiguration().getDollarChar());
+
+ if (i == -1)
+ {
+ /* yikes! */
+ log.error("ASTReference.getRoot(): internal error: "
+ + "no $ found for slashbang.");
+ computableReference = false;
+ nullString = t.image;
+ return nullString;
+ }
+
+ while (i < len && t.image.charAt(i) != '\\')
+ {
+ i++;
+ }
+
+ /* ok, i is the first \ char */
+
+ int start = i;
+ int count = 0;
+
+ while (i < len && t.image.charAt(i++) == '\\')
+ {
+ count++;
+ }
+
+ /*
+ * now construct the output string. We really don't care about
+ * leading slashes as this is not a reference. It's quasi-schmoo
+ */
+
+ nullString = t.image.substring(0,start); // prefix up to the first
+ nullString += t.image.substring(start, start + count-1 ); // get the slashes
+ nullString += t.image.substring(start+count); // and the rest, including the
+
+ /*
+ * this isn't a valid reference, so lets short circuit the value
+ * and set calcs
+ */
+
+ computableReference = false;
+
+ return nullString;
+ }
+
+ /*
+ * we need to see if this reference is escaped. if so
+ * we will clean off the leading \'s and let the
+ * regular behavior determine if we should output this
+ * as \$foo or $foo later on in render(). Laziness..
+ */
+
+ escaped = false;
+
+ if (t.image.startsWith("\\"))
+ {
+ /*
+ * count the escapes: even # -> not escaped, odd -> escaped
+ */
+
+ int i = 0;
+ int len = t.image.length();
+
+ while (i < len && t.image.charAt(i) == '\\')
+ {
+ i++;
+ }
+
+ if ((i % 2) != 0)
+ escaped = true;
+
+ if (i > 0)
+ escPrefix = t.image.substring(0, i / 2 );
+
+ t.image = t.image.substring(i);
+ }
+
+ /*
+ * Look for preceding stuff like '#' and '$'
+ * and snip it off, except for the
+ * last $
+ */
+
+ int loc1 = t.image.lastIndexOf(rsvc.getParserConfiguration().getDollarChar());
+
+ /*
+ * if we have extra stuff, loc > 0
+ * ex. '#$foo' so attach that to
+ * the prefix.
+ */
+ if (loc1 > 0)
+ {
+ morePrefix = morePrefix + t.image.substring(0, loc1);
+ t.image = t.image.substring(loc1);
+ }
+
+ /*
+ * Now it should be clean. Get the literal in case this reference
+ * isn't backed by the context at runtime, and then figure out what
+ * we are working with.
+ */
+
+ // FIXME: this is the key to render nulls as literals, we need to look at context(refname+".literal")
+ nullString = literal();
+
+ if (t.image.startsWith("$!"))
+ {
+ referenceType = QUIET_REFERENCE;
+
+ /*
+ * only if we aren't escaped do we want to null the output
+ */
+
+ if (!escaped)
+ nullString = "";
+
+ if (t.image.startsWith("$!{"))
+ {
+ /*
+ * ex: $!{provider.Title}
+ */
+
+ return t.next.image;
+ }
+ else
+ {
+ /*
+ * ex: $!provider.Title
+ */
+
+ return t.image.substring(2);
+ }
+ }
+ else if (t.image.equals("${"))
+ {
+ /*
+ * ex: ${provider.Title}
+ */
+
+ referenceType = FORMAL_REFERENCE;
+ return t.next.image;
+ }
+ else if (t.image.startsWith("$"))
+ {
+ /*
+ * just nip off the '$' so we have
+ * the root
+ */
+
+ referenceType = NORMAL_REFERENCE;
+ return t.image.substring(1);
+ }
+ else
+ {
+ /*
+ * this is a 'RUNT', which can happen in certain circumstances where
+ * the parser is fooled into believing that an IDENTIFIER is a real
+ * reference. Another 'dreaded' MORE hack :).
+ */
+ referenceType = RUNT;
+ return t.image;
+ }
+
+ }
+
+ /**
+ * @param context
+ * @return The evaluated value of the variable.
+ * @throws MethodInvocationException
+ */
+ public Object getRootVariableValue(InternalContextAdapter context)
+ {
+ Object obj = null;
+ try
+ {
+ obj = context.get(rootString);
+ }
+ catch(RuntimeException e)
+ {
+ log.error("Exception calling reference ${} at {}",
+ rootString, StringUtils.formatFileString(uberInfo));
+ throw e;
+ }
+
+ if (obj == null && strictRef && astAlternateValue == null)
+ {
+ if (!context.containsKey(rootString))
+ {
+ log.error("Variable ${} has not been set at {}",
+ rootString, StringUtils.formatFileString(uberInfo));
+ throw new MethodInvocationException("Variable $" + rootString +
+ " has not been set", null, rsvc.getLogContext().getStackTrace(), identifier,
+ uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
+ }
+ }
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java
new file mode 100644
index 00000000..0cf4d566
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java
@@ -0,0 +1,304 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.app.event.EventHandlerUtil;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+import org.apache.velocity.util.introspection.Info;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Node for the #set directive
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTSetDirective extends SimpleNode
+{
+ private String leftReference = "";
+ private Node right = null;
+ private ASTReference left = null;
+ private boolean isInitialized;
+ private String prefix = "";
+ private String postfix = "";
+
+ /*
+ * '#' and '$' prefix characters eaten by javacc MORE mode
+ */
+ private String morePrefix = "";
+
+
+ /**
+ * This is really immutable after the init, so keep one for this node
+ */
+ protected Info uberInfo;
+
+ /**
+ * Indicates if we are running in strict reference mode.
+ */
+ protected boolean strictRef = false;
+
+ /**
+ * @param id
+ */
+ public ASTSetDirective(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTSetDirective(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * simple init. We can get the RHS and LHS as the the tree structure is static
+ * @param context
+ * @param data
+ * @return Init result.
+ * @throws TemplateInitException
+ */
+ @Override
+ public synchronized Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ /* This method is synchronized to prevent double initialization or initialization while rendering */
+
+ if (!isInitialized)
+ {
+ /*
+ * init the tree correctly
+ */
+
+ super.init( context, data );
+
+ /*
+ * handle '$' and '#' chars prefix
+ */
+ Token t = getFirstToken();
+ int pos = -1;
+ while (t != null && (pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar())) == -1)
+ {
+ t = t.next;
+ }
+ if (t != null && pos > 0)
+ {
+ morePrefix = t.image.substring(0, pos);
+ }
+
+
+ uberInfo = new Info(getTemplateName(),
+ getLine(), getColumn());
+
+ right = getRightHandSide();
+ left = getLeftHandSide();
+
+ strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
+
+ /*
+ * grab this now. No need to redo each time
+ */
+ leftReference = left.firstImage.substring(1);
+
+ /* handle backward compatible space gobbling if asked so */
+ if (rsvc.getSpaceGobbling() == SpaceGobbling.BC)
+ {
+ Node previousNode = null;
+ for (int brother = 0; brother < parent.jjtGetNumChildren(); ++brother)
+ {
+ Node node = parent.jjtGetChild(brother);
+ if (node == this) break;
+ previousNode = node;
+ }
+ if (previousNode == null) prefix = "";
+ else if (previousNode instanceof ASTText)
+ {
+ ASTText text = (ASTText)previousNode;
+ if (text.getCtext().matches("[ \t]*"))
+ {
+ text.setCtext("");
+ }
+ }
+ else prefix = "";
+ }
+
+ isInitialized = true;
+
+ cleanupParserAndTokens();
+ }
+
+ return data;
+ }
+
+ /**
+ * set indentation prefix
+ * @param prefix
+ */
+ public void setPrefix(String prefix)
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * get indentation prefix
+ * @return indentation prefix
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ /**
+ * set indentation postfix
+ * @param postfix
+ */
+ public void setPostfix(String postfix)
+ {
+ this.postfix = postfix;
+ }
+
+ /**
+ * get indentation postfix
+ * @return indentation prefix
+ */
+ public String getPostfix()
+ {
+ return postfix;
+ }
+
+ /**
+ * puts the value of the RHS into the context under the key of the LHS
+ * @param context
+ * @param writer
+ * @return True if rendering was sucessful.
+ * @throws IOException
+ * @throws MethodInvocationException
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException, MethodInvocationException
+ {
+ try
+ {
+ rsvc.getLogContext().pushLogContext(this, uberInfo);
+
+ SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling();
+
+ /* Velocity 1.x space gobbling for #set is rather wacky:
+ prefix is eaten *only* if previous token is not a text node.
+ We handle this by appropriately emptying the prefix in BC mode.
+ */
+
+ if (morePrefix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0)
+ {
+ writer.write(prefix);
+ }
+
+ writer.write(morePrefix);
+
+ /*
+ * get the RHS node, and its value
+ */
+
+ Object value = right.value(context);
+
+ if ( value == null && !strictRef)
+ {
+ String rightReference = null;
+ if (right instanceof ASTExpression)
+ {
+ rightReference = ((ASTExpression) right).lastImage;
+ }
+ EventHandlerUtil.invalidSetMethod(rsvc, context, leftReference, rightReference, uberInfo);
+ }
+
+ if (morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE)
+ {
+ writer.write(postfix);
+ }
+
+ return left.setValue(context, value);
+ }
+ finally
+ {
+ rsvc.getLogContext().popLogContext();
+ StringBuilder builder;
+ }
+ }
+
+ /**
+ * Returns the string "#set($<i>reference</i> = ...)". RHS is not rendered. This method is only
+ * used for displaying the VTL stacktrace when a rendering error is encountered when runtime.log.track_location is true.
+ * @return
+ */
+ @Override
+ public String literal()
+ {
+ if (literal != null)
+ {
+ return literal;
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append("#set(").append(left.literal()).append(" = ...)");
+ return literal = builder.toString();
+ }
+
+ /**
+ * returns the ASTReference that is the LHS of the set statement
+ *
+ * @return left hand side of #set statement
+ */
+ private ASTReference getLeftHandSide()
+ {
+ return (ASTReference) jjtGetChild(0);
+ }
+
+ /**
+ * returns the RHS Node of the set statement
+ *
+ * @return right hand side of #set statement
+ */
+ private Node getRightHandSide()
+ {
+ return jjtGetChild(1);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java
new file mode 100644
index 00000000..44134727
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java
@@ -0,0 +1,364 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you 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.
+ */
+
+import org.apache.velocity.Template;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.parser.ParseException;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+import org.apache.velocity.util.StringBuilderWriter;
+import org.apache.velocity.util.StringUtils;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.Writer;
+
+/**
+ * ASTStringLiteral support. Will interpolate!
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class ASTStringLiteral extends SimpleNode
+{
+ /* cache the value of the interpolation switch */
+ private boolean interpolate = true;
+
+ private SimpleNode nodeTree = null;
+
+ private String image = "";
+
+ /**
+ * @param id
+ */
+ public ASTStringLiteral(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTStringLiteral(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * init : we don't have to do much. Init the tree (there shouldn't be one)
+ * and then see if interpolation is turned on.
+ *
+ * @param context
+ * @param data
+ * @return Init result.
+ * @throws TemplateInitException
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ /*
+ * simple habit... we prollie don't have an AST beneath us
+ */
+
+ super.init(context, data);
+
+ /*
+ * the stringlit is set at template parse time, so we can do this here
+ * for now. if things change and we can somehow create stringlits at
+ * runtime, this must move to the runtime execution path
+ *
+ * so, only if interpolation is turned on AND it starts with a " AND it
+ * has a directive or reference, then we can interpolate. Otherwise,
+ * don't bother.
+ */
+
+ interpolate = rsvc.getBoolean(
+ RuntimeConstants.INTERPOLATE_STRINGLITERALS, true)
+ && getFirstToken().image.startsWith("\"")
+ && ((getFirstToken().image.indexOf(rsvc.getParserConfiguration().getDollarChar()) != -1) || (getFirstToken().image
+ .indexOf(rsvc.getParserConfiguration().getHashChar()) != -1));
+
+ /*
+ * get the contents of the string, minus the '/" at each end
+ */
+ String img = getFirstToken().image;
+
+ /*
+ the literal string *should* contain enclosing quotes
+ */
+ literal = img;
+
+ image = img.substring(1, img.length() - 1);
+
+ if (img.startsWith("\""))
+ {
+ image = unescape(image);
+ }
+ if (img.charAt(0) == '"' || img.charAt(0) == '\'' )
+ {
+ // replace double-double quotes like "" with a single double quote "
+ // replace double single quotes '' with a single quote '
+ image = replaceQuotes(image, img.charAt(0));
+ }
+
+ if (interpolate)
+ {
+ /*
+ * parse and init the nodeTree
+ */
+ StringReader br = new StringReader(image);
+
+ /*
+ * it's possible to not have an initialization context - or we don't
+ * want to trust the caller - so have a fallback value if so
+ *
+ * Also, do *not* dump the VM namespace for this template
+ */
+
+ Template template = null;
+ if (context != null)
+ {
+ template = (Template)context.getCurrentResource();
+ }
+ if (template == null)
+ {
+ template = new Template();
+ template.setName("StringLiteral");
+ }
+ try
+ {
+ nodeTree = rsvc.parse(br, template);
+ }
+ catch (ParseException e)
+ {
+ String msg = "Failed to parse String literal at "+
+ StringUtils.formatFileString(template.getName(), getLine(), getColumn());
+ throw new TemplateInitException(msg, e, rsvc.getLogContext().getStackTrace(), template.getName(), getColumn(), getLine());
+ }
+
+ adjTokenLineNums(nodeTree);
+
+ /*
+ * init with context. It won't modify anything
+ */
+
+ nodeTree.init(context, rsvc);
+ }
+
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * Adjust all the line and column numbers that comprise a node so that they
+ * are corrected for the string literals position within the template file.
+ * This is necessary if an exception is thrown while processing the node so
+ * that the line and column position reported reflects the error position
+ * within the template and not just relative to the error position within
+ * the string literal.
+ * @param node
+ */
+ public void adjTokenLineNums(Node node)
+ {
+ Token tok = node.getFirstToken();
+ // Test against null is probably not necessary, but just being safe
+ while(tok != null && tok != node.getLastToken())
+ {
+ // If tok is on the first line, then the actual column is
+ // offset by the template column.
+
+ if (tok.beginLine == 1)
+ tok.beginColumn += getColumn();
+
+ if (tok.endLine == 1)
+ tok.endColumn += getColumn();
+
+ tok.beginLine += getLine()- 1;
+ tok.endLine += getLine() - 1;
+ tok = tok.next;
+ }
+ }
+
+ /**
+ * Replaces double double-quotes with a single double quote ("" to ").
+ * Replaces double single quotes with a single quote ('' to ').
+ *
+ * @param s StringLiteral without the surrounding quotes
+ * @param literalQuoteChar char that starts the StringLiteral (" or ')
+ */
+ private String replaceQuotes(String s, char literalQuoteChar)
+ {
+ if( (literalQuoteChar == '"' && !s.contains("\"")) ||
+ (literalQuoteChar == '\'' && !s.contains("'")) )
+ {
+ return s;
+ }
+
+ StringBuilder result = new StringBuilder(s.length());
+ for(int i = 0, is = s.length(); i < is; i++)
+ {
+ char c = s.charAt(i);
+ result.append(c);
+
+ if( i + 1 < is )
+ {
+ char next = s.charAt(i + 1);
+ // '""' -> "", "''" -> ''
+ // thus it is not necessary to double quotes if the "surrounding" quotes
+ // of the StringLiteral are different. See VELOCITY-785
+ if( (literalQuoteChar == '"' && (next == '"' && c == '"')) ||
+ (literalQuoteChar == '\'' && (next == '\'' && c == '\'')) )
+ {
+ i++;
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * @param string
+ * @return unescaped string
+ * @since 1.6
+ */
+ public static String unescape(final String string)
+ {
+ int u = string.indexOf("\\u");
+ if (u < 0) return string;
+
+ StringBuilder result = new StringBuilder();
+
+ int lastCopied = 0;
+
+ for (;;)
+ {
+ result.append(string.substring(lastCopied, u));
+
+ /* we don't worry about an exception here,
+ * because the lexer checked that string is correct */
+ char c = (char) Integer.parseInt(string.substring(u + 2, u + 6), 16);
+ result.append(c);
+
+ lastCopied = u + 6;
+
+ u = string.indexOf("\\u", lastCopied);
+ if (u < 0)
+ {
+ result.append(string.substring(lastCopied));
+ return result.toString();
+ }
+ }
+ }
+
+
+ /**
+ * @param visitor
+ * @param data
+ * @return rendered object
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
+ * java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * Check to see if this is an interpolated string.
+ * @return true if this is constant (not an interpolated string)
+ * @since 1.6
+ */
+ public boolean isConstant()
+ {
+ return !interpolate;
+ }
+
+ /**
+ * renders the value of the string literal If the properties allow, and the
+ * string literal contains a $ or a # the literal is rendered against the
+ * context Otherwise, the stringlit is returned.
+ *
+ * @param context
+ * @return result of the rendering.
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ {
+ if (interpolate)
+ {
+ try
+ {
+ /*
+ * now render against the real context
+ */
+
+ Writer writer = new StringBuilderWriter();
+ nodeTree.render(context, writer);
+
+ /*
+ * and return the result as a String
+ */
+
+ return writer.toString();
+ }
+
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+
+ catch (IOException e)
+ {
+ String msg = "Error in interpolating string literal";
+ log.error(msg, e);
+ throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
+ }
+
+ }
+
+ /*
+ * ok, either not allowed to interpolate, there wasn't a ref or
+ * directive, or we failed, so just output the literal
+ */
+
+ return image;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ String str = (String)value(context);
+ return str != null && (!rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || !str.isEmpty());
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java
new file mode 100644
index 00000000..25642c13
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java
@@ -0,0 +1,67 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ * Handles subtraction of nodes (in #set() )<br><br>
+ *
+ * Please look at the Parser.jjt file which is
+ * what controls the generation of this class.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ASTSubtractNode extends ASTMathNode
+{
+ /**
+ * @param id
+ */
+ public ASTSubtractNode(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTSubtractNode(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ @Override
+ public String getLiteralOperator()
+ {
+ return "-";
+ }
+
+ @Override
+ public Number perform(Number left, Number right, InternalContextAdapter context)
+ {
+ return MathUtils.subtract(left, right);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java
new file mode 100644
index 00000000..0f4be56f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java
@@ -0,0 +1,122 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ *
+ */
+public class ASTText extends SimpleNode
+{
+ private String ctext;
+
+ /**
+ * @param id
+ */
+ public ASTText(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTText(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * text getter
+ * @return ctext
+ */
+ public String getCtext()
+ {
+ return ctext;
+ }
+
+ /**
+ * text setter
+ * @param ctext
+ */
+ public void setCtext(String ctext)
+ {
+ this.ctext = ctext;
+ }
+
+ /**
+ * text getter
+ * @return literal representation
+ */
+ @Override
+ public String literal()
+ {
+ return ctext;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ StringBuilder builder = new StringBuilder();
+ Token t = getFirstToken();
+ for (; t != getLastToken(); t = t.next)
+ {
+ builder.append(NodeUtils.tokenLiteral(parser, t));
+ }
+ builder.append(NodeUtils.tokenLiteral(parser, t));
+ ctext = builder.toString();
+
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException
+ {
+ writer.write(ctext);
+ return true;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java
new file mode 100644
index 00000000..d133b292
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java
@@ -0,0 +1,94 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This node holds the "Textblock" data which should not be interpreted by Velocity.
+ *
+ * Textblocks are marked in Velocity with #[[content here]]# notation. Velocity
+ * will output everything between the markers and does not attempt to parse it in any way.
+ */
+public class ASTTextblock extends SimpleNode
+{
+ public final String START;
+ public final String END;
+ private char[] ctext;
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTTextblock(Parser p, int id)
+ {
+ super(p, id);
+ START = parser.hash() + "[[";
+ END = "]]" + parser.hash();
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data)
+ throws TemplateInitException
+ {
+ Token t = getFirstToken();
+
+ String text = t.image;
+
+ // t.image is in format: #[[ <string> ]]#
+ // we must strip away the hash tags
+ text = text.substring(START.length(), text.length() - END.length());
+
+ ctext = text.toCharArray();
+
+ cleanupParserAndTokens();
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException
+ {
+ writer.write(ctext);
+ return true;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java
new file mode 100644
index 00000000..7763ed60
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java
@@ -0,0 +1,89 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTTrue extends SimpleNode
+{
+ private static Boolean value = Boolean.TRUE;
+
+ /**
+ * @param id
+ */
+ public ASTTrue(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTTrue(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ {
+ return true;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ {
+ return value;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java
new file mode 100644
index 00000000..6b104667
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java
@@ -0,0 +1,70 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+
+/**
+ *
+ */
+public class ASTVariable extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTVariable(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTVariable(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java
new file mode 100644
index 00000000..9493babf
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java
@@ -0,0 +1,69 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTWord extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTWord(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTWord(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ saveTokenImages();
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java
new file mode 100644
index 00000000..e9c67039
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java
@@ -0,0 +1,68 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.parser.Parser;
+
+/**
+ *
+ */
+public class ASTprocess extends SimpleNode
+{
+ /**
+ * @param id
+ */
+ public ASTprocess(int id)
+ {
+ super(id);
+ }
+
+ /**
+ * @param p
+ * @param id
+ */
+ public ASTprocess(Parser p, int id)
+ {
+ super(p, id);
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ Object obj = super.init(context, data);
+ cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
+ return obj;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java
new file mode 100644
index 00000000..d29b6cfe
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java
@@ -0,0 +1,83 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.slf4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Abstract class that is used to execute an arbitrary
+ * method that is in introspected. This is the superclass
+ * for the GetExecutor and PropertyExecutor.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public abstract class AbstractExecutor
+{
+ /** */
+ protected Logger log = null;
+
+ /**
+ * Method to be executed.
+ */
+ private Method method = null;
+
+ /**
+ * Execute method against context.
+ * @param o
+ * @return The resulting object.
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ public abstract Object execute(Object o)
+ throws IllegalAccessException, InvocationTargetException;
+
+ /**
+ * Tell whether the executor is alive by looking
+ * at the value of the method.
+ *
+ * @return True if executor is alive.
+ */
+ public boolean isAlive()
+ {
+ return (method != null);
+ }
+
+ /**
+ * @return The current method.
+ */
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * @param method
+ * @since 1.5
+ */
+ protected void setMethod(final Method method)
+ {
+ this.method = method;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java
new file mode 100644
index 00000000..1c834f8c
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java
@@ -0,0 +1,123 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+/**
+ * Handles discovery and valuation of a
+ * boolean object property, of the
+ * form public boolean is&lt;property&gt; when executed.
+ *
+ * We do this separately as to preserve the current
+ * quasi-broken semantics of get&lt;as is property&gt;
+ * get&lt;flip 1st char&gt; get("property") and now followed
+ * by is&lt;Property&gt;
+ *
+ * @author <a href="geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class BooleanPropertyExecutor extends PropertyExecutor
+{
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @since 1.5
+ */
+ public BooleanPropertyExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property)
+ {
+ this(log, introspector, clazz, property, false);
+ }
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @param wrapArray
+ * @since 1.5
+ */
+ public BooleanPropertyExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property, final boolean wrapArray)
+ {
+ super(log, introspector, clazz, property, wrapArray);
+ }
+
+ @Override
+ protected void discover(final Class<?> clazz, final String property)
+ {
+ try
+ {
+ Object [] params = {};
+
+ StringBuilder sb = new StringBuilder("is");
+ sb.append(property);
+
+ setMethod(getIntrospector().getMethod(clazz, sb.toString(), params));
+
+ if (!isAlive())
+ {
+ /*
+ * now the convenience, flip the 1st character
+ */
+
+ char c = sb.charAt(2);
+
+ if (Character.isLowerCase(c))
+ {
+ sb.setCharAt(2, Character.toUpperCase(c));
+ }
+ else
+ {
+ sb.setCharAt(2, Character.toLowerCase(c));
+ }
+
+ setMethod(getIntrospector().getMethod(clazz, sb.toString(), params));
+ }
+
+ if (isAlive())
+ {
+ if( getMethod().getReturnType() != Boolean.TYPE &&
+ getMethod().getReturnType() != Boolean.class )
+ {
+ setMethod(null);
+ }
+ }
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for boolean property getter for '" + property;
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java
new file mode 100644
index 00000000..e08d2c69
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java
@@ -0,0 +1,120 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * Executor that simply tries to execute a get(key)
+ * operation. This will try to find a get(key) method
+ * for any type of object, not just objects that
+ * implement the Map interface as was previously
+ * the case.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class GetExecutor extends AbstractExecutor
+{
+ private final Introspector introspector;
+
+ // This is still threadsafe because this object is only read except in the C'tor.
+ private Object [] params = {};
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @since 1.5
+ */
+ public GetExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property)
+ {
+ this.log = log;
+ this.introspector = introspector;
+
+ // If you passed in null as property, we don't use the value
+ // for parameter lookup. Instead we just look for get() without
+ // any parameters.
+ //
+ // In any other case, the following condition will set up an array
+ // for looking up get(String) on the class.
+
+ if (property != null)
+ {
+ this.params = new Object[] { property };
+ }
+ discover(clazz);
+ }
+
+ /**
+ * @param clazz
+ * @since 1.5
+ */
+ protected void discover(final Class<?> clazz)
+ {
+ try
+ {
+ setMethod(introspector.getMethod(clazz, "get", params));
+ /* get(Number) or get(integral) are NOT admissible,
+ * as the key is a string
+ */
+ if (getMethod() != null)
+ {
+ Class<?> [] args = getMethod().getParameterTypes();
+ if (args.length == 1 && (args[0].isPrimitive() || Number.class.isAssignableFrom(args[0])))
+ {
+ setMethod(null);
+ }
+ }
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for get('" + params[0] + "') method";
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+
+ /**
+ * @param o
+ * @return value
+ * @see org.apache.velocity.runtime.parser.node.AbstractExecutor#execute(java.lang.Object)
+ */
+ @Override
+ public Object execute(final Object o)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ return isAlive() ? getMethod().invoke(o, params) : null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java
new file mode 100644
index 00000000..907aaecb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java
@@ -0,0 +1,362 @@
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.runtime.directive.Directive;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to fix indentation in structured mode.
+ */
+
+public class IndentationFixer implements ParserVisitor
+{
+ protected String parentIndentation = null;
+ protected String extraIndentation = null;
+ protected Pattern fix = null;
+
+ protected void fillExtraIndentation(String prefix)
+ {
+ Pattern captureExtraIndentation = Pattern.compile("^" + parentIndentation + "(\\s+)");
+ Matcher matcher = captureExtraIndentation.matcher(prefix);
+ if (matcher.find())
+ {
+ extraIndentation = matcher.group(1);
+ fix = Pattern.compile("^" + parentIndentation + extraIndentation, Pattern.MULTILINE);
+ }
+ else
+ {
+ extraIndentation = "";
+ }
+ }
+
+ public IndentationFixer(String parentIndentation)
+ {
+ this.parentIndentation = parentIndentation;
+ }
+
+ @Override
+ public Object visit(SimpleNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTprocess node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTText node, Object data)
+ {
+ String text = node.getCtext();
+ if (extraIndentation == null)
+ {
+ fillExtraIndentation(text);
+ }
+ if (extraIndentation.length() > 0)
+ {
+ Matcher matcher = fix.matcher(text);
+ node.setCtext(matcher.replaceAll(parentIndentation));
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTEscapedDirective node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTEscape node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTComment node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTTextblock node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTFloatingPointLiteral node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTIntegerLiteral node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTStringLiteral node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTIdentifier node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTWord node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTDirectiveAssign node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTDirective node, Object data)
+ {
+ String prefix = node.getPrefix();
+ if (prefix.length() > 0)
+ {
+ if (extraIndentation == null)
+ {
+ fillExtraIndentation(prefix);
+ }
+ if (extraIndentation.length() > 0)
+ {
+ Matcher matcher = fix.matcher(prefix);
+ node.setPrefix(matcher.replaceAll(parentIndentation));
+ if (node.getDirectiveType() == Directive.BLOCK)
+ {
+ node.childrenAccept(this, null);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTBlock node, Object data)
+ {
+ String prefix = node.getPrefix();
+ if (prefix.length() > 0)
+ {
+ node.childrenAccept(this, null);
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTMap node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTObjectArray node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTIntegerRange node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTMethod node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTIndex node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTReference node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTTrue node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTFalse node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTIfStatement node, Object data)
+ {
+ String prefix = node.getPrefix();
+ if (prefix.length() > 0)
+ {
+ if (extraIndentation == null)
+ {
+ fillExtraIndentation(prefix);
+ }
+ if (extraIndentation.length() > 0)
+ {
+ Matcher matcher = fix.matcher(prefix);
+ node.setPrefix(matcher.replaceAll(parentIndentation));
+ node.childrenAccept(this, null);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTElseStatement node, Object data)
+ {
+ if (extraIndentation != null && extraIndentation.length() > 0)
+ {
+ node.childrenAccept(this, null);
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTElseIfStatement node, Object data)
+ {
+ if (extraIndentation != null && extraIndentation.length() > 0)
+ {
+ node.childrenAccept(this, null);
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTSetDirective node, Object data)
+ {
+ String prefix = node.getPrefix();
+ if (prefix.length() > 0)
+ {
+ if (extraIndentation == null)
+ {
+ fillExtraIndentation(prefix);
+ }
+ if (extraIndentation.length() > 0)
+ {
+ Matcher matcher = fix.matcher(prefix);
+ node.setPrefix(matcher.replaceAll(parentIndentation));
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTExpression node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTAssignment node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTOrNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTAndNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTEQNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTNENode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTLTNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTGTNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTLENode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTGENode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTAddNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTSubtractNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTMulNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTDivNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTModNode node, Object data)
+ {
+ return null;
+ }
+
+ @Override
+ public Object visit(ASTNotNode node, Object data)
+ {
+ return null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java
new file mode 100644
index 00000000..9f84bfb0
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java
@@ -0,0 +1,5 @@
+package org.apache.velocity.runtime.parser.node;
+
+public class JJTParserState extends JJTStandardParserState
+{
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java
new file mode 100644
index 00000000..2c9aaea5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java
@@ -0,0 +1,105 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.slf4j.Logger;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * GetExecutor that is smart about Maps. If it detects one, it does not
+ * use Reflection but a cast to access the getter.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class MapGetExecutor
+ extends AbstractExecutor
+{
+ private final String property;
+ private final boolean isAlive;
+
+ public MapGetExecutor(final Logger log, final Object object, final String property)
+ {
+ this.log = log;
+ this.property = property;
+ isAlive = discover(object);
+ }
+
+ @Override
+ public Method getMethod()
+ {
+ if (isAlive())
+ {
+ return MapGetMethod.instance();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isAlive()
+ {
+ return isAlive;
+ }
+
+ protected boolean discover (final Object object)
+ {
+ if (object instanceof Map)
+ {
+ if (property != null)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Object execute(final Object o)
+ {
+ return ((Map) o).get(property);
+ }
+
+ private static final class MapGetMethod
+ {
+ private static final Method instance;
+
+ static
+ {
+ try
+ {
+ instance = Map.class.getMethod("get", Object.class);
+ }
+ catch (final NoSuchMethodException mapGetMethodMissingError)
+ {
+ throw new Error(mapGetMethodMissingError);
+ }
+ }
+
+ private MapGetMethod() { }
+
+ static Method instance()
+ {
+ return instance;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java
new file mode 100644
index 00000000..3dd3301a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java
@@ -0,0 +1,84 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.exception.VelocityException;
+import org.slf4j.Logger;
+
+import java.util.Map;
+
+/**
+ * SetExecutor that is smart about Maps. If it detects one, it does not
+ * use Reflection but a cast to access the setter.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class MapSetExecutor
+ extends SetExecutor
+{
+ private final String property;
+
+ public MapSetExecutor(final Logger log, final Class clazz, final String property)
+ {
+ this.log = log;
+ this.property = property;
+ discover(clazz);
+ }
+
+ protected void discover (final Class<?> clazz)
+ {
+ Class<?> [] interfaces = clazz.getInterfaces();
+ for (Class<?> anInterface : interfaces)
+ {
+ if (anInterface.equals(Map.class))
+ {
+ try
+ {
+ if (property != null)
+ {
+ setMethod(Map.class.getMethod("put", Object.class, Object.class));
+ }
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ String msg = "Exception while looking for put('" + property + "') method";
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public Object execute(final Object o, final Object arg)
+ {
+ return ((Map) o).put(property, arg);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java
new file mode 100644
index 00000000..52cedccb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java
@@ -0,0 +1,539 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility-class for all arithmetic-operations.<br><br>
+ *
+ * All operations (+ - / *) return a Number which type is the type of the bigger argument.<br>
+ * Example:<br>
+ * <code>add ( new Integer(10), new Integer(1))</code> will return an <code>Integer</code>-Object with the value 11<br>
+ * <code>add ( new Long(10), new Integer(1))</code> will return an <code>Long</code>-Object with the value 11<br>
+ * <code>add ( new Integer(10), new Float(1))</code> will return an <code>Float</code>-Object with the value 11<br><br>
+ *
+ * Overflow checking:<br>
+ * For integral values (byte, short, int) there is an implicit overflow correction (the next "bigger"
+ * type will be returned). For example, if you call <code>add (new Integer (Integer.MAX_VALUE), 1)</code> a
+ * <code>Long</code>-object will be returned with the correct value of <code>Integer.MAX_VALUE+1</code>.<br>
+ * In addition to that the methods <code>multiply</code>,<code>add</code> and <code>substract</code> implement overflow
+ * checks for <code>long</code>-values. That means that if an overflow occurs while working with long values a BigInteger
+ * will be returned.<br>
+ * For all other operations and types (such as Float and Double) there is no overflow checking.
+ *
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ * @since 1.5
+ */
+public abstract class MathUtils
+{
+
+ /**
+ * A BigDecimal representing the number 0
+ */
+ protected static final BigDecimal DECIMAL_ZERO = new BigDecimal ( BigInteger.ZERO );
+
+ /**
+ * The constants are used to determine in which context we have to calculate.
+ */
+ protected static final int BASE_LONG = 0;
+ protected static final int BASE_FLOAT = 1;
+ protected static final int BASE_DOUBLE = 2;
+ protected static final int BASE_BIGINTEGER = 3;
+ protected static final int BASE_BIGDECIMAL = 4;
+
+ /**
+ * The <code>Class</code>-object is key, the maximum-value is the value
+ */
+ private static final Map<Class<? extends Number>, BigDecimal> ints = new HashMap<>();
+ static
+ {
+ ints.put (Byte.class, BigDecimal.valueOf (Byte.MAX_VALUE));
+ ints.put (Short.class, BigDecimal.valueOf (Short.MAX_VALUE));
+ ints.put (Integer.class, BigDecimal.valueOf (Integer.MAX_VALUE));
+ ints.put (Long.class, BigDecimal.valueOf (Long.MAX_VALUE));
+ ints.put (BigInteger.class, BigDecimal.valueOf (-1));
+ }
+
+ /**
+ * The "size" of the number-types - ascending.
+ */
+ private static final List<Class<? extends Number>> typesBySize = new ArrayList<>();
+ static
+ {
+ typesBySize.add (Byte.class);
+ typesBySize.add (Short.class);
+ typesBySize.add (Integer.class);
+ typesBySize.add (Long.class);
+ typesBySize.add (Float.class);
+ typesBySize.add (Double.class);
+ }
+
+ /**
+ * Convert the given Number to a BigDecimal
+ * @param n
+ * @return The number as BigDecimal
+ */
+ public static BigDecimal toBigDecimal (Number n)
+ {
+
+ if (n instanceof BigDecimal)
+ {
+ return (BigDecimal)n;
+ }
+
+ if (n instanceof BigInteger)
+ {
+ return new BigDecimal ( (BigInteger)n );
+ }
+
+ return BigDecimal.valueOf(n.doubleValue());
+
+ }
+
+ /**
+ * Convert the given Number to a BigInteger
+ * @param n
+ * @return The number as BigInteger
+ */
+ public static BigInteger toBigInteger (Number n)
+ {
+
+ if (n instanceof BigInteger)
+ {
+ return (BigInteger)n;
+ }
+
+ return BigInteger.valueOf (n.longValue());
+
+ }
+
+ /**
+ * Compare the given Number to 0.
+ * @param n
+ * @return True if number is 0.
+ */
+ public static boolean isZero (Number n)
+ {
+ if (isInteger( n ) )
+ {
+ if (n instanceof BigInteger)
+ {
+ return ((BigInteger)n).compareTo (BigInteger.ZERO) == 0;
+ }
+ return n.doubleValue() == 0;
+ }
+ if (n instanceof Float)
+ {
+ return n.floatValue() == 0f;
+ }
+ if (n instanceof Double)
+ {
+ return n.doubleValue() == 0d;
+ }
+ return toBigDecimal( n ).compareTo( DECIMAL_ZERO) == 0;
+ }
+
+ /**
+ * Test, whether the given object is an integer value
+ * (Byte, Short, Integer, Long, BigInteger)
+ * @param n
+ * @return True if n is an integer.
+ */
+ public static boolean isInteger (Number n)
+ {
+ return ints.containsKey (n.getClass());
+ }
+
+ /**
+ * Wrap the given primitive into the given class if the value is in the
+ * range of the destination type. If not the next bigger type will be chosen.
+ * @param value
+ * @param type
+ * @return Number object representing the primitive.
+ */
+ public static Number wrapPrimitive (long value, Class<?> type)
+ {
+ if (type == Byte.class)
+ {
+ if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE)
+ {
+ type = Short.class;
+ }
+ else
+ {
+ return (byte) value;
+ }
+ }
+ if (type == Short.class)
+ {
+ if (value > Short.MAX_VALUE || value < Short.MIN_VALUE)
+ {
+ type = Integer.class;
+ }
+ else
+ {
+ return (short) value;
+ }
+ }
+ if (type == Integer.class)
+ {
+ if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE)
+ {
+ type = Long.class;
+ }
+ else
+ {
+ return (int) value;
+ }
+ }
+ if (type == Long.class)
+ {
+ return value;
+ }
+ return BigInteger.valueOf(value);
+ }
+
+ /**
+ * Wrap the result in the object of the bigger type.
+ *
+ * @param value result of operation (as a long) - used to check size
+ * @param op1 first operand of binary operation
+ * @param op2 second operand of binary operation
+ * @return Number object of appropriate size to fit the value and operators
+ */
+ private static Number wrapPrimitive (long value, Number op1, Number op2)
+ {
+ if ( typesBySize.indexOf( op1.getClass()) > typesBySize.indexOf( op2.getClass()))
+ {
+ return wrapPrimitive(value, op1.getClass());
+ }
+ return wrapPrimitive(value, op2.getClass());
+ }
+
+ /**
+ * Find the common Number-type to be used in calculations.
+ *
+ * @param op1 first operand of binary operation
+ * @param op2 second operand of binary operation
+ * @return constant indicating type of Number to use in calculations
+ */
+ private static int findCalculationBase (Number op1, Number op2)
+ {
+
+ boolean op1Int = isInteger(op1);
+ boolean op2Int = isInteger(op2);
+
+ if ( (op1 instanceof BigDecimal || op2 instanceof BigDecimal) ||
+ ( (!op1Int || !op2Int) && (op1 instanceof BigInteger || op2 instanceof BigInteger)) )
+ {
+ return BASE_BIGDECIMAL;
+ }
+
+ if (op1Int && op2Int) {
+ if (op1 instanceof BigInteger || op2 instanceof BigInteger)
+ {
+ return BASE_BIGINTEGER;
+ }
+ return BASE_LONG;
+ }
+
+ if ((op1 instanceof Double) || (op2 instanceof Double))
+ {
+ return BASE_DOUBLE;
+ }
+ return BASE_FLOAT;
+ }
+
+ /**
+ * Find the Number-type to be used for a single number
+ *
+ * @param op operand
+ * @return constant indicating type of Number to use in calculations
+ */
+ public static int findCalculationBase(Number op)
+ {
+ if (isInteger(op))
+ {
+ if (op instanceof BigInteger)
+ {
+ return BASE_BIGINTEGER;
+ }
+ return BASE_LONG;
+ } else if (op instanceof BigDecimal)
+ {
+ return BASE_BIGDECIMAL;
+ } else if (op instanceof Double)
+ {
+ return BASE_DOUBLE;
+ }
+ return BASE_FLOAT;
+ }
+
+ /**
+ * Add two numbers and return the correct value / type.
+ * Overflow detection is done for integer values (byte, short, int, long) only!
+ * @param op1
+ * @param op2
+ * @return Addition result.
+ */
+ public static Number add (Number op1, Number op2)
+ {
+
+ int calcBase = findCalculationBase( op1, op2);
+ switch (calcBase)
+ {
+ case BASE_BIGINTEGER:
+ return toBigInteger( op1 ).add( toBigInteger( op2 ));
+ case BASE_LONG:
+ long l1 = op1.longValue();
+ long l2 = op2.longValue();
+ long result = l1+l2;
+
+ // Overflow check
+ if ((result ^ l1) < 0 && (result ^ l2) < 0)
+ {
+ return toBigInteger( op1).add( toBigInteger( op2));
+ }
+ return wrapPrimitive( result, op1, op2);
+ case BASE_FLOAT:
+ return op1.floatValue() + op2.floatValue();
+ case BASE_DOUBLE:
+ return op1.doubleValue() + op2.doubleValue();
+
+ // Default is BigDecimal operation
+ default:
+ return toBigDecimal( op1 ).add( toBigDecimal( op2 ));
+ }
+ }
+
+ /**
+ * Subtract two numbers and return the correct value / type.
+ * Overflow detection is done for integer values (byte, short, int, long) only!
+ * @param op1
+ * @param op2
+ * @return Subtraction result.
+ */
+ public static Number subtract (Number op1, Number op2) {
+
+ int calcBase = findCalculationBase( op1, op2);
+ switch (calcBase) {
+ case BASE_BIGINTEGER:
+ return toBigInteger( op1 ).subtract( toBigInteger( op2 ));
+ case BASE_LONG:
+ long l1 = op1.longValue();
+ long l2 = op2.longValue();
+ long result = l1-l2;
+
+ // Overflow check
+ if ((result ^ l1) < 0 && (result ^ ~l2) < 0) {
+ return toBigInteger( op1).subtract( toBigInteger( op2));
+ }
+ return wrapPrimitive( result, op1, op2);
+ case BASE_FLOAT:
+ return op1.floatValue() - op2.floatValue();
+ case BASE_DOUBLE:
+ return op1.doubleValue() - op2.doubleValue();
+
+ // Default is BigDecimal operation
+ default:
+ return toBigDecimal( op1 ).subtract( toBigDecimal( op2 ));
+ }
+ }
+
+ /**
+ * Multiply two numbers and return the correct value / type.
+ * Overflow detection is done for integer values (byte, short, int, long) only!
+ * @param op1
+ * @param op2
+ * @return Multiplication result.
+ */
+ public static Number multiply (Number op1, Number op2) {
+
+ int calcBase = findCalculationBase( op1, op2);
+ switch (calcBase) {
+ case BASE_BIGINTEGER:
+ return toBigInteger( op1 ).multiply( toBigInteger( op2 ));
+ case BASE_LONG:
+ long l1 = op1.longValue();
+ long l2 = op2.longValue();
+ long result = l1*l2;
+
+ // Overflow detection
+ if ((l2 != 0) && (result / l2 != l1)) {
+ return toBigInteger( op1).multiply( toBigInteger( op2));
+ }
+ return wrapPrimitive( result, op1, op2);
+ case BASE_FLOAT:
+ return op1.floatValue() * op2.floatValue();
+ case BASE_DOUBLE:
+ return op1.doubleValue() * op2.doubleValue();
+
+ // Default is BigDecimal operation
+ default:
+ return toBigDecimal( op1 ).multiply( toBigDecimal( op2 ));
+ }
+ }
+
+ /**
+ * Divide two numbers. The result will be returned as Integer-type if and only if
+ * both sides of the division operator are Integer-types. Otherwise a Float, Double,
+ * or BigDecimal will be returned.
+ * @param op1
+ * @param op2
+ * @return Division result.
+ */
+ public static Number divide (Number op1, Number op2) {
+
+ int calcBase = findCalculationBase( op1, op2);
+ switch (calcBase) {
+ case BASE_BIGINTEGER:
+ BigInteger b1 = toBigInteger( op1 );
+ BigInteger b2 = toBigInteger( op2 );
+ return b1.divide( b2);
+
+ case BASE_LONG:
+ long l1 = op1.longValue();
+ long l2 = op2.longValue();
+ return wrapPrimitive( l1 / l2, op1, op2);
+
+ case BASE_FLOAT:
+ return op1.floatValue() / op2.floatValue();
+ case BASE_DOUBLE:
+ return op1.doubleValue() / op2.doubleValue();
+
+ // Default is BigDecimal operation
+ default:
+ return toBigDecimal( op1 ).divide( toBigDecimal( op2 ), BigDecimal.ROUND_HALF_DOWN);
+ }
+ }
+
+ /**
+ * Modulo two numbers.
+ * @param op1
+ * @param op2
+ * @return Modulo result.
+ *
+ * @throws ArithmeticException If at least one parameter is a BigDecimal
+ */
+ public static Number modulo (Number op1, Number op2) throws ArithmeticException {
+
+ int calcBase = findCalculationBase( op1, op2);
+ switch (calcBase) {
+ case BASE_BIGINTEGER:
+ return toBigInteger( op1 ).mod( toBigInteger( op2 ));
+ case BASE_LONG:
+ return wrapPrimitive( op1.longValue() % op2.longValue(), op1, op2);
+ case BASE_FLOAT:
+ return op1.floatValue() % op2.floatValue();
+ case BASE_DOUBLE:
+ return op1.doubleValue() % op2.doubleValue();
+
+ // Default is BigDecimal operation
+ default:
+ throw new ArithmeticException( "Cannot calculate the modulo of BigDecimals.");
+ }
+ }
+
+ /**
+ * Compare two numbers.
+ * @param op1
+ * @param op2
+ * @return 1 if n1 &gt; n2, -1 if n1 &lt; n2 and 0 if equal.
+ */
+ public static int compare (Number op1, Number op2) {
+
+ int calcBase = findCalculationBase( op1, op2);
+ switch (calcBase) {
+ case BASE_BIGINTEGER:
+ return toBigInteger( op1 ).compareTo( toBigInteger( op2 ));
+ case BASE_LONG:
+ long l1 = op1.longValue();
+ long l2 = op2.longValue();
+ if (l1 < l2) {
+ return -1;
+ }
+ if (l1 > l2) {
+ return 1;
+ }
+ return 0;
+ case BASE_FLOAT:
+ float f1 = op1.floatValue();
+ float f2 = op2.floatValue();
+ if (f1 < f2) {
+ return -1;
+ }
+ if (f1 > f2) {
+ return 1;
+ }
+ return 0;
+ case BASE_DOUBLE:
+ double d1 = op1.doubleValue();
+ double d2 = op2.doubleValue();
+ if (d1 < d2) {
+ return -1;
+ }
+ if (d1 > d2) {
+ return 1;
+ }
+ return 0;
+
+ // Default is BigDecimal operation
+ default:
+ return toBigDecimal( op1 ).compareTo( toBigDecimal ( op2 ));
+ }
+ }
+
+ /**
+ * Negate a number
+ * @param op n
+ * @return -n (unary negation of n)
+ */
+ public static Number negate(Number op)
+ {
+ int calcBase = findCalculationBase( op);
+ switch (calcBase) {
+ case BASE_BIGINTEGER:
+ return toBigInteger(op).negate();
+ case BASE_LONG:
+ long l = op.longValue();
+ /* overflow check */
+ if (l == Long.MIN_VALUE)
+ {
+ return toBigInteger(l).negate();
+ }
+ return wrapPrimitive(-l, op.getClass());
+ case BASE_FLOAT:
+ float f = op.floatValue();
+ return -f;
+ case BASE_DOUBLE:
+ double d = op.doubleValue();
+ return -d;
+ // Default is BigDecimal operation
+ default:
+ return toBigDecimal(op).negate();
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java
new file mode 100644
index 00000000..ff867a15
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java
@@ -0,0 +1,232 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.Template;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.Renderable;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This file describes the interface between the Velocity code
+ * and the JavaCC generated code.
+ *
+ * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+
+public interface Node extends Renderable
+{
+ /** This method is called after the node has been made the current
+ * node. It indicates that child nodes can now be added to it. */
+ void jjtOpen();
+
+ /** This method is called after all the child nodes have been
+ added.
+ */
+ void jjtClose();
+
+ /**
+ * This pair of methods are used to inform the node of its
+ * parent.
+ * @param n
+ *
+ */
+ void jjtSetParent(Node n);
+
+ /**
+ * @return The node parent.
+ */
+ Node jjtGetParent();
+
+ /**
+ * This method tells the node to add its argument to the node's
+ * list of children.
+ * @param n
+ * @param i
+ */
+ void jjtAddChild(Node n, int i);
+
+ /**
+ * This method returns a child node. The children are numbered
+ * from zero, left to right.
+ * @param i
+ * @return A child node.
+ */
+ Node jjtGetChild(int i);
+
+ /**
+ * Return the number of children the node has.
+ * @return The number of children of this node.
+ */
+ int jjtGetNumChildren();
+
+ /**
+ * @param visitor
+ * @param data
+ * @return The Node execution result object.
+ */
+ Object jjtAccept(ParserVisitor visitor, Object data);
+
+ /*
+ * ========================================================================
+ *
+ * The following methods are not generated automatically be the Parser but
+ * added manually to be used by Velocity.
+ *
+ * ========================================================================
+ */
+
+ /**
+ * @see #jjtAccept(ParserVisitor, Object)
+ * @param visitor
+ * @param data
+ * @return The node execution result.
+ */
+ Object childrenAccept(ParserVisitor visitor, Object data);
+
+ /**
+ * @return The first token.
+ */
+ Token getFirstToken();
+ /**
+ * @return The last token.
+ */
+ Token getLastToken();
+ /**
+ * @return The NodeType.
+ */
+ int getType();
+
+ /**
+ * @param context
+ * @param data
+ * @return The init result.
+ * @throws TemplateInitException
+ */
+ Object init(InternalContextAdapter context, Object data) throws TemplateInitException;
+
+ /**
+ * @param context
+ * @return The evaluation result.
+ * @throws MethodInvocationException
+ */
+ boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException;
+
+ /**
+ * @param context
+ * @return The node value.
+ * @throws MethodInvocationException
+ */
+ Object value(InternalContextAdapter context)
+ throws MethodInvocationException;
+
+ /**
+ * @param context
+ * @param writer
+ * @return True if the node rendered successfully.
+ * @throws IOException
+ * @throws MethodInvocationException
+ * @throws ParseErrorException
+ * @throws ResourceNotFoundException
+ */
+ @Override
+ boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException,MethodInvocationException, ParseErrorException, ResourceNotFoundException;
+
+ /**
+ * @param o
+ * @param context
+ * @return The execution result.
+ * @throws MethodInvocationException
+ */
+ Object execute(Object o, InternalContextAdapter context)
+ throws MethodInvocationException;
+
+ /**
+ * @param info
+ */
+ void setInfo(int info);
+
+ /**
+ * @return The current node info.
+ */
+ int getInfo();
+
+ /**
+ * @return A literal.
+ */
+ String literal();
+
+ /**
+ * Mark the node as invalid.
+ */
+ void setInvalid();
+
+ /**
+ * @return True if the node is invalid.
+ */
+ boolean isInvalid();
+
+ /**
+ * @return The current line position.
+ */
+ int getLine();
+
+ /**
+ * @return The current column position.
+ */
+ int getColumn();
+
+ /**
+ * @return the file name of the template
+ */
+ String getTemplateName();
+
+ /**
+ * @return cached image (String) of the first Token for this Node returned by the Parser
+ */
+ String getFirstTokenImage();
+
+ /**
+ * @return cached image (String) of the last Token for this Node returned by the Parser
+ */
+ String getLastTokenImage();
+
+ /**
+ * @return the template this node belongs to
+ */
+ Template getTemplate();
+
+ /**
+ * @return the parser which generated this node
+ * @since 2.2
+ */
+ Parser getParser();
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java
new file mode 100644
index 00000000..84b735bd
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java
@@ -0,0 +1,162 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.StandardParserConstants;
+import org.apache.velocity.runtime.parser.Token;
+
+/**
+ * Utilities for dealing with the AST node structure.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class NodeUtils
+{
+ /**
+ * Collect all the &lt;SPECIAL_TOKEN&gt;s that
+ * are carried along with a token. Special
+ * tokens do not participate in parsing but
+ * can still trigger certain lexical actions.
+ * In some cases you may want to retrieve these
+ * special tokens, this is simply a way to
+ * extract them.
+ * @param t the Token
+ * @return StrBuilder with the special tokens.
+ * @since 2.0.0
+ */
+ public static StringBuilder getSpecialText(Parser parser, Token t)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ Token tmp_t = t.specialToken;
+
+ while (tmp_t.specialToken != null)
+ {
+ tmp_t = tmp_t.specialToken;
+ }
+
+ while (tmp_t != null)
+ {
+ String st = tmp_t.image;
+
+ for(int i = 0, is = st.length(); i < is; i++)
+ {
+ char c = st.charAt(i);
+
+ if ( c == parser.hash() || c == parser.dollar() )
+ {
+ sb.append( c );
+ }
+
+ /*
+ * more dreaded MORE hack :)
+ *
+ * looking for ("\\")*"$" sequences
+ */
+
+ if ( c == '\\')
+ {
+ boolean ok = true;
+ boolean term = false;
+
+ int j = i;
+ for( ok = true; ok && j < is; j++)
+ {
+ char cc = st.charAt( j );
+
+ if (cc == '\\')
+ {
+ /*
+ * if we see a \, keep going
+ */
+ continue;
+ }
+ else if( cc == parser.dollar() )
+ {
+ /*
+ * a $ ends it correctly
+ */
+ term = true;
+ ok = false;
+ }
+ else
+ {
+ /*
+ * nah...
+ */
+ ok = false;
+ }
+ }
+
+ if (term)
+ {
+ String foo = st.substring( i, j );
+ sb.append( foo );
+ i = j;
+ }
+ }
+ }
+
+ tmp_t = tmp_t.next;
+ }
+ return sb;
+ }
+
+ /**
+ * complete node literal
+ * @param t
+ * @return A node literal.
+ */
+ public static String tokenLiteral( Parser parser, Token t )
+ {
+ // Look at kind of token and return "" when it's a block comment
+ if (t.kind == StandardParserConstants.MULTI_LINE_COMMENT)
+ {
+ return "";
+ }
+ else if (t.specialToken == null || t.specialToken.image.startsWith(parser.lineComment()))
+ {
+ return t.image;
+ }
+ else
+ {
+ StringBuilder special = getSpecialText(parser, t);
+ if (special.length() > 0)
+ {
+ return special.append(t.image).toString();
+ }
+ return t.image;
+ }
+ }
+
+ /**
+ * Fix children indentation in structured space gobbling mode.
+ * @param parent
+ * @param parentIndentation
+ */
+ public static void fixIndentation(SimpleNode parent, String parentIndentation)
+ {
+ IndentationFixer fixer = new IndentationFixer(parentIndentation);
+ parent.childrenAccept(fixer, null);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java
new file mode 100644
index 00000000..7cc1dc53
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java
@@ -0,0 +1,24 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+public interface ParserTreeConstants extends StandardParserTreeConstants
+{
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java
new file mode 100644
index 00000000..8fe14fe6
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java
@@ -0,0 +1,332 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Interface used in implementation of visitor pattern. Based on
+ * code autogenerated by JavaCC. Formerly found in package
+ * org.apache.velocity.runtime.parser.
+ *
+ * @version $Id$
+ * @since 1.5
+ */
+public interface ParserVisitor
+{
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(SimpleNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+
+ Object visit(ASTprocess node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTText node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTEscapedDirective node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTEscape node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTComment node, Object data);
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTTextblock node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTFloatingPointLiteral node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTIntegerLiteral node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTStringLiteral node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTIdentifier node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTWord node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+
+ Object visit(ASTDirectiveAssign node, Object data);
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTDirective node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTBlock node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTMap node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTObjectArray node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTIntegerRange node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTMethod node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTIndex node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTReference node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTTrue node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTFalse node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTIfStatement node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTElseStatement node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTElseIfStatement node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTSetDirective node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTExpression node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTAssignment node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTOrNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTAndNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTEQNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTNENode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTLTNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTGTNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTLENode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTGENode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTAddNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTSubtractNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTMulNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTDivNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTModNode node, Object data);
+
+ /**
+ * @param node
+ * @param data
+ * @return The object rendered by this node.
+ */
+ Object visit(ASTNotNode node, Object data);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java
new file mode 100644
index 00000000..7763724c
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java
@@ -0,0 +1,151 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.ArrayListWrapper;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Returned the value of object property when executed.
+ */
+public class PropertyExecutor extends AbstractExecutor
+{
+ private final Introspector introspector;
+ private final boolean wrapArray;
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @since 1.5
+ */
+ public PropertyExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property)
+ {
+ this(log, introspector, clazz, property, false);
+ }
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @param wrapArray
+ * @since 1.5
+ */
+ public PropertyExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property, final boolean wrapArray)
+ {
+ this.log = log;
+ this.introspector = introspector;
+ this.wrapArray = wrapArray;
+
+ // Don't allow passing in the empty string or null because
+ // it will either fail with a StringIndexOutOfBounds error
+ // or the introspector will get confused.
+ if (StringUtils.isNotEmpty(property))
+ {
+ discover(clazz, property);
+ }
+ }
+
+ /**
+ * @return The current introspector.
+ * @since 1.5
+ */
+ protected Introspector getIntrospector()
+ {
+ return this.introspector;
+ }
+
+ /**
+ * @param clazz
+ * @param property
+ */
+ protected void discover(final Class<?> clazz, final String property)
+ {
+ /*
+ * this is gross and linear, but it keeps it straightforward.
+ */
+
+ try
+ {
+ Object [] params = {};
+
+ StringBuilder sb = new StringBuilder("get");
+ sb.append(property);
+
+ setMethod(introspector.getMethod(clazz, sb.toString(), params));
+
+ if (!isAlive())
+ {
+ /*
+ * now the convenience, flip the 1st character
+ */
+
+ char c = sb.charAt(3);
+
+ if (Character.isLowerCase(c))
+ {
+ sb.setCharAt(3, Character.toUpperCase(c));
+ }
+ else
+ {
+ sb.setCharAt(3, Character.toLowerCase(c));
+ }
+
+ setMethod(introspector.getMethod(clazz, sb.toString(), params));
+ }
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for property getter for '" + property;
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.AbstractExecutor#execute(java.lang.Object)
+ */
+ @Override
+ public Object execute(Object o)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ if (wrapArray)
+ {
+ o = new ArrayListWrapper(o);
+ }
+ return isAlive() ? getMethod().invoke(o, ((Object []) null)) : null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java
new file mode 100644
index 00000000..643f9817
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java
@@ -0,0 +1,128 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Returns the value of a public field when executed.
+ */
+public class PublicFieldExecutor extends AbstractExecutor
+{
+ private final Introspector introspector;
+
+ /**
+ * Field to be accessed
+ */
+ private Field field = null;
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @since 1.5
+ */
+ public PublicFieldExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property)
+ {
+ this.log = log;
+ this.introspector = introspector;
+
+ // Don't allow passing in the empty string or null because
+ // it will either fail with a StringIndexOutOfBounds error
+ // or the introspector will get confused.
+ if (StringUtils.isNotEmpty(property))
+ {
+ discover(clazz, property);
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ return getField() != null;
+ }
+
+ /**
+ * @return The current field.
+ */
+ public Field getField()
+ {
+ return field;
+ }
+
+ /**
+ * @param field
+ */
+ protected void setField(final Field field)
+ {
+ this.field = field;
+ }
+
+ /**
+ * @return The current introspector.
+ * @since 1.5
+ */
+ protected Introspector getIntrospector()
+ {
+ return this.introspector;
+ }
+
+ /**
+ * @param clazz
+ * @param property
+ */
+ protected void discover(final Class<?> clazz, final String property)
+ {
+ try
+ {
+ setField(introspector.getField(clazz, property));
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for public field '" + property;
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.AbstractExecutor#execute(java.lang.Object)
+ */
+ @Override
+ public Object execute(Object o)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ return isAlive() ? getField().get(o) : null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java
new file mode 100644
index 00000000..09072615
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java
@@ -0,0 +1,133 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * Executor that simply tries to execute a put(key, value)
+ * operation. This will try to find a put(key) method
+ * for any type of object, not just objects that
+ * implement the Map interface as was previously
+ * the case.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class PutExecutor extends SetExecutor
+{
+ private final Introspector introspector;
+ private final String property;
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param arg
+ * @param property
+ */
+ public PutExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final Object arg, final String property)
+ {
+ this.log = log;
+ this.introspector = introspector;
+ this.property = property;
+
+ discover(clazz, arg);
+ }
+
+ /**
+ * @param clazz
+ * @param arg
+ */
+ protected void discover(final Class<?> clazz, final Object arg)
+ {
+ Object [] params;
+
+ // If you passed in null as property, we don't use the value
+ // for parameter lookup. Instead we just look for put(Object) without
+ // any parameters.
+ //
+ // In any other case, the following condition will set up an array
+ // for looking up put(String, Object) on the class.
+
+ if (property == null)
+ {
+ // The passed in arg object is used by the Cache to look up the method.
+ params = new Object[] { arg };
+ }
+ else
+ {
+ params = new Object[] { property, arg };
+ }
+
+ try
+ {
+ setMethod(introspector.getMethod(clazz, "put", params));
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for put('" + params[0] + "') method";
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.SetExecutor#execute(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public Object execute(final Object o, final Object value)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ Object [] params;
+
+ if (isAlive())
+ {
+ // If property != null, pass in the name for put(key, value). Else just put(value).
+ if (property == null)
+ {
+ params = new Object [] { value };
+ }
+ else
+ {
+ params = new Object [] { property, value };
+ }
+
+ return getMethod().invoke(o, params);
+ }
+
+ return null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java
new file mode 100644
index 00000000..461c92a8
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java
@@ -0,0 +1,87 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.slf4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Abstract class that is used to execute an arbitrary
+ * method that is in introspected. This is the superclass
+ * for the PutExecutor and SetPropertyExecutor.
+ *
+ * There really should be a superclass for this and AbstractExecutor (which should
+ * be refactored to GetExecutor) because they differ only in the execute() method.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public abstract class SetExecutor
+{
+ /** Class logger */
+ protected Logger log = null;
+
+ /**
+ * Method to be executed.
+ */
+ private Method method = null;
+
+ /**
+ * Execute method against context.
+ * @param o
+ * @param value
+ * @return The result of the invocation.
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ public abstract Object execute(Object o, Object value)
+ throws IllegalAccessException, InvocationTargetException;
+
+ /**
+ * Tell whether the executor is alive by looking
+ * at the value of the method.
+ * @return True if the executor is alive.
+ */
+ public boolean isAlive()
+ {
+ return (method != null);
+ }
+
+ /**
+ * @return The method to invoke.
+ */
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * @param method
+ */
+ protected void setMethod(final Method method)
+ {
+ this.method = method;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java
new file mode 100644
index 00000000..e02ca4e6
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java
@@ -0,0 +1,138 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Executor for looking up property names in the passed in class
+ * This will try to find a set&lt;foo&gt;(key, value) method
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class SetPropertyExecutor
+ extends SetExecutor
+{
+ private final Introspector introspector;
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @param arg
+ */
+ public SetPropertyExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property, final Object arg)
+ {
+ this.log = log;
+ this.introspector = introspector;
+
+ // Don't allow passing in the empty string or null because
+ // it will either fail with a StringIndexOutOfBounds error
+ // or the introspector will get confused.
+ if (StringUtils.isNotEmpty(property))
+ {
+ discover(clazz, property, arg);
+ }
+ }
+
+ /**
+ * @return The current introspector.
+ */
+ protected Introspector getIntrospector()
+ {
+ return this.introspector;
+ }
+
+ /**
+ * @param clazz
+ * @param property
+ * @param arg
+ */
+ protected void discover(final Class<?> clazz, final String property, final Object arg)
+ {
+ Object [] params = new Object [] { arg };
+
+ try
+ {
+ StringBuilder sb = new StringBuilder("set");
+ sb.append(property);
+
+ setMethod(introspector.getMethod(clazz, sb.toString(), params));
+
+ if (!isAlive())
+ {
+ /*
+ * now the convenience, flip the 1st character
+ */
+
+ char c = sb.charAt(3);
+
+ if (Character.isLowerCase(c))
+ {
+ sb.setCharAt(3, Character.toUpperCase(c));
+ }
+ else
+ {
+ sb.setCharAt(3, Character.toLowerCase(c));
+ }
+
+ setMethod(introspector.getMethod(clazz, sb.toString(), params));
+ }
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for property setter for '" + property;
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+
+ /**
+ * Execute method against context.
+ * @param o
+ * @param value
+ * @return The value of the invocation.
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ @Override
+ public Object execute(final Object o, final Object value)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ Object [] params = new Object [] { value };
+ return isAlive() ? getMethod().invoke(o, params) : null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java
new file mode 100644
index 00000000..7dead361
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java
@@ -0,0 +1,149 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.util.introspection.Introspector;
+import org.slf4j.Logger;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+
+/**
+ * Executor for setting public fields in objects
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
+ */
+public class SetPublicFieldExecutor
+ extends SetExecutor
+{
+ private final Introspector introspector;
+
+ /**
+ * Field to be accessed
+ */
+ private Field field = null;
+
+ /**
+ * @param log
+ * @param introspector
+ * @param clazz
+ * @param property
+ * @param arg
+ */
+ public SetPublicFieldExecutor(final Logger log, final Introspector introspector,
+ final Class<?> clazz, final String property, final Object arg)
+ {
+ this.log = log;
+ this.introspector = introspector;
+
+ // Don't allow passing in the empty string or null because
+ // it will either fail with a StringIndexOutOfBounds error
+ // or the introspector will get confused.
+ if (StringUtils.isNotEmpty(property))
+ {
+ discover(clazz, property, arg);
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ return getField() != null;
+ }
+
+ /**
+ * @return The current field.
+ */
+ public Field getField()
+ {
+ return field;
+ }
+
+ /**
+ * @param field
+ */
+ protected void setField(final Field field)
+ {
+ this.field = field;
+ }
+
+ /**
+ * @return The current introspector.
+ */
+ protected Introspector getIntrospector()
+ {
+ return this.introspector;
+ }
+
+ /**
+ * @param clazz
+ * @param property
+ * @param arg
+ */
+ protected void discover(final Class<?> clazz, final String property, final Object arg)
+ {
+ try
+ {
+ Field field = introspector.getField(clazz, property);
+ if(!Modifier.isFinal(field.getModifiers()))
+ {
+ setField(field);
+ }
+ }
+ /*
+ * pass through application level runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ String msg = "Exception while looking for public field '" + property;
+ log.error(msg, e);
+ throw new VelocityException(msg, e);
+ }
+ }
+
+ /**
+ * Execute method against context.
+ * @param o
+ * @param value
+ * @return The value of the invocation.
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ @Override
+ public Object execute(final Object o, final Object value)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ if (isAlive())
+ {
+ Object oldValue = getField().get(o);
+ getField().set(o, value);
+ return oldValue;
+ }
+ else
+ return null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java
new file mode 100644
index 00000000..f85d6926
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java
@@ -0,0 +1,650 @@
+package org.apache.velocity.runtime.parser.node;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.TemplateInitException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.Token;
+import org.apache.velocity.util.StringUtils;
+
+import org.slf4j.Logger;
+
+/**
+ *
+ */
+public class SimpleNode implements Node, Cloneable
+{
+ /** */
+ protected RuntimeServices rsvc = null;
+
+ /** */
+ protected Logger log = null;
+
+ /** */
+ protected Node parent;
+
+ /** */
+ protected Node[] children;
+
+ /** */
+ protected int id;
+
+ /** */
+ protected Parser parser;
+
+ /** */
+ protected int info;
+
+ /** */
+ public boolean state;
+
+ /** */
+ protected boolean invalid = false;
+
+ /** */
+ protected Token first;
+
+ /** */
+ protected Token last;
+
+ protected Template template;
+
+ /**
+ * For caching the literal value.
+ */
+ protected String literal = null;
+
+ /**
+ * Line number for this Node in the vm source file.
+ */
+
+ protected int line;
+
+ /**
+ * Column number for this Node in the vm source file.
+ */
+ protected int column;
+
+ /**
+ * String image variable of the first Token element that was parsed and connected to this Node.
+ */
+ protected String firstImage;
+
+ /**
+ * String image variable of the last Token element that was parsed and connected to this Node.
+ */
+ protected String lastImage;
+
+ public RuntimeServices getRuntimeServices()
+ {
+ return rsvc;
+ }
+
+ /**
+ * @param i
+ */
+ public SimpleNode(int i)
+ {
+ id = i;
+ }
+
+ /**
+ * @param p
+ * @param i
+ */
+ public SimpleNode(Parser p, int i)
+ {
+ this(i);
+ parser = p;
+ template = parser.getCurrentTemplate();
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtOpen()
+ */
+ @Override
+ public void jjtOpen()
+ {
+ first = parser.getToken(1); // added
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtClose()
+ */
+ @Override
+ public void jjtClose()
+ {
+ last = parser.getToken(0); // added
+ }
+
+ /**
+ * @param t
+ */
+ public void setFirstToken(Token t)
+ {
+ this.first = t;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#getFirstToken()
+ */
+ @Override
+ public Token getFirstToken()
+ {
+ return first;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#getLastToken()
+ */
+ @Override
+ public Token getLastToken()
+ {
+ return last;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtSetParent(org.apache.velocity.runtime.parser.node.Node)
+ */
+ @Override
+ public void jjtSetParent(Node n)
+ {
+ parent = n;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtGetParent()
+ */
+ @Override
+ public Node jjtGetParent()
+ {
+ return parent;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtAddChild(org.apache.velocity.runtime.parser.node.Node, int)
+ */
+ @Override
+ public void jjtAddChild(Node n, int i)
+ {
+ if (children == null)
+ {
+ children = new Node[i + 1];
+ }
+ else if (i >= children.length)
+ {
+ Node c[] = new Node[i + 1];
+ System.arraycopy(children, 0, c, 0, children.length);
+ children = c;
+ }
+ children[i] = n;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtGetChild(int)
+ */
+ @Override
+ public Node jjtGetChild(int i)
+ {
+ return children[i];
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtGetNumChildren()
+ */
+ @Override
+ public int jjtGetNumChildren()
+ {
+ return (children == null) ? 0 : children.length;
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data)
+ {
+ return visitor.visit(this, data);
+ }
+
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#childrenAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
+ */
+ @Override
+ public Object childrenAccept(ParserVisitor visitor, Object data)
+ {
+ if (children != null)
+ {
+ for (Node aChildren : children)
+ {
+ aChildren.jjtAccept(visitor, data);
+ }
+ }
+ return data;
+ }
+
+ /* You can override these two methods in subclasses of SimpleNode to
+ customize the way the node appears when the tree is dumped. If
+ your output uses more than one line you should override
+ toString(String), otherwise overriding toString() is probably all
+ you need to do. */
+ /**
+ * @param prefix display prefix
+ * @return String representation of this node.
+ */
+ public String toString(String prefix)
+ {
+ return prefix + "_" + toString();
+ }
+
+ /**
+ * <p>Dumps nodes tree on System.out.</p>
+ * <p>Override {@link #dump(String, PrintWriter)} if you want to customize
+ * how the node dumps out its children.
+ *
+ * @param prefix
+ */
+ public final void dump(String prefix)
+ {
+ dump(prefix, System.out);
+ }
+
+ /**
+ * <p>Dumps nodes tree on System.out.</p>
+ * <p>Override {@link #dump(String, PrintWriter)} if you want to customize
+ * how the node dumps out its children.
+ *
+ * @param prefix display prefix
+ * @param out output print stream
+ */
+ public final void dump(String prefix, PrintStream out)
+ {
+ Charset charset = null;
+ if (rsvc != null) /* may be null if node isn't yet initialized */
+ {
+ String encoding = rsvc.getString(RuntimeConstants.INPUT_ENCODING);
+ try
+ {
+ charset = Charset.forName(encoding);
+ }
+ catch (Exception e) {}
+ }
+ if (charset == null)
+ {
+ charset = Charset.defaultCharset();
+ }
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, charset));
+ dump(prefix, pw);
+ pw.flush();
+ }
+
+ /**
+ * <p>Dumps nodes tree on System.out.</p>
+ * <p>Override this method if you want to customize how the node dumps
+ * out its children.</p>
+ *
+ * @param prefix display prefix
+ * @param out output print writer
+ */
+ public void dump(String prefix, PrintWriter out)
+ {
+ out.println(toString());
+ if (children != null)
+ {
+ for (int i = 0; i < children.length; ++i)
+ {
+ SimpleNode n = (SimpleNode) children[i];
+ out.print(prefix + " |_");
+ if (n != null)
+ {
+ n.dump(prefix + ( i == children.length - 1 ? " " : " | " ), out);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a string that tells the current location of this node.
+ * @param context
+ * @return location
+ */
+ protected String getLocation(InternalContextAdapter context)
+ {
+ return StringUtils.formatFileString(this);
+ }
+
+ // All additional methods
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#literal()
+ */
+ @Override
+ public String literal()
+ {
+ if( literal != null )
+ {
+ return literal;
+ }
+
+ // if we have only one string, just return it and avoid
+ // buffer allocation. VELOCITY-606
+ if (first == last)
+ {
+ literal = NodeUtils.tokenLiteral(parser, first);
+ return literal;
+ }
+
+ Token t = first;
+ StringBuilder sb = new StringBuilder(NodeUtils.tokenLiteral(parser, t));
+ while (t != last)
+ {
+ t = t.next;
+ sb.append(NodeUtils.tokenLiteral(parser, t));
+ }
+ literal = sb.toString();
+ return literal;
+ }
+
+ /**
+ * @throws TemplateInitException
+ * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
+ */
+ @Override
+ public Object init(InternalContextAdapter context, Object data) throws TemplateInitException
+ {
+ /*
+ * hold onto the RuntimeServices
+ */
+
+ rsvc = (RuntimeServices) data;
+ log = rsvc.getLog("rendering");
+
+ int i, k = jjtGetNumChildren();
+
+ for (i = 0; i < k; i++)
+ {
+ jjtGetChild(i).init( context, data);
+ }
+
+ line = first.beginLine;
+ column = first.beginColumn;
+
+ return data;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#evaluate(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public boolean evaluate(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return false;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#value(org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object value(InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return null;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)
+ */
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer)
+ throws IOException, MethodInvocationException, ParseErrorException, ResourceNotFoundException
+ {
+ int i, k = jjtGetNumChildren();
+
+ for (i = 0; i < k; i++)
+ jjtGetChild(i).render(context, writer);
+
+ return true;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter)
+ */
+ @Override
+ public Object execute(Object o, InternalContextAdapter context)
+ throws MethodInvocationException
+ {
+ return null;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#getType()
+ */
+ @Override
+ public int getType()
+ {
+ return id;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#setInfo(int)
+ */
+ @Override
+ public void setInfo(int info)
+ {
+ this.info = info;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#getInfo()
+ */
+ @Override
+ public int getInfo()
+ {
+ return info;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#setInvalid()
+ */
+ @Override
+ public void setInvalid()
+ {
+ invalid = true;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#isInvalid()
+ */
+ @Override
+ public boolean isInvalid()
+ {
+ return invalid;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#getLine()
+ */
+ @Override
+ public int getLine()
+ {
+ return line;
+ }
+
+ /**
+ * @see org.apache.velocity.runtime.parser.node.Node#getColumn()
+ */
+ @Override
+ public int getColumn()
+ {
+ return column;
+ }
+
+ /**
+ * @since 1.5
+ */
+ public String toString()
+ {
+ StringBuilder tokens = new StringBuilder();
+
+ for (Token t = getFirstToken(); t != null; )
+ {
+ tokens.append("[").append(t.image.replace("\n", "\\n")).append("]");
+ if (t.next != null)
+ {
+ if (t.equals(getLastToken()))
+ {
+ break;
+ }
+ else
+ {
+ tokens.append(", ");
+ }
+ }
+ t = t.next;
+ }
+ String tok = tokens.toString();
+ if (tok.length() > 50) tok = tok.substring(0, 50) + "...";
+ return getClass().getSimpleName() + " [id=" + id + ", info=" + info + ", invalid="
+ + invalid
+ + ", tokens=" + tok + "]";
+ }
+
+ @Override
+ public String getTemplateName()
+ {
+ return template.getName();
+ }
+
+ /**
+ * Call before calling cleanupParserAndTokens() if you want to store image of
+ * the first and last token of this node.
+ */
+ public void saveTokenImages()
+ {
+ if( first != null )
+ {
+ this.firstImage = first.image;
+ }
+ if( last != null )
+ {
+ this.lastImage = last.image;
+ }
+ }
+
+ /**
+ * Removes references to Parser and Tokens since they are not needed anymore at this point.
+ *
+ * This allows us to save memory quite a bit.
+ */
+ public void cleanupParserAndTokens()
+ {
+ this.parser = null;
+ this.first = null;
+ this.last = null;
+ }
+
+ /**
+ * @return String image variable of the first Token element that was parsed and connected to this Node.
+ */
+ @Override
+ public String getFirstTokenImage()
+ {
+ return firstImage;
+ }
+
+ /**
+ * @return String image variable of the last Token element that was parsed and connected to this Node.
+ */
+ @Override
+ public String getLastTokenImage()
+ {
+ return lastImage;
+ }
+
+ @Override
+ public Template getTemplate() { return template; }
+
+ /**
+ * @return the parser which created this node
+ * @since 2.2
+ */
+ @Override
+ public Parser getParser()
+ {
+ return parser;
+ }
+
+ /**
+ * Root node deep cloning
+ * @param template owner template
+ * @return cloned node
+ * @throws CloneNotSupportedException
+ * @since 2.4
+ */
+ public Node clone(Template template) throws CloneNotSupportedException
+ {
+ if (parent != null) {
+ throw new IllegalStateException("cannot clone a child node without knowing its parent");
+ }
+ return clone(template, null);
+ }
+
+ /**
+ * Child node deep cloning
+ * @param template owner template
+ * @param parent parent node
+ * @return cloned node
+ * @throws CloneNotSupportedException
+ * @since 2.4
+ */
+ protected Node clone(Template template, Node parent) throws CloneNotSupportedException
+ {
+ SimpleNode clone = (SimpleNode)super.clone();
+ clone.template = template;
+ clone.parent = parent;
+ if (children != null)
+ {
+ clone.children = new SimpleNode[children.length];
+ for (int i = 0; i < children.length; ++i)
+ {
+ clone.children[i] = ((SimpleNode)children[i]).clone(template, clone);
+ }
+ }
+ return clone;
+ }
+}