diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser')
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: <result of getMessage> + * </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: <result of getMessage> + * </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 && right = false + * left && null = false + * null && 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 >= 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 > 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 <= 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 < 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 -> left + * null || right -> right + * null || null -> false + * left || right -> 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 -> 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<property> when executed. + * + * We do this separately as to preserve the current + * quasi-broken semantics of get<as is property> + * get<flip 1st char> get("property") and now followed + * by is<Property> + * + * @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 > n2, -1 if n1 < 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 <SPECIAL_TOKEN>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<foo>(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; + } +} |