/*
 * 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.
 */

package freemarker.template;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;

import freemarker.core.Environment;
import freemarker.core.Expression;
import freemarker.core.InvalidReferenceException;
import freemarker.core.ParseException;
import freemarker.core.TemplateElement;
import freemarker.core.TemplateObject;
import freemarker.core._CoreAPI;
import freemarker.core._ErrorDescriptionBuilder;
import freemarker.template.utility.CollectionUtils;

Runtime exception in a template (as opposed to a parsing-time exception: ParseException). It prints a special stack trace that contains the template-language stack trace along the usual Java stack trace.
/** * Runtime exception in a template (as opposed to a parsing-time exception: {@link ParseException}). * It prints a special stack trace that contains the template-language stack trace along the usual Java stack trace. */
public class TemplateException extends Exception { private static final String FTL_INSTRUCTION_STACK_TRACE_TITLE = "FTL stack trace (\"~\" means nesting-related):"; // Set in constructor: private transient _ErrorDescriptionBuilder descriptionBuilder; private final transient Environment env; private final transient Expression blamedExpression; private transient TemplateElement[] ftlInstructionStackSnapshot; // Calculated on demand: private String renderedFtlInstructionStackSnapshot; // clalc. from ftlInstructionStackSnapshot private String renderedFtlInstructionStackSnapshotTop; // clalc. from ftlInstructionStackSnapshot private String description; // calc. from descriptionBuilder, or set by the construcor private transient String messageWithoutStackTop; private transient String message; private boolean blamedExpressionStringCalculated; private String blamedExpressionString; private boolean positionsCalculated; private String templateName; private String templateSourceName; private Integer lineNumber; private Integer columnNumber; private Integer endLineNumber; private Integer endColumnNumber; // Concurrency: private transient Object lock = new Object(); private transient ThreadLocal messageWasAlreadyPrintedForThisTrace;
Constructs a TemplateException with no specified detail message or underlying cause.
/** * Constructs a TemplateException with no specified detail message * or underlying cause. */
public TemplateException(Environment env) { this((String) null, null, env); }
Constructs a TemplateException with the given detail message, but no underlying cause exception.
Params:
  • description – the description of the error that occurred
/** * Constructs a TemplateException with the given detail message, * but no underlying cause exception. * * @param description the description of the error that occurred */
public TemplateException(String description, Environment env) { this(description, null, env); }
The same as TemplateException(Throwable, Environment); it's exists only for binary backward-compatibility.
/** * The same as {@link #TemplateException(Throwable, Environment)}; it's exists only for binary * backward-compatibility. */
public TemplateException(Exception cause, Environment env) { this((String) null, cause, env); }
Constructs a TemplateException with the given underlying Exception, but no detail message.
Params:
  • cause – the underlying Exception that caused this exception to be raised
Since:2.3.20
/** * Constructs a TemplateException with the given underlying Exception, * but no detail message. * * @param cause the underlying {@link Exception} that caused this * exception to be raised * * @since 2.3.20 */
public TemplateException(Throwable cause, Environment env) { this((String) null, cause, env); }
The same as TemplateException(String, Throwable, Environment); it's exists only for binary backward-compatibility.
/** * The same as {@link #TemplateException(String, Throwable, Environment)}; it's exists only for binary * backward-compatibility. */
public TemplateException(String description, Exception cause, Environment env) { this(description, cause, env, null, null); }
Constructs a TemplateException with both a description of the error that occurred and the underlying Exception that caused this exception to be raised.
Params:
  • description – the description of the error that occurred
  • cause – the underlying Exception that caused this exception to be raised
Since:2.3.20
/** * Constructs a TemplateException with both a description of the error * that occurred and the underlying Exception that caused this exception * to be raised. * * @param description the description of the error that occurred * @param cause the underlying {@link Exception} that caused this exception to be raised * * @since 2.3.20 */
public TemplateException(String description, Throwable cause, Environment env) { this(description, cause, env, null, null); }
Don't use this; this is to be used internally by FreeMarker. No backward compatibility guarantees.
Params:
  • blamedExpr – Maybe null. The FTL stack in the Environment only specifies the error location with "template element" granularity, and this can be used to point to the expression inside the template element.
/** * Don't use this; this is to be used internally by FreeMarker. No backward compatibility guarantees. * * @param blamedExpr Maybe {@code null}. The FTL stack in the {@link Environment} only specifies the error location * with "template element" granularity, and this can be used to point to the expression inside the * template element. */
protected TemplateException(Throwable cause, Environment env, Expression blamedExpr, _ErrorDescriptionBuilder descriptionBuilder) { this(null, cause, env, blamedExpr, descriptionBuilder); } private TemplateException( String renderedDescription, Throwable cause, Environment env, Expression blamedExpression, _ErrorDescriptionBuilder descriptionBuilder) { // Note: Keep this constructor lightweight. super(cause); // Message managed locally. if (env == null) env = Environment.getCurrentEnvironment(); this.env = env; this.blamedExpression = blamedExpression; this.descriptionBuilder = descriptionBuilder; description = renderedDescription; if (env != null) ftlInstructionStackSnapshot = _CoreAPI.getInstructionStackSnapshot(env); } private void renderMessages() { String description = getDescription(); if (description != null && description.length() != 0) { messageWithoutStackTop = description; } else if (getCause() != null) { messageWithoutStackTop = "No error description was specified for this error; low-level message: " + getCause().getClass().getName() + ": " + getCause().getMessage(); } else { messageWithoutStackTop = "[No error description was available.]"; } String stackTopFew = getFTLInstructionStackTopFew(); if (stackTopFew != null) { message = messageWithoutStackTop + "\n\n" + _CoreAPI.ERROR_MESSAGE_HR + "\n" + FTL_INSTRUCTION_STACK_TRACE_TITLE + "\n" + stackTopFew + _CoreAPI.ERROR_MESSAGE_HR; messageWithoutStackTop = message.substring(0, messageWithoutStackTop.length()); // to reuse backing char[] } else { message = messageWithoutStackTop; } } private void calculatePosition() { synchronized (lock) { if (!positionsCalculated) { // The expressions is the argument of the template element, so we prefer it as it's more specific. TemplateObject templateObject = blamedExpression != null ? (TemplateObject) blamedExpression : ( ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length != 0 ? ftlInstructionStackSnapshot[0] : null); // Line number blow 0 means no info, negative means position in ?eval-ed value that we won't use here. if (templateObject != null && templateObject.getBeginLine() > 0) { final Template template = templateObject.getTemplate(); templateName = template != null ? template.getName() : null; templateSourceName = template != null ? template.getSourceName() : null; lineNumber = Integer.valueOf(templateObject.getBeginLine()); columnNumber = Integer.valueOf(templateObject.getBeginColumn()); endLineNumber = Integer.valueOf(templateObject.getEndLine()); endColumnNumber = Integer.valueOf(templateObject.getEndColumn()); } positionsCalculated = true; deleteFTLInstructionStackSnapshotIfNotNeeded(); } } }
Deprecated:Java 1.4 has introduced Throwable.getCause() - use that instead, especially as this can't return runtime exceptions and errors as is.
/** * @deprecated Java 1.4 has introduced {@link #getCause()} - use that instead, especially as this can't return * runtime exceptions and errors as is. */
@Deprecated public Exception getCauseException() { return getCause() instanceof Exception ? (Exception) getCause() : new Exception("Wrapped to Exception: " + getCause(), getCause()); }
Returns the snapshot of the FTL stack trace at the time this exception was created.
/** * Returns the snapshot of the FTL stack trace at the time this exception was created. */
public String getFTLInstructionStack() { synchronized (lock) { if (ftlInstructionStackSnapshot != null || renderedFtlInstructionStackSnapshot != null) { if (renderedFtlInstructionStackSnapshot == null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); _CoreAPI.outputInstructionStack(ftlInstructionStackSnapshot, false, pw); pw.close(); if (renderedFtlInstructionStackSnapshot == null) { renderedFtlInstructionStackSnapshot = sw.toString(); deleteFTLInstructionStackSnapshotIfNotNeeded(); } } return renderedFtlInstructionStackSnapshot; } else { return null; } } } private String getFTLInstructionStackTopFew() { synchronized (lock) { if (ftlInstructionStackSnapshot != null || renderedFtlInstructionStackSnapshotTop != null) { if (renderedFtlInstructionStackSnapshotTop == null) { int stackSize = ftlInstructionStackSnapshot.length; String s; if (stackSize == 0) { s = ""; } else { StringWriter sw = new StringWriter(); _CoreAPI.outputInstructionStack(ftlInstructionStackSnapshot, true, sw); s = sw.toString(); } if (renderedFtlInstructionStackSnapshotTop == null) { renderedFtlInstructionStackSnapshotTop = s; deleteFTLInstructionStackSnapshotIfNotNeeded(); } } return renderedFtlInstructionStackSnapshotTop.length() != 0 ? renderedFtlInstructionStackSnapshotTop : null; } else { return null; } } } private void deleteFTLInstructionStackSnapshotIfNotNeeded() { if (renderedFtlInstructionStackSnapshot != null && renderedFtlInstructionStackSnapshotTop != null && (positionsCalculated || blamedExpression != null)) { ftlInstructionStackSnapshot = null; } } private String getDescription() { synchronized (lock) { if (description == null && descriptionBuilder != null) { description = descriptionBuilder.toString( getFailingInstruction(), env != null ? env.getShowErrorTips() : true); descriptionBuilder = null; } return description; } } private TemplateElement getFailingInstruction() { if (ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length > 0) { return ftlInstructionStackSnapshot[0]; } else { return null; } }
Returns:the execution environment in which the exception occurred. null if the exception was deserialized.
/** * @return the execution environment in which the exception occurred. * {@code null} if the exception was deserialized. */
public Environment getEnvironment() { return env; }
Overrides Throwable.printStackTrace(PrintStream) so that it will include the FTL stack trace.
/** * Overrides {@link Throwable#printStackTrace(PrintStream)} so that it will include the FTL stack trace. */
@Override public void printStackTrace(PrintStream out) { printStackTrace(out, true, true, true); }
Overrides Throwable.printStackTrace(PrintWriter) so that it will include the FTL stack trace.
/** * Overrides {@link Throwable#printStackTrace(PrintWriter)} so that it will include the FTL stack trace. */
@Override public void printStackTrace(PrintWriter out) { printStackTrace(out, true, true, true); }
Params:
  • heading – should the heading at the top be printed
  • ftlStackTrace – should the FTL stack trace be printed
  • javaStackTrace – should the Java stack trace be printed
Since:2.3.20
/** * @param heading should the heading at the top be printed * @param ftlStackTrace should the FTL stack trace be printed * @param javaStackTrace should the Java stack trace be printed * * @since 2.3.20 */
public void printStackTrace(PrintWriter out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) { synchronized (out) { printStackTrace(new PrintWriterStackTraceWriter(out), heading, ftlStackTrace, javaStackTrace); } }
Params:
  • heading – should the heading at the top be printed
  • ftlStackTrace – should the FTL stack trace be printed
  • javaStackTrace – should the Java stack trace be printed
Since:2.3.20
/** * @param heading should the heading at the top be printed * @param ftlStackTrace should the FTL stack trace be printed * @param javaStackTrace should the Java stack trace be printed * * @since 2.3.20 */
public void printStackTrace(PrintStream out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) { synchronized (out) { printStackTrace(new PrintStreamStackTraceWriter(out), heading, ftlStackTrace, javaStackTrace); } } private void printStackTrace(StackTraceWriter out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) { synchronized (out) { if (heading) { out.println("FreeMarker template error:"); } if (ftlStackTrace) { String stackTrace = getFTLInstructionStack(); if (stackTrace != null) { out.println(getMessageWithoutStackTop()); // Not getMessage()! out.println(); out.println(_CoreAPI.ERROR_MESSAGE_HR); out.println(FTL_INSTRUCTION_STACK_TRACE_TITLE); out.print(stackTrace); out.println(_CoreAPI.ERROR_MESSAGE_HR); } else { ftlStackTrace = false; javaStackTrace = true; } } if (javaStackTrace) { if (ftlStackTrace) { // We are after an FTL stack trace out.println(); out.println("Java stack trace (for programmers):"); out.println(_CoreAPI.ERROR_MESSAGE_HR); synchronized (lock) { if (messageWasAlreadyPrintedForThisTrace == null) { messageWasAlreadyPrintedForThisTrace = new ThreadLocal(); } messageWasAlreadyPrintedForThisTrace.set(Boolean.TRUE); } try { out.printStandardStackTrace(this); } finally { messageWasAlreadyPrintedForThisTrace.set(Boolean.FALSE); } } else { // javaStackTrace only out.printStandardStackTrace(this); } if (getCause() != null) { // Dirty hack to fight with ServletException class whose getCause() method doesn't work properly: Throwable causeCause = getCause().getCause(); if (causeCause == null) { try { // Reflection is used to prevent dependency on Servlet classes. Method m = getCause().getClass().getMethod("getRootCause", CollectionUtils.EMPTY_CLASS_ARRAY); Throwable rootCause = (Throwable) m.invoke(getCause(), CollectionUtils.EMPTY_OBJECT_ARRAY); if (rootCause != null) { out.println("ServletException root cause: "); out.printStandardStackTrace(rootCause); } } catch (Throwable exc) { ; // ignore } } } } // if (javaStackTrace) } }
Prints the stack trace as if wasn't overridden by TemplateException.
Since:2.3.20
/** * Prints the stack trace as if wasn't overridden by {@link TemplateException}. * @since 2.3.20 */
public void printStandardStackTrace(PrintStream ps) { super.printStackTrace(ps); }
Prints the stack trace as if wasn't overridden by TemplateException.
Since:2.3.20
/** * Prints the stack trace as if wasn't overridden by {@link TemplateException}. * @since 2.3.20 */
public void printStandardStackTrace(PrintWriter pw) { super.printStackTrace(pw); } @Override public String getMessage() { if (messageWasAlreadyPrintedForThisTrace != null && messageWasAlreadyPrintedForThisTrace.get() == Boolean.TRUE) { return "[... Exception message was already printed; see it above ...]"; } else { synchronized (lock) { if (message == null) renderMessages(); return message; } } }
Similar to getMessage(), but it doesn't contain the position of the failing instruction at then end of the text. It might contains the position of the failing expression though as part of the expression quotation, as that's the part of the description.
/** * Similar to {@link #getMessage()}, but it doesn't contain the position of the failing instruction at then end * of the text. It might contains the position of the failing <em>expression</em> though as part of the expression * quotation, as that's the part of the description. */
public String getMessageWithoutStackTop() { synchronized (lock) { if (messageWithoutStackTop == null) renderMessages(); return messageWithoutStackTop; } }
1-based line number of the failing section, or null if the information is not available.
Since:2.3.21
/** * 1-based line number of the failing section, or {@code null} if the information is not available. * * @since 2.3.21 */
public Integer getLineNumber() { synchronized (lock) { if (!positionsCalculated) { calculatePosition(); } return lineNumber; } }
Returns the name (Template.getName()) of the template where the error has occurred, or null if the information isn't available. This shouldn't be used for showing the error position; use getTemplateSourceName() instead.
Deprecated:Use getTemplateSourceName() instead, unless you are really sure that this is what you want. This method isn't really deprecated, it's just marked so to warn users about this.
Since:2.3.21
/** * Returns the name ({@link Template#getName()}) of the template where the error has occurred, or {@code null} if * the information isn't available. This shouldn't be used for showing the error position; use * {@link #getTemplateSourceName()} instead. * * @deprecated Use {@link #getTemplateSourceName()} instead, unless you are really sure that this is what you want. * This method isn't really deprecated, it's just marked so to warn users about this. * * @since 2.3.21 */
@Deprecated public String getTemplateName() { synchronized (lock) { if (!positionsCalculated) { calculatePosition(); } return templateName; } }
Returns the source name (Template.getSourceName()) of the template where the error has occurred, or null if the information isn't available. This is what should be used for showing the error position.
Since:2.3.22
/** * Returns the source name ({@link Template#getSourceName()}) of the template where the error has occurred, or * {@code null} if the information isn't available. This is what should be used for showing the error position. * * @since 2.3.22 */
public String getTemplateSourceName() { synchronized (lock) { if (!positionsCalculated) { calculatePosition(); } return templateSourceName; } }
1-based column number of the failing section, or null if the information is not available.
Since:2.3.21
/** * 1-based column number of the failing section, or {@code null} if the information is not available. * * @since 2.3.21 */
public Integer getColumnNumber() { synchronized (lock) { if (!positionsCalculated) { calculatePosition(); } return columnNumber; } }
1-based line number of the last line that contains the failing section, or null if the information is not available.
Since:2.3.21
/** * 1-based line number of the last line that contains the failing section, or {@code null} if the information is not * available. * * @since 2.3.21 */
public Integer getEndLineNumber() { synchronized (lock) { if (!positionsCalculated) { calculatePosition(); } return endLineNumber; } }
1-based column number of the last character of the failing template section, or null if the information is not available. Note that unlike with Java string API-s, this column number is inclusive.
Since:2.3.21
/** * 1-based column number of the last character of the failing template section, or {@code null} if the information * is not available. Note that unlike with Java string API-s, this column number is inclusive. * * @since 2.3.21 */
public Integer getEndColumnNumber() { synchronized (lock) { if (!positionsCalculated) { calculatePosition(); } return endColumnNumber; } }
If there was a blamed expression attached to this exception, it returns its canonical form, otherwise it returns null. This expression should always be inside the failing FTL instruction.

The typical application of this is getting the undefined expression from InvalidReferenceException-s.

Since:2.3.21
/** * If there was a blamed expression attached to this exception, it returns its canonical form, otherwise it returns * {@code null}. This expression should always be inside the failing FTL instruction. * * <p>The typical application of this is getting the undefined expression from {@link InvalidReferenceException}-s. * * @since 2.3.21 */
public String getBlamedExpressionString() { synchronized (lock) { if (!blamedExpressionStringCalculated) { if (blamedExpression != null) { blamedExpressionString = blamedExpression.getCanonicalForm(); } blamedExpressionStringCalculated = true; } return blamedExpressionString; } } Expression getBlamedExpression() { return blamedExpression; } private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException { // These are calculated from transient fields, so this is the last chance to calculate them: getFTLInstructionStack(); getFTLInstructionStackTopFew(); getDescription(); calculatePosition(); getBlamedExpressionString(); out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { lock = new Object(); in.defaultReadObject(); }
Delegate to a PrintWriter or to a PrintStream.
/** Delegate to a {@link PrintWriter} or to a {@link PrintStream}. */
private interface StackTraceWriter { void print(Object obj); void println(Object obj); void println(); void printStandardStackTrace(Throwable exception); } private static class PrintStreamStackTraceWriter implements StackTraceWriter { private final PrintStream out; PrintStreamStackTraceWriter(PrintStream out) { this.out = out; } public void print(Object obj) { out.print(obj); } public void println(Object obj) { out.println(obj); } public void println() { out.println(); } public void printStandardStackTrace(Throwable exception) { if (exception instanceof TemplateException) { ((TemplateException) exception).printStandardStackTrace(out); } else { exception.printStackTrace(out); } } } private static class PrintWriterStackTraceWriter implements StackTraceWriter { private final PrintWriter out; PrintWriterStackTraceWriter(PrintWriter out) { this.out = out; } public void print(Object obj) { out.print(obj); } public void println(Object obj) { out.println(obj); } public void println() { out.println(); } public void printStandardStackTrace(Throwable exception) { if (exception instanceof TemplateException) { ((TemplateException) exception).printStandardStackTrace(out); } else { exception.printStackTrace(out); } } } }