blob: 3c9637bd8816d9c95aa2299d05ced2d50f2b42c6 [file] [log] [blame]
/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
package freemarker.core;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.utility.SecurityUtilities;
import freemarker.template.utility.StringUtil;
/**
* Parsing-time exception in a template (as opposed to runtime: {@link TemplateException}). This usually signals
* syntactical/lexical errors.
*
* Note that on JavaCC-level lexical errors throw {@link TokenMgrError} instead of this, however with the API-s that
* most users use those will be wrapped into {@link ParseException}-s.
*
* This is a modified version of file generated by JavaCC from FTL.jj.
* You can modify this class to customize the error reporting mechanisms so long as the public interface
* remains compatible with the original.
*
* @see TokenMgrError
*/
public class ParseException extends java.io.IOException implements FMParserConstants {
/**
* This is the last token that has been consumed successfully. If
* this object has been created due to a parse error, the token
* following this token will (therefore) be the first error token.
*/
public Token currentToken;
private static volatile Boolean jbossToolsMode;
private boolean messageAndDescriptionRendered;
private String message;
private String description;
public int columnNumber, lineNumber;
public int endColumnNumber, endLineNumber;
/**
* 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;
/**
* The end of line string for this machine.
*/
protected String eol = SecurityUtilities.getSystemProperty("line.separator", "\n");
/** @deprecated Will be remove without replacement in 2.4. */
protected boolean specialConstructor;
// This was no part of Throwable on J2SE 1.2; remove in Java 5
private final Throwable cause;
private String templateName;
/**
* 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.
* 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:
* ParseException: <result of getMessage>
*/
public ParseException(Token currentTokenVal,
int[][] expectedTokenSequencesVal,
String[] tokenImageVal
)
{
super("");
cause = null;
currentToken = currentTokenVal;
specialConstructor = true;
expectedTokenSequences = expectedTokenSequencesVal;
tokenImage = tokenImageVal;
lineNumber = currentToken.next.beginLine;
columnNumber = currentToken.next.beginColumn;
endLineNumber = currentToken.next.endLine;
endColumnNumber = currentToken.next.endColumn;
}
/**
* 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.
*
* @deprecated
*/
protected ParseException() {
super();
cause = null;
}
/**
* @deprecated Use a constructor to which you can also pass the template, and the end positions.
*/
public ParseException(String description, int lineNumber, int columnNumber) {
this(description, (Template) null, lineNumber, columnNumber, null);
}
/**
* @since 2.3.21
*/
public ParseException(String description, Template template,
int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber) {
this(description, template, lineNumber, columnNumber, endLineNumber, endColumnNumber, null);
}
/**
* @since 2.3.21
*/
public ParseException(String description, Template template,
int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber,
Throwable cause) {
this(description,
template == null ? null : template.getName(),
lineNumber, columnNumber,
endLineNumber, endColumnNumber,
cause);
}
/**
* @deprecated Use {@link #ParseException(String, Template, int, int, int, int)} instead, as IDE-s need the end
* position of the error too.
* @since 2.3.20
*/
public ParseException(String description, Template template, int lineNumber, int columnNumber) {
this(description, template, lineNumber, columnNumber, null);
}
/**
* @deprecated Use {@link #ParseException(String, Template, int, int, int, int, Throwable)} instead, as IDE-s need
* the end position of the error too.
* @since 2.3.20
*/
public ParseException(String description, Template template, int lineNumber, int columnNumber, Throwable cause) {
this(description,
template == null ? null : template.getName(),
lineNumber, columnNumber,
0, 0,
cause);
}
/**
* @since 2.3.20
*/
public ParseException(String description, Template template, Token tk) {
this(description, template, tk, null);
}
/**
* @since 2.3.20
*/
public ParseException(String description, Template template, Token tk, Throwable cause) {
this(description,
template == null ? null : template.getName(),
tk.beginLine, tk.beginColumn,
tk.endLine, tk.endColumn,
cause);
}
/**
* @since 2.3.20
*/
public ParseException(String description, TemplateObject tobj) {
this(description, tobj, null);
}
/**
* @since 2.3.20
*/
public ParseException(String description, TemplateObject tobj, Throwable cause) {
this(description,
tobj.getTemplate() == null ? null : tobj.getTemplate().getName(),
tobj.beginLine, tobj.beginColumn,
tobj.endLine, tobj.endColumn,
cause);
}
private ParseException(String description, String templateName,
int lineNumber, int columnNumber,
int endLineNumber, int endColumnNumber,
Throwable cause) {
super(description); // but we override getMessage, so it will be different
this.cause = cause;
this.description = description;
this.templateName = templateName;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.endLineNumber = endLineNumber;
this.endColumnNumber = endColumnNumber;
}
/**
* Should be used internally only; sets the name of the template that contains the error.
* This is needed as the constructor that JavaCC automatically calls doesn't pass in the template, so we
* set it somewhere later in an exception handler.
*/
public void setTemplateName(String templateName) {
this.templateName = templateName;
synchronized (this) {
messageAndDescriptionRendered = false;
message = null;
}
}
public Throwable getCause() {
return cause;
}
/**
* Returns the error location plus the error description.
*
* @see #getDescription()
* @see #getTemplateName()
* @see #getLineNumber()
* @see #getColumnNumber()
*/
public String getMessage() {
synchronized (this) {
if (messageAndDescriptionRendered) return message;
}
renderMessageAndDescription();
synchronized (this) {
return message;
}
}
private String getDescription() {
synchronized (this) {
if (messageAndDescriptionRendered) return description;
}
renderMessageAndDescription();
synchronized (this) {
return description;
}
}
/**
* Returns the description of the error without error location or source quotations, or {@code null} if there's no
* description available. This is useful in editors (IDE-s) where the error markers and the editor window itself
* already carry this information, so it's redundant the repeat in the error dialog.
*/
public String getEditorMessage() {
return getDescription();
}
/**
* Returns the name (template-root relative path) of the template whose parsing was failed.
* Maybe {@code null} if this is a non-stored template.
*
* @since 2.3.20
*/
public String getTemplateName() {
return templateName;
}
/**
* 1-based line number of the failing section, or 0 is the information is not available.
*/
public int getLineNumber() {
return lineNumber;
}
/**
* 1-based column number of the failing section, or 0 is the information is not available.
*/
public int getColumnNumber() {
return columnNumber;
}
/**
* 1-based line number of the failing section, or 0 is the information is not available.
*/
public int getEndLineNumber() {
return endLineNumber;
}
/**
* 1-based column number of the failing section, or 0 is the information is not available.
*/
public int getEndColumnNumber() {
return endColumnNumber;
}
private void renderMessageAndDescription() {
String desc = getOrRenderDescription();
String prefix;
if (!isInJBossToolsMode()) {
prefix = "Parsing error "
+ MessageUtil.formatLocationForSimpleParsingError(templateName, lineNumber, columnNumber)
+ ":\n";
} else {
prefix = "[col. " + columnNumber + "] ";
}
String msg = prefix + desc;
desc = msg.substring(prefix.length()); // so we reuse the backing char[]
synchronized (this) {
message = msg;
description = desc;
messageAndDescriptionRendered = true;
}
}
private boolean isInJBossToolsMode() {
if (jbossToolsMode == null) {
try {
jbossToolsMode =
ParseException.class.getClassLoader().toString().indexOf(
"[org.jboss.ide.eclipse.freemarker:") != -1
? Boolean.TRUE : Boolean.FALSE;
} catch (Throwable e) {
jbossToolsMode = Boolean.FALSE;
}
}
return jbossToolsMode.booleanValue();
}
/**
* Returns the description of the error without the error location, or {@code null} if there's no description
* available.
*/
private String getOrRenderDescription() {
synchronized (this) {
if (description != null) return description; // When we already have it from the constructor
}
String tokenErrDesc;
if (currentToken != null) {
tokenErrDesc = getCustomTokenErrorDescription();
if (tokenErrDesc == null) {
// The default JavaCC message generation stuff follows.
StringBuffer expected = new StringBuffer();
int maxSize = 0;
for (int i = 0; i < expectedTokenSequences.length; i++) {
if (i != 0) {
expected.append(eol);
}
expected.append(" ");
if (maxSize < expectedTokenSequences[i].length) {
maxSize = expectedTokenSequences[i].length;
}
for (int j = 0; j < expectedTokenSequences[i].length; j++) {
if (j != 0) expected.append(' ');
expected.append(tokenImage[expectedTokenSequences[i][j]]);
}
}
tokenErrDesc = "Encountered \"";
Token tok = currentToken.next;
for (int i = 0; i < maxSize; i++) {
if (i != 0) tokenErrDesc += " ";
if (tok.kind == 0) {
tokenErrDesc += tokenImage[0];
break;
}
tokenErrDesc += add_escapes(tok.image);
tok = tok.next;
}
tokenErrDesc += "\", but ";
if (expectedTokenSequences.length == 1) {
tokenErrDesc += "was expecting:" + eol;
} else {
tokenErrDesc += "was expecting one of:" + eol;
}
tokenErrDesc += expected;
}
} else {
tokenErrDesc = null;
}
return tokenErrDesc;
}
private String getCustomTokenErrorDescription() {
final Token nextToken = currentToken.next;
final int kind = nextToken.kind;
if (kind == EOF) {
for (int i = 0; i < expectedTokenSequences.length; i++) {
int[] sequence = expectedTokenSequences[i];
String name;
switch (sequence[0]) {
case END_FOREACH :
name = "#foreach";
break;
case END_LIST :
name = "#list";
break;
case END_SWITCH :
name = "#switch";
break;
case END_IF :
name = "#if";
break;
case END_COMPRESS :
name = "#compress";
break;
case END_MACRO :
case END_FUNCTION :
// It gives [..., [END_FUNCTION, ...], [END_MACRO, ...], ...] for both.
name = "#macro or #function";
break;
case END_TRANSFORM :
name = "#transform";
break;
case END_ESCAPE :
name = "#escape";
break;
case END_NOESCAPE :
name = "#noescape";
break;
case END_ASSIGN:
name = "#assign";
break;
case END_LOCAL:
name = "#local";
break;
case END_GLOBAL:
name = "#global";
break;
case END_ATTEMPT:
name = "#attempt";
break;
case CLOSE_BRACE:
name = "{";
break;
case CLOSE_BRACKET:
name = "[";
break;
case CLOSE_PAREN:
name = "(";
break;
case UNIFIED_CALL_END:
name = "@...";
break;
default:
name = null;
}
if (name != null) {
if (!name.startsWith("#") && !name.startsWith("@")) name = StringUtil.jQuote(name);
return "Unclosed " + name + " when the end of the file was reached.";
}
}
return "Unexpected end of file reached.";
} else if (kind == END_IF || kind == ELSE_IF || kind == ELSE) {
return "Unexpected directive, "
+ StringUtil.jQuote(nextToken)
+ ". Check whether you have a valid #if-#elseif-#else structure.";
}
return null;
}
/**
* Used to convert raw characters to their escaped version
* when these raw version cannot be used as part of an ASCII
* string literal.
*/
protected String add_escapes(String str) {
StringBuffer retval = new StringBuffer();
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" + s.substring(s.length() - 4, s.length()));
} else {
retval.append(ch);
}
continue;
}
}
return retval.toString();
}
}