001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.core;
054:
055: import freemarker.template.utility.SecurityUtilities;
056:
057: /**
058: * This exception is thrown when parse errors are encountered.
059: * You can explicitly create objects of this exception type by
060: * calling the method generateParseException in the generated
061: * parser.
062: *
063: * You can modify this class to customize your error reporting
064: * mechanisms so long as you retain the public fields.
065: */
066: public class ParseException extends java.io.IOException implements
067: FMParserConstants {
068:
069: /**
070: * This constructor is used by the method "generateParseException"
071: * in the generated parser. Calling this constructor generates
072: * a new object of this type with the fields "currentToken",
073: * "expectedTokenSequences", and "tokenImage" set. The boolean
074: * flag "specialConstructor" is also set to true to indicate that
075: * this constructor was used to create this object.
076: * This constructor calls its super class with the empty string
077: * to force the "toString" method of parent class "Throwable" to
078: * print the error message in the form:
079: * ParseException: <result of getMessage>
080: */
081: public ParseException(Token currentTokenVal,
082: int[][] expectedTokenSequencesVal, String[] tokenImageVal) {
083: super ("");
084: specialConstructor = true;
085: currentToken = currentTokenVal;
086: expectedTokenSequences = expectedTokenSequencesVal;
087: tokenImage = tokenImageVal;
088: }
089:
090: /**
091: * The following constructors are for use by you for whatever
092: * purpose you can think of. Constructing the exception in this
093: * manner makes the exception behave in the normal way - i.e., as
094: * documented in the class "Throwable". The fields "errorToken",
095: * "expectedTokenSequences", and "tokenImage" do not contain
096: * relevant information. The JavaCC generated code does not use
097: * these constructors.
098: */
099:
100: protected ParseException() {
101: super ();
102: specialConstructor = false;
103: }
104:
105: /*
106: public ParseException(String message) {
107: super(message);
108: specialConstructor = false;
109: }
110: */
111:
112: public ParseException(String message, int lineNumber,
113: int columnNumber) {
114: super (message);
115: specialConstructor = false;
116: this .lineNumber = lineNumber;
117: this .columnNumber = columnNumber;
118: }
119:
120: public ParseException(String message, TemplateObject tobj) {
121: super (message);
122: specialConstructor = false;
123: this .lineNumber = tobj.beginLine;
124: this .columnNumber = tobj.beginColumn;
125: }
126:
127: /**
128: * This variable determines which constructor was used to create
129: * this object and thereby affects the semantics of the
130: * "getMessage" method (see below).
131: */
132: protected boolean specialConstructor;
133:
134: /**
135: * This is the last token that has been consumed successfully. If
136: * this object has been created due to a parse error, the token
137: * following this token will (therefore) be the first error token.
138: */
139: public Token currentToken;
140:
141: public int columnNumber, lineNumber;
142:
143: /**
144: * Each entry in this array is an array of integers. Each array
145: * of integers represents a sequence of tokens (by their ordinal
146: * values) that is expected at this point of the parse.
147: */
148: public int[][] expectedTokenSequences;
149:
150: /**
151: * This is a reference to the "tokenImage" array of the generated
152: * parser within which the parse error occurred. This array is
153: * defined in the generated ...Constants interface.
154: */
155: public String[] tokenImage;
156:
157: /**
158: * This method has the standard behavior when this object has been
159: * created using the standard constructors. Otherwise, it uses
160: * "currentToken" and "expectedTokenSequences" to generate a parse
161: * error message and returns it. If this object has been created
162: * due to a parse error, and you do not catch it (it gets thrown
163: * from the parser), then this method is called during the printing
164: * of the final stack trace, and hence the correct error message
165: * gets displayed.
166: */
167: public String getMessage() {
168: if (!specialConstructor) {
169: return super .getMessage();
170: }
171: String retval = customGetMessage();
172: if (retval != null) {
173: return retval;
174: }
175: // The default JavaCC message generation stuff follows.
176: String expected = "";
177: int maxSize = 0;
178: for (int i = 0; i < expectedTokenSequences.length; i++) {
179: if (maxSize < expectedTokenSequences[i].length) {
180: maxSize = expectedTokenSequences[i].length;
181: }
182: for (int j = 0; j < expectedTokenSequences[i].length; j++) {
183: expected += tokenImage[expectedTokenSequences[i][j]]
184: + " ";
185: }
186: if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
187: expected += "...";
188: }
189: expected += eol + " ";
190: }
191: retval = "Encountered \"";
192: Token tok = currentToken.next;
193: for (int i = 0; i < maxSize; i++) {
194: if (i != 0)
195: retval += " ";
196: if (tok.kind == 0) {
197: retval += tokenImage[0];
198: break;
199: }
200: retval += add_escapes(tok.image);
201: tok = tok.next;
202: }
203: retval += "\" at line " + currentToken.next.beginLine
204: + ", column " + currentToken.next.beginColumn;
205: retval += "." + eol;
206: if (expectedTokenSequences.length == 1) {
207: retval += "Was expecting:" + eol + " ";
208: } else {
209: retval += "Was expecting one of:" + eol + " ";
210: }
211: retval += expected;
212: return retval;
213: }
214:
215: public int getLineNumber() {
216: return currentToken != null ? currentToken.next.beginLine
217: : lineNumber;
218: }
219:
220: public int getColumnNumber() {
221: return currentToken != null ? currentToken.next.beginColumn
222: : columnNumber;
223: }
224:
225: // Custom message generation
226:
227: private String customGetMessage() {
228: Token nextToken = currentToken.next;
229: int kind = nextToken.kind;
230: if (kind == EOF) {
231: StringBuffer buf = new StringBuffer(
232: "Unexpected end of file reached.\n");
233: for (int i = 0; i < expectedTokenSequences.length; i++) {
234: int[] sequence = expectedTokenSequences[i];
235: switch (sequence[0]) {
236: case END_FOREACH:
237: buf.append("Unclosed foreach directive.\n");
238: break;
239: case END_LIST:
240: buf.append("Unclosed list directive.\n");
241: break;
242: case END_SWITCH:
243: buf.append("Unclosed switch directive.\n");
244: break;
245: case END_IF:
246: buf.append("Unclosed if directive.\n");
247: break;
248: case END_COMPRESS:
249: buf.append("Unclosed compress directive.\n");
250: break;
251: case END_MACRO:
252: buf.append("Unclosed macro directive.\n");
253: break;
254: case END_FUNCTION:
255: buf.append("Unclosed function directive.\n");
256: break;
257: case END_TRANSFORM:
258: buf.append("Unclosed transform directive.\n");
259: break;
260: case END_ESCAPE:
261: buf.append("Unclosed escape directive.\n");
262: break;
263: case END_NOESCAPE:
264: buf.append("Unclosed noescape directive.\n");
265: break;
266: }
267: }
268: return buf.toString();
269: }
270: if (kind == END_IF || kind == ELSE_IF || kind == ELSE) {
271: return "Found unexpected directive: "
272: + nextToken
273: + " on line "
274: + nextToken.beginLine
275: + ", column "
276: + nextToken.beginColumn
277: + "\nCheck whether you have a well-formed if-else block.";
278: }
279: return null;
280: }
281:
282: /**
283: * The end of line string for this machine.
284: */
285: protected String eol = SecurityUtilities.getSystemProperty(
286: "line.separator", "\n");
287:
288: /**
289: * Used to convert raw characters to their escaped version
290: * when these raw version cannot be used as part of an ASCII
291: * string literal.
292: */
293: protected String add_escapes(String str) {
294: StringBuffer retval = new StringBuffer();
295: char ch;
296: for (int i = 0; i < str.length(); i++) {
297: switch (str.charAt(i)) {
298: case 0:
299: continue;
300: case '\b':
301: retval.append("\\b");
302: continue;
303: case '\t':
304: retval.append("\\t");
305: continue;
306: case '\n':
307: retval.append("\\n");
308: continue;
309: case '\f':
310: retval.append("\\f");
311: continue;
312: case '\r':
313: retval.append("\\r");
314: continue;
315: case '\"':
316: retval.append("\\\"");
317: continue;
318: case '\'':
319: retval.append("\\\'");
320: continue;
321: case '\\':
322: retval.append("\\\\");
323: continue;
324: default:
325: if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
326: String s = "0000" + Integer.toString(ch, 16);
327: retval.append("\\u"
328: + s.substring(s.length() - 4, s.length()));
329: } else {
330: retval.append(ch);
331: }
332: continue;
333: }
334: }
335: return retval.toString();
336: }
337:
338: }
|