freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [04/54] [partial] incubator-freemarker git commit: Top level package name change to org.apache.freemarker, and some of of the internal package structure changes. Other smaller cleanup. To be continued...
Date Thu, 16 Feb 2017 23:08:29 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java
new file mode 100644
index 0000000..9f1ded1
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -0,0 +1,1093 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import java.io.BufferedReader;
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.freemarker.core.ast.BugException;
+import org.apache.freemarker.core.ast.Configurable;
+import org.apache.freemarker.core.ast.Environment;
+import org.apache.freemarker.core.ast.FMParser;
+import org.apache.freemarker.core.ast.LibraryLoad;
+import org.apache.freemarker.core.ast.Macro;
+import org.apache.freemarker.core.ast.OutputFormat;
+import org.apache.freemarker.core.ast.ParseException;
+import org.apache.freemarker.core.ast.ParserConfiguration;
+import org.apache.freemarker.core.ast.TemplateConfiguration;
+import org.apache.freemarker.core.ast.TemplateElement;
+import org.apache.freemarker.core.ast.TemplateSpecifiedEncodingHandler;
+import org.apache.freemarker.core.ast.TextBlock;
+import org.apache.freemarker.core.ast.TokenMgrError;
+import org.apache.freemarker.core.ast._CoreAPI;
+import org.apache.freemarker.core.debug.impl.DebuggerService;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNodeModel;
+import org.apache.freemarker.core.model.impl.SimpleHash;
+import org.apache.freemarker.core.templateresolver.TemplateCache;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+import org.apache.freemarker.core.util.NullArgumentException;
+
+/**
+ * <p>
+ * Stores an already parsed template, ready to be processed (rendered) for unlimited times, possibly from multiple
+ * threads.
+ * 
+ * <p>
+ * Typically, you will use {@link Configuration#getTemplate(String)} to create/get {@link Template} objects, so you
+ * don't construct them directly. But you can also construct a template from a {@link Reader} or a {@link String} that
+ * contains the template source code. But then it's important to know that while the resulting {@link Template} is
+ * efficient for later processing, creating a new {@link Template} itself is relatively expensive. So try to re-use
+ * {@link Template} objects if possible. {@link Configuration#getTemplate(String)} (and its overloads) does that
+ * (caching {@link Template}-s) for you, but the constructor of course doesn't, so it's up to you to solve then.
+ * 
+ * <p>
+ * Objects of this class meant to be handled as immutable and thus thread-safe. However, it has some setter methods for
+ * changing FreeMarker settings. Those must not be used while the template is being processed, or if the template object
+ * is already accessible from multiple threads. If some templates need different settings that those coming from the
+ * shared {@link Configuration}, and you are using {@link Configuration#getTemplate(String)} (or its overloads), then
+ * use {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)} to achieve that.
+ */
+public class Template extends Configurable {
+    public static final String DEFAULT_NAMESPACE_PREFIX = "D";
+    public static final String NO_NS_PREFIX = "N";
+
+    private static final int READER_BUFFER_SIZE = 8192;
+    
+    /** This is only non-null during parsing. It's used internally to make some information available through the
+     *  Template API-s earlier than the parsing was finished. */
+    private transient FMParser parser;
+
+    private Map macros = new HashMap();
+    private List imports = new Vector();
+    private TemplateElement rootElement;
+    private String encoding, defaultNS;
+    private Object customLookupCondition;
+    private int actualTagSyntax;
+    private int actualNamingConvention;
+    private boolean autoEscaping;
+    private OutputFormat outputFormat;
+    private final String name;
+    private final String sourceName;
+    private final ArrayList lines = new ArrayList();
+    private final ParserConfiguration parserConfiguration;
+    private Map prefixToNamespaceURILookup = new HashMap();
+    private Map namespaceURIToPrefixLookup = new HashMap();
+    private Version templateLanguageVersion;
+
+    /**
+     * A prime constructor to which all other constructors should
+     * delegate directly or indirectly.
+     */
+    private Template(String name, String sourceName, Configuration cfg, ParserConfiguration customParserConfiguration) {
+        super(toNonNull(cfg));
+        this.name = name;
+        this.sourceName = sourceName;
+        this.templateLanguageVersion = normalizeTemplateLanguageVersion(toNonNull(cfg).getIncompatibleImprovements());
+        this.parserConfiguration = customParserConfiguration != null ? customParserConfiguration : getConfiguration();
+    }
+
+    private static Configuration toNonNull(Configuration cfg) {
+        return cfg != null ? cfg : Configuration.getDefaultConfiguration();
+    }
+
+    /**
+     * Same as {@link #Template(String, String, Reader, Configuration)} with {@code null} {@code sourceName} parameter.
+     */
+    public Template(String name, Reader reader, Configuration cfg) throws IOException {
+        this(name, null, reader, cfg);
+    }
+
+    /**
+     * Convenience constructor for {@link #Template(String, Reader, Configuration)
+     * Template(name, new StringReader(reader), cfg)}.
+     * 
+     * @since 2.3.20
+     */
+    public Template(String name, String sourceCode, Configuration cfg) throws IOException {
+        this(name, new StringReader(sourceCode), cfg);
+    }
+
+    /**
+     * Convenience constructor for {@link #Template(String, String, Reader, Configuration, String) Template(name, null,
+     * reader, cfg, encoding)}.
+     */
+    public Template(String name, Reader reader, Configuration cfg, String encoding) throws IOException {
+        this(name, null, reader, cfg, encoding);
+    }
+
+    /**
+     * Constructs a template from a character stream. Note that this is a relatively expensive operation; where higher
+     * performance matters, you should re-use (cache) {@link Template} instances instead of re-creating them from the
+     * same source again and again. ({@link Configuration#getTemplate(String) and its overloads already do such reuse.})
+     * 
+     * @param name
+     *            The path of the template file relatively to the (virtual) directory that you use to store the
+     *            templates (except if {@link #Template(String, String, Reader, Configuration, String) sourceName}
+     *            differs from it). Shouldn't start with {@code '/'}. Should use {@code '/'}, not {@code '\'}. Check
+     *            {@link #getName()} to see how the name will be used. The name should be independent of the actual
+     *            storage mechanism and physical location as far as possible. Even when the templates are stored
+     *            straightforwardly in real files (they often aren't; see {@link TemplateLoader}), the name shouldn't be
+     *            an absolute file path. Like if the template is stored in {@code "/www/templates/forum/main.ftl"}, and
+     *            you are using {@code "/www/templates/"} as the template root directory via
+     *            {@link Configuration#setDirectoryForTemplateLoading(java.io.File)}, then the template name will be
+     *            {@code "forum/main.ftl"}. The name can be {@code null} (should be used for template made on-the-fly
+     *            instead of being loaded from somewhere), in which case relative paths in it will be relative to
+     *            the template root directory (and here again, it's the {@link TemplateLoader} that knows what that
+     *            "physically" means).
+     * @param sourceName
+     *            See {@link #getSourceName()} for the meaning. Can be {@code null}, in which case
+     *            {@link #getSourceName()} will return the same as {@link #getName()}.
+     * @param reader
+     *            The character stream to read from. The {@link Reader} is <em>not</em> closed by this method (unlike
+     *            in FreeMarker 2.x.x), so be sure that it's closed somewhere. (Except of course, readers like
+     *            {@link StringReader} need not be closed.) The {@link Reader} need not be buffered, because this
+     *            method ensures that it will be read in few kilobyte chunks, not byte by byte.
+     * @param cfg
+     *            The Configuration object that this Template is associated with. If this is {@code null}, the "default"
+     *            {@link Configuration} object is used, which is highly discouraged, because it can easily lead to
+     *            erroneous, unpredictable behavior. (See more {@link Configuration#getDefaultConfiguration() here...})
+     * 
+     * @since 2.3.22
+     */
+   public Template(
+           String name, String sourceName, Reader reader, Configuration cfg) throws IOException {
+       this(name, sourceName, reader, cfg, null);
+   }
+    
+    /**
+     * Same as {@link #Template(String, String, Reader, Configuration)}, but also specifies the template's encoding (not
+     * recommended).
+     *
+     * @param encoding
+     *            This is the encoding that we are supposed to be using. At the first glance it's unnecessary because we
+     *            already have a {@link Reader} (so decoding with the charset has already happened), however, if this is
+     *            non-{@code null} and there's an {@code #ftl} header with {@code encoding} parameter, they must match,
+     *            or else a {@link WrongEncodingException} is thrown. Thus, it should be set if to decode the template,
+     *            we were using an encoding (a charset), otherwise it should be {@code null}. It's also kept as
+     *            meta-info (returned by {@link #getEncoding()}). It also has an impact when {@code #include}-ing or
+     *            {@code #import}-ing another template from this template, as its default encoding will be this. But
+     *            this behavior of said directives is considered to be harmful, and will be probably phased out.
+     * 
+     * @since 2.3.22
+     */
+   public Template(
+           String name, String sourceName, Reader reader, Configuration cfg, String encoding) throws IOException {
+       this(name, sourceName, reader, cfg, null, encoding);
+   }
+   
+    /**
+     * Same as {@link #Template(String, String, Reader, Configuration, String)}, but also specifies a
+     * {@link TemplateConfiguration}. This is mostly meant to be used by FreeMarker internally, but advanced users might
+     * still find this useful.
+     * 
+     * @param customParserConfiguration
+     *            Overrides the parsing related configuration settings of the {@link Configuration} parameter; can be
+     *            {@code null}. This is useful as the {@link Configuration} is normally a singleton shared by all
+     *            templates, and so it's not good for specifying template-specific settings. (While {@link Template}
+     *            itself has methods to specify settings just for that template, those don't influence the parsing, and
+     *            you only have opportunity to call them after the parsing anyway.) This objects is often a
+     *            {@link TemplateConfiguration} whose parent is the {@link Configuration} parameter, and then it
+     *            practically just overrides some of the parser settings, as the others are inherited from the
+     *            {@link Configuration}. Note that if this is a {@link TemplateConfiguration}, you will also want to
+     *            call {@link TemplateConfiguration#apply(Template)} on the resulting {@link Template} so that
+     *            {@link Configurable} settings will be set too, because this constructor only uses it as a
+     *            {@link ParserConfiguration}.
+     * @param encoding
+     *            Same as in {@link #Template(String, String, Reader, Configuration, String)}.
+     * 
+     * @since 2.3.24
+     */
+   public Template(
+           String name, String sourceName, Reader reader,
+           Configuration cfg, ParserConfiguration customParserConfiguration,
+           String encoding) throws IOException {
+       this(name, sourceName, reader, cfg, customParserConfiguration, encoding,
+               TemplateSpecifiedEncodingHandler.DEFAULT);
+    }
+   
+   /**
+    * Same as {@link #Template(String, String, Reader, Configuration, ParserConfiguration, String)}, but allows
+    * specifying a non-default (non-{@link TemplateSpecifiedEncodingHandler#DEFAULT}) behavior regarding encoding
+    * specified in the template content.
+    *  
+    * @param templateSpecifiedEncodingHandler Not {@code null}.
+    * 
+    * @since 2.3.26
+    */
+   public Template(
+           String name, String sourceName, Reader reader,
+           Configuration cfg, ParserConfiguration customParserConfiguration,
+           String encoding, TemplateSpecifiedEncodingHandler templateSpecifiedEncodingHandler) throws IOException {
+        this(name, sourceName, cfg, customParserConfiguration);
+       
+        NullArgumentException.check("templateSpecifiedEncodingHandler", templateSpecifiedEncodingHandler);
+       
+        this.setEncoding(encoding);
+        LineTableBuilder ltbReader;
+        try {
+            ParserConfiguration actualParserConfiguration = getParserConfiguration();
+            
+            // Ensure that the parameter Reader is only read in bigger chunks, as we don't know if the it's buffered.
+            // In particular, inside the FreeMarker code, we assume that the stream stages need not be buffered.
+            if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) {
+                reader = new BufferedReader(reader, READER_BUFFER_SIZE);
+            }
+            
+            ltbReader = new LineTableBuilder(reader, actualParserConfiguration);
+            reader = ltbReader;
+            
+            try {
+                parser = _CoreAPI.newFMParser(
+                        this, reader, actualParserConfiguration, templateSpecifiedEncodingHandler);
+                try {
+                    this.rootElement = parser.Root();
+                } catch (IndexOutOfBoundsException exc) {
+                    // There's a JavaCC bug where the Reader throws a RuntimeExcepton and then JavaCC fails with
+                    // IndexOutOfBoundsException. If that wasn't the case, we just rethrow. Otherwise we suppress the
+                    // IndexOutOfBoundsException and let the real cause to be thrown later. 
+                    if (!ltbReader.hasFailure()) {
+                        throw exc;
+                    }
+                    rootElement = null;
+                }
+                this.actualTagSyntax = parser._getLastTagSyntax();
+                this.actualNamingConvention = parser._getLastNamingConvention();
+            } catch (TokenMgrError exc) {
+                // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it
+                // to ParseException
+                throw exc.toParseException(this);
+            } finally {
+                parser = null;
+            }
+        } catch (ParseException e) {
+            e.setTemplateName(getSourceName());
+            throw e;
+        }
+        
+        // Throws any exception that JavaCC has silently treated as EOF:
+        ltbReader.throwFailure();
+        
+        DebuggerService.registerTemplate(this);
+        namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup);
+        prefixToNamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup);
+    }
+
+    /**
+     * Equivalent to {@link #Template(String, Reader, Configuration)
+     * Template(name, reader, null)}.
+     * 
+     * @deprecated This constructor uses the "default" {@link Configuration}
+     * instance, which can easily lead to erroneous, unpredictable behavior.
+     * See more {@link Configuration#getDefaultConfiguration() here...}.
+     */
+    @Deprecated
+    public Template(String name, Reader reader) throws IOException {
+        this(name, reader, (Configuration) null);
+    }
+
+    /**
+     * Only meant to be used internally.
+     * 
+     * @deprecated Has problems setting actualTagSyntax and templateLanguageVersion; will be removed in 2.4.
+     */
+    @Deprecated
+    // [2.4] remove this
+    Template(String name, TemplateElement root, Configuration cfg) {
+        this(name, null, cfg, (ParserConfiguration) null);
+        this.rootElement = root;
+        DebuggerService.registerTemplate(this);
+    }
+    
+    /**
+     * Same as {@link #getPlainTextTemplate(String, String, String, Configuration)} with {@code null} {@code sourceName}
+     * argument.
+     */
+    static public Template getPlainTextTemplate(String name, String content, Configuration config) {
+        return getPlainTextTemplate(name, null, content, config);
+    }
+    
+    /**
+     * Creates (not "get"-s) a {@link Template} that only contains a single block of static text, no dynamic content.
+     * 
+     * @param name
+     *            See {@link #getName} for more details.
+     * @param sourceName
+     *            See {@link #getSourceName} for more details. If {@code null}, it will be the same as the {@code name}.
+     * @param content
+     *            the block of text that this template represents
+     * @param config
+     *            the configuration to which this template belongs
+     * 
+     * @since 2.3.22
+     */
+    static public Template getPlainTextTemplate(String name, String sourceName, String content, Configuration config) {
+        Template template;
+        try {
+            template = new Template(name, sourceName, new StringReader("X"), config);
+        } catch (IOException e) {
+            throw new BugException("Plain text template creation failed", e);
+        }
+        _CoreAPI.replaceText((TextBlock) template.rootElement, content);
+        DebuggerService.registerTemplate(template);
+        return template;
+    }
+
+    private static Version normalizeTemplateLanguageVersion(Version incompatibleImprovements) {
+        _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+        int v = incompatibleImprovements.intValue();
+        if (v < _TemplateAPI.VERSION_INT_2_3_19) {
+            return Configuration.VERSION_2_3_0;
+        } else if (v > _TemplateAPI.VERSION_INT_2_3_21) {
+            return Configuration.VERSION_2_3_21;
+        } else { // if 2.3.19 or 2.3.20 or 2.3.21
+            return incompatibleImprovements;
+        }
+    }
+
+    /**
+     * Executes template, using the data-model provided, writing the generated output to the supplied {@link Writer}.
+     * 
+     * <p>
+     * For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker
+     * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead.
+     * 
+     * @param dataModel
+     *            the holder of the variables visible from the template (name-value pairs); usually a
+     *            {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables). Can
+     *            be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}. You can
+     *            also use an object that already implements {@link TemplateHashModel}; in that case it won't be
+     *            wrapped. If it's {@code null}, an empty data model is used.
+     * @param out
+     *            The {@link Writer} where the output of the template will go. Note that unless you have used
+     *            {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at
+     *            the when the template processing was finished. {@link Writer#close()} is not called. Can't be
+     *            {@code null}.
+     * 
+     * @throws TemplateException
+     *             if an exception occurs during template processing
+     * @throws IOException
+     *             if an I/O exception occurs during writing to the writer.
+     */
+    public void process(Object dataModel, Writer out)
+    throws TemplateException, IOException {
+        createProcessingEnvironment(dataModel, out, null).process();
+    }
+
+    /**
+     * Like {@link #process(Object, Writer)}, but also sets a (XML-)node to be recursively processed by the template.
+     * That node is accessed in the template with <tt>.node</tt>, <tt>#recurse</tt>, etc. See the
+     * <a href="http://freemarker.org/docs/xgui_declarative.html" target="_blank">Declarative XML Processing</a> as a
+     * typical example of recursive node processing.
+     * 
+     * @param rootNode The root node for recursive processing or {@code null}.
+     * 
+     * @throws TemplateException if an exception occurs during template processing
+     * @throws IOException if an I/O exception occurs during writing to the writer.
+     */
+    public void process(Object dataModel, Writer out, ObjectWrapper wrapper, TemplateNodeModel rootNode)
+    throws TemplateException, IOException {
+        Environment env = createProcessingEnvironment(dataModel, out, wrapper);
+        if (rootNode != null) {
+            env.setCurrentVisitorNode(rootNode);
+        }
+        env.process();
+    }
+    
+    /**
+     * Like {@link #process(Object, Writer)}, but overrides the {@link Configuration#getObjectWrapper()}.
+     * 
+     * @param wrapper The {@link ObjectWrapper} to be used instead of what {@link Configuration#getObjectWrapper()}
+     *      provides, or {@code null} if you don't want to override that. 
+     */
+    public void process(Object dataModel, Writer out, ObjectWrapper wrapper)
+    throws TemplateException, IOException {
+        createProcessingEnvironment(dataModel, out, wrapper).process();
+    }
+    
+   /**
+    * Creates a {@link org.apache.freemarker.core.ast.Environment Environment} object, using this template, the data-model provided as
+    * parameter. You have to call {@link Environment#process()} on the return value to set off the actual rendering.
+    * 
+    * <p>Use this method if you want to do some special initialization on the {@link Environment} before template
+    * processing, or if you want to read the {@link Environment} after template processing. Otherwise using
+    * {@link Template#process(Object, Writer)} is simpler.
+    *
+    * <p>Example:
+    *
+    * <pre>
+    * Environment env = myTemplate.createProcessingEnvironment(root, out, null);
+    * env.process();</pre>
+    * 
+    * <p>The above is equivalent with this:
+    * 
+    * <pre>
+    * myTemplate.process(root, out);</pre>
+    * 
+    * <p>But with <tt>createProcessingEnvironment</tt>, you can manipulate the environment
+    * before and after the processing:
+    * 
+    * <pre>
+    * Environment env = myTemplate.createProcessingEnvironment(root, out);
+    * 
+    * env.setLocale(myUsersPreferredLocale);
+    * env.setTimeZone(myUsersPreferredTimezone);
+    * 
+    * env.process();  // output is rendered here
+    * 
+    * TemplateModel x = env.getVariable("x");  // read back a variable set by the template</pre>
+    *
+    * @param dataModel the holder of the variables visible from all templates; see {@link #process(Object, Writer)} for
+    *     more details.
+    * @param wrapper The {@link ObjectWrapper} to use to wrap objects into {@link TemplateModel}
+    *     instances. Normally you left it {@code null}, in which case {@link Configurable#getObjectWrapper()} will be
+    *     used.
+    * @param out The {@link Writer} where the output of the template will go; see {@link #process(Object, Writer)} for
+    *     more details.
+    *     
+    * @return the {@link Environment} object created for processing. Call {@link Environment#process()} to process the
+    *    template.
+    * 
+    * @throws TemplateException if an exception occurs while setting up the Environment object.
+    * @throws IOException if an exception occurs doing any auto-imports
+    */
+    public Environment createProcessingEnvironment(Object dataModel, Writer out, ObjectWrapper wrapper)
+    throws TemplateException, IOException {
+        final TemplateHashModel dataModelHash;
+        if (dataModel instanceof TemplateHashModel) {
+            dataModelHash = (TemplateHashModel) dataModel;
+        } else {
+            if (wrapper == null) {
+                wrapper = getObjectWrapper();
+            }
+
+            if (dataModel == null) {
+                dataModelHash = new SimpleHash(wrapper);
+            } else {
+                TemplateModel wrappedDataModel = wrapper.wrap(dataModel);
+                if (wrappedDataModel instanceof TemplateHashModel) {
+                    dataModelHash = (TemplateHashModel) wrappedDataModel;
+                } else if (wrappedDataModel == null) {
+                    throw new IllegalArgumentException(
+                            wrapper.getClass().getName() + " converted " + dataModel.getClass().getName() + " to null.");
+                } else {
+                    throw new IllegalArgumentException(
+                            wrapper.getClass().getName() + " didn't convert " + dataModel.getClass().getName()
+                            + " to a TemplateHashModel. Generally, you want to use a Map<String, Object> or a "
+                            + "JavaBean as the root-map (aka. data-model) parameter. The Map key-s or JavaBean "
+                            + "property names will be the variable names in the template.");
+                }
+            }
+        }
+        return new Environment(this, dataModelHash, out);
+    }
+
+    /**
+     * Same as {@link #createProcessingEnvironment(Object, Writer, ObjectWrapper)
+     * createProcessingEnvironment(dataModel, out, null)}.
+     */
+    public Environment createProcessingEnvironment(Object dataModel, Writer out)
+    throws TemplateException, IOException {
+        return createProcessingEnvironment(dataModel, out, null);
+    }
+    
+    /**
+     * Returns a string representing the raw template
+     * text in canonical form.
+     */
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        try {
+            dump(sw);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe.getMessage());
+        }
+        return sw.toString();
+    }
+
+
+    /**
+     * The usually path-like (or URL-like) identifier of the template, or possibly {@code null} for non-stored
+     * templates. It usually looks like a relative UN*X path; it should use {@code /}, not {@code \}, and shouldn't
+     * start with {@code /} (but there are no hard guarantees). It's not a real path in a file-system, it's just a name
+     * that a {@link TemplateLoader} used to load the backing resource (in simple cases; actually that name is
+     * {@link #getSourceName()}, but see it there). Or, it can also be a name that was never used to load the template
+     * (directly created with {@link #Template(String, Reader, Configuration)}). Even if the templates are stored
+     * straightforwardly in files, this is relative to the base directory of the {@link TemplateLoader}. So it really
+     * could be anything, except that it has importance in these situations:
+     * 
+     * <p>
+     * Relative paths to other templates in this template will be resolved relatively to the directory part of this.
+     * Like if the template name is {@code "foo/this.ftl"}, then {@code <#include "other.ftl">} gets the template with
+     * name {@code "foo/other.ftl"}.
+     * </p>
+     * 
+     * <p>
+     * You should not use this name to indicate error locations, or to find the actual templates in general, because
+     * localized lookup, acquisition and other lookup strategies can transform names before they get to the
+     * {@link TemplateLoader} (the template storage) mechanism. Use {@link #getSourceName()} for these purposes.
+     * </p>
+     * 
+     * <p>
+     * Some frameworks use URL-like template names like {@code "someSchema://foo/bar.ftl"}. FreeMarker understands this
+     * notation, so an absolute path like {@code "/baaz.ftl"} in that template will be resolved too
+     * {@code "someSchema://baaz.ftl"}.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * The name that was actually used to load this template from the {@link TemplateLoader} (or from other custom
+     * storage mechanism). This is what should be shown in error messages as the error location. This is usually the
+     * same as {@link #getName()}, except when localized lookup, template acquisition ({@code *} step in the name), or
+     * other {@link TemplateLookupStrategy} transforms the requested name ({@link #getName()}) to a different final
+     * {@link TemplateLoader}-level name. For example, when you get a template with name {@code "foo.ftl"} then because
+     * of localized lookup, it's possible that something like {@code "foo_en.ftl"} will be loaded behind the scenes.
+     * While the template name will be still the same as the requested template name ({@code "foo.ftl"}), errors should
+     * point to {@code "foo_de.ftl"}. Note that relative paths are always resolved relatively to the {@code name}, not
+     * to the {@code sourceName}.
+     * 
+     * @since 2.3.22
+     */
+    public String getSourceName() {
+        return sourceName != null ? sourceName : getName();
+    }
+
+    /**
+     * Returns the Configuration object associated with this template.
+     */
+    public Configuration getConfiguration() {
+        return (Configuration) getParent();
+    }
+    
+    /**
+     * Returns the {@link ParserConfiguration} that was used for parsing this template. This is most often the same
+     * object as {@link #getConfiguration()}, but sometimes it's a {@link TemplateConfiguration}, or something else.
+     * It's never {@code null}.
+     * 
+     * @since 2.3.24
+     */
+    public ParserConfiguration getParserConfiguration() {
+        return parserConfiguration;
+    }
+    
+    /**
+     * Return the template language (FTL) version used by this template.
+     * For now (2.3.21) this is the same as {@link Configuration#getIncompatibleImprovements()}, except
+     * that it's normalized to the lowest version where the template language was changed.
+     */
+    Version getTemplateLanguageVersion() {
+        return templateLanguageVersion;
+    }
+
+    /**
+     * @param encoding
+     *            The encoding that was used to read this template. When this template {@code #include}-s or
+     *            {@code #import}-s another template, by default it will use this encoding for those. For backward
+     *            compatibility, this can be {@code null}, which will unset this setting.
+     * 
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    /**
+     * Returns the default character encoding used for reading included/imported files; if {@code null}, then
+     * the encoding returned by {@link Configuration#getEncoding(java.util.Locale)} should be used instead.
+     */
+    public String getEncoding() {
+        return this.encoding;
+    }
+    
+    /**
+     * Gets the custom lookup condition with which this template was found. See the {@code customLookupCondition}
+     * parameter of {@link Configuration#getTemplate(String, java.util.Locale, Object, String, boolean, boolean)} for
+     * more explanation.
+     * 
+     * @since 2.3.22
+     */
+    public Object getCustomLookupCondition() {
+        return customLookupCondition;
+    }
+
+    /**
+     * Mostly only used internally; setter pair of {@link #getCustomLookupCondition()}. This meant to be called directly
+     * after instantiating the template with its constructor, after a successfull lookup that used this condition. So
+     * this should only be called from code that deals with creating new {@code Template} objects, like from
+     * {@link TemplateCache}.
+     * 
+     * @since 2.3.22
+     */
+    public void setCustomLookupCondition(Object customLookupCondition) {
+        this.customLookupCondition = customLookupCondition;
+    }
+
+    /**
+     * Returns the tag syntax the parser has chosen for this template. If the syntax could be determined, it's
+     * {@link Configuration#SQUARE_BRACKET_TAG_SYNTAX} or {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}. If the syntax
+     * couldn't be determined (like because there was no tags in the template, or it was a plain text template), this
+     * returns whatever the default is in the current configuration, so it's maybe
+     * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}.
+     * 
+     * @since 2.3.20
+     */
+    public int getActualTagSyntax() {
+        return actualTagSyntax;
+    }
+    
+    /**
+     * Returns the naming convention the parser has chosen for this template. If it could be determined, it's
+     * {@link Configuration#LEGACY_NAMING_CONVENTION} or {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it
+     * couldn't be determined (like because there no identifier that's part of the template language was used where
+     * the naming convention matters), this returns whatever the default is in the current configuration, so it's maybe
+     * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}.
+     * 
+     * @since 2.3.23
+     */
+    public int getActualNamingConvention() {
+        return actualNamingConvention;
+    }
+    
+    /**
+     * Returns the output format (see {@link Configuration#setOutputFormat(OutputFormat)}) used for this template.
+     * The output format of a template can come from various places, in order of increasing priority:
+     * {@link Configuration#getOutputFormat()}, {@link ParserConfiguration#getOutputFormat()} (which is usually
+     * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's {@code output_format}
+     * option in the template.
+     * 
+     * @since 2.3.24
+     */
+    public OutputFormat getOutputFormat() {
+        return outputFormat;
+    }
+    
+    /**
+     * Meant to be called by the parser only. 
+     */
+    void setOutputFormat(OutputFormat outputFormat) {
+        this.outputFormat = outputFormat;
+    }
+    
+    /**
+     * Returns if the template actually uses auto-escaping (see {@link Configuration#setAutoEscapingPolicy(int)}). This value
+     * is decided by the parser based on the actual {@link OutputFormat}, and the auto-escaping enums, in order of
+     * increasing priority: {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()}
+     * (which is usually provided by {@link Configuration#getTemplateConfigurations()}), and finally on the {@code #ftl}
+     * header's {@code auto_esc} option in the template.
+     * 
+     * @since 2.3.24
+     */
+    public boolean getAutoEscaping() {
+        return autoEscaping;
+    }
+
+    /**
+     * Meant to be called by the parser only. 
+     */
+    void setAutoEscaping(boolean autoEscaping) {
+        this.autoEscaping = autoEscaping;
+    }
+    
+    /**
+     * Dump the raw template in canonical form.
+     */
+    public void dump(PrintStream ps) {
+        ps.print(rootElement.getCanonicalForm());
+    }
+
+    /**
+     * Dump the raw template in canonical form.
+     */
+    public void dump(Writer out) throws IOException {
+        out.write(rootElement.getCanonicalForm());
+    }
+
+    /**
+     * Called by code internally to maintain a table of macros
+     * 
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public void addMacro(Macro macro) {
+        macros.put(macro.getName(), macro);
+    }
+
+    /**
+     * Called by code internally to maintain a list of imports
+     * 
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public void addImport(LibraryLoad ll) {
+        imports.add(ll);
+    }
+
+    /**
+     * Returns the template source at the location specified by the coordinates given, or {@code null} if unavailable.
+     * A strange legacy in the behavior of this method is that it replaces tab characters with spaces according the
+     * value of {@link Template#getParserConfiguration()}/{@link ParserConfiguration#getTabSize()} (which usually
+     * comes from {@link Configuration#getTabSize()}), because tab characters move the column number with more than
+     * 1 in error messages. However, if you set the tab size to 1, this method leaves the tab characters as is.
+     * 
+     * @param beginColumn the first column of the requested source, 1-based
+     * @param beginLine the first line of the requested source, 1-based
+     * @param endColumn the last column of the requested source, 1-based
+     * @param endLine the last line of the requested source, 1-based
+     * 
+     * @see org.apache.freemarker.core.ast.TemplateObject#getSource()
+     */
+    public String getSource(int beginColumn,
+                            int beginLine,
+                            int endColumn,
+                            int endLine) {
+        if (beginLine < 1 || endLine < 1) return null;  // dynamically ?eval-ed expressions has no source available
+        
+        // Our container is zero-based.
+        --beginLine;
+        --beginColumn;
+        --endColumn;
+        --endLine;
+        StringBuilder buf = new StringBuilder();
+        for (int i = beginLine ; i <= endLine; i++) {
+            if (i < lines.size()) {
+                buf.append(lines.get(i));
+            }
+        }
+        int lastLineLength = lines.get(endLine).toString().length();
+        int trailingCharsToDelete = lastLineLength - endColumn - 1;
+        buf.delete(0, beginColumn);
+        buf.delete(buf.length() - trailingCharsToDelete, buf.length());
+        return buf.toString();
+    }
+
+    /**
+     * Reader that builds up the line table info for us, and also helps in working around JavaCC's exception
+     * suppression.
+     */
+    private class LineTableBuilder extends FilterReader {
+        
+        private final int tabSize;
+        private final StringBuilder lineBuf = new StringBuilder();
+        int lastChar;
+        boolean closed;
+        
+        /** Needed to work around JavaCC behavior where it silently treats any errors as EOF. */ 
+        private Exception failure; 
+
+        /**
+         * @param r the character stream to wrap
+         */
+        LineTableBuilder(Reader r, ParserConfiguration parserConfiguration) {
+            super(r);
+            tabSize = parserConfiguration.getTabSize();
+        }
+        
+        public boolean hasFailure() {
+            return failure != null;
+        }
+
+        public void throwFailure() throws IOException {
+            if (failure != null) {
+                if (failure instanceof IOException) {
+                    throw (IOException) failure;
+                }
+                if (failure instanceof RuntimeException) {
+                    throw (RuntimeException) failure;
+                }
+                throw new UndeclaredThrowableException(failure);
+            }
+        }
+
+        @Override
+        public int read() throws IOException {
+            try {
+                int c = in.read();
+                handleChar(c);
+                return c;
+            } catch (Exception e) {
+                throw rememberException(e);
+            }
+        }
+
+        private IOException rememberException(Exception e) throws IOException {
+            // JavaCC used to read from the Reader after it was closed. So we must not treat that as a failure. 
+            if (!closed) {
+                failure = e;
+            }
+            if (e instanceof IOException) {
+                return (IOException) e;
+            }
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new UndeclaredThrowableException(e);
+        }
+
+        @Override
+        public int read(char cbuf[], int off, int len) throws IOException {
+            try {
+                int numchars = in.read(cbuf, off, len);
+                for (int i = off; i < off + numchars; i++) {
+                    char c = cbuf[i];
+                    handleChar(c);
+                }
+                return numchars;
+            } catch (Exception e) {
+                throw rememberException(e);
+            }
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (lineBuf.length() > 0) {
+                lines.add(lineBuf.toString());
+                lineBuf.setLength(0);
+            }
+            super.close();
+            closed = true;
+        }
+
+        private void handleChar(int c) {
+            if (c == '\n' || c == '\r') {
+                if (lastChar == '\r' && c == '\n') { // CRLF under Windoze
+                    int lastIndex = lines.size() - 1;
+                    String lastLine = (String) lines.get(lastIndex);
+                    lines.set(lastIndex, lastLine + '\n');
+                } else {
+                    lineBuf.append((char) c);
+                    lines.add(lineBuf.toString());
+                    lineBuf.setLength(0);
+                }
+            } else if (c == '\t' && tabSize != 1) {
+                int numSpaces = tabSize - (lineBuf.length() % tabSize);
+                for (int i = 0; i < numSpaces; i++) {
+                    lineBuf.append(' ');
+                }
+            } else {
+                lineBuf.append((char) c);
+            }
+            lastChar = c;
+        }
+    }
+
+    /**
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public TemplateElement getRootTreeNode() {
+        return rootElement;
+    }
+    
+    /**
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public Map getMacros() {
+        return macros;
+    }
+
+    /**
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public List getImports() {
+        return imports;
+    }
+
+    /**
+     * This is used internally.
+     * 
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public void addPrefixNSMapping(String prefix, String nsURI) {
+        if (nsURI.length() == 0) {
+            throw new IllegalArgumentException("Cannot map empty string URI");
+        }
+        if (prefix.length() == 0) {
+            throw new IllegalArgumentException("Cannot map empty string prefix");
+        }
+        if (prefix.equals(NO_NS_PREFIX)) {
+            throw new IllegalArgumentException("The prefix: " + prefix + " cannot be registered, it's reserved for special internal use.");
+        }
+        if (prefixToNamespaceURILookup.containsKey(prefix)) {
+            throw new IllegalArgumentException("The prefix: '" + prefix + "' was repeated. This is illegal.");
+        }
+        if (namespaceURIToPrefixLookup.containsKey(nsURI)) {
+            throw new IllegalArgumentException("The namespace URI: " + nsURI + " cannot be mapped to 2 different prefixes.");
+        }
+        if (prefix.equals(DEFAULT_NAMESPACE_PREFIX)) {
+            this.defaultNS = nsURI;
+        } else {
+            prefixToNamespaceURILookup.put(prefix, nsURI);
+            namespaceURIToPrefixLookup.put(nsURI, prefix);
+        }
+    }
+    
+    public String getDefaultNS() {
+        return this.defaultNS;
+    }
+    
+    /**
+     * @return the NamespaceUri mapped to this prefix in this template. (Or null if there is none.)
+     */
+    public String getNamespaceForPrefix(String prefix) {
+        if (prefix.equals("")) {
+            return defaultNS == null ? "" : defaultNS;
+        }
+        return (String) prefixToNamespaceURILookup.get(prefix);
+    }
+    
+    /**
+     * @return the prefix mapped to this nsURI in this template. (Or null if there is none.)
+     */
+    public String getPrefixForNamespace(String nsURI) {
+        if (nsURI == null) {
+            return null;
+        }
+        if (nsURI.length() == 0) {
+            return defaultNS == null ? "" : NO_NS_PREFIX;
+        }
+        if (nsURI.equals(defaultNS)) {
+            return "";
+        }
+        return (String) namespaceURIToPrefixLookup.get(nsURI);
+    }
+    
+    /**
+     * @return the prefixed name, based on the ns_prefixes defined
+     * in this template's header for the local name and node namespace
+     * passed in as parameters.
+     */
+    public String getPrefixedName(String localName, String nsURI) {
+        if (nsURI == null || nsURI.length() == 0) {
+            if (defaultNS != null) {
+                return NO_NS_PREFIX + ":" + localName;
+            } else {
+                return localName;
+            }
+        } 
+        if (nsURI.equals(defaultNS)) {
+            return localName;
+        } 
+        String prefix = getPrefixForNamespace(nsURI);
+        if (prefix == null) {
+            return null;
+        }
+        return prefix + ":" + localName;
+    }
+    
+    /**
+     * @return an array of the {@link TemplateElement}s containing the given column and line numbers.
+     * @deprecated Should only be used internally, and might will be removed later.
+     */
+    @Deprecated
+    public List containingElements(int column, int line) {
+        final ArrayList elements = new ArrayList();
+        TemplateElement element = rootElement;
+        mainloop: while (element.contains(column, line)) {
+            elements.add(element);
+            for (Enumeration enumeration = element.children(); enumeration.hasMoreElements(); ) {
+                TemplateElement elem = (TemplateElement) enumeration.nextElement();
+                if (elem.contains(column, line)) {
+                    element = elem;
+                    continue mainloop;
+                }
+            }
+            break;
+        }
+        return elements.isEmpty() ? null : elements;
+    }
+
+    /**
+     * Thrown by the {@link Template} constructors that specify a non-{@code null} encoding whoch doesn't match the
+     * encoding specified in the {@code #ftl} header of the template.
+     */
+    static public class WrongEncodingException extends ParseException {
+        private static final long serialVersionUID = 1L;
+
+        /** @deprecated Use {@link #getTemplateSpecifiedEncoding()} instead. */
+        @Deprecated
+        public String specifiedEncoding;
+        
+        private final String constructorSpecifiedEncoding;
+
+        /**
+         * @deprecated Use {@link #WrongEncodingException(String, String)}.
+         */
+        @Deprecated
+        public WrongEncodingException(String templateSpecifiedEncoding) {
+            this(templateSpecifiedEncoding, null);
+        }
+
+        /**
+         * @since 2.3.22
+         */
+        public WrongEncodingException(String templateSpecifiedEncoding, String constructorSpecifiedEncoding) {
+            this.specifiedEncoding = templateSpecifiedEncoding;
+            this.constructorSpecifiedEncoding = constructorSpecifiedEncoding;
+        }
+        
+        @Override
+        public String getMessage() {
+            return "Encoding specified inside the template (" + specifiedEncoding
+                    + ") doesn't match the encoding specified for the Template constructor"
+                    + (constructorSpecifiedEncoding != null ? " (" + constructorSpecifiedEncoding + ")." : ".");
+        }
+
+        /**
+         * @since 2.3.22
+         */
+        public String getTemplateSpecifiedEncoding() {
+            return specifiedEncoding;
+        }
+
+        /**
+         * @since 2.3.22
+         */
+        public String getConstructorSpecifiedEncoding() {
+            return constructorSpecifiedEncoding;
+        }
+
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/TemplateException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateException.java b/src/main/java/org/apache/freemarker/core/TemplateException.java
new file mode 100644
index 0000000..e0739e0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateException.java
@@ -0,0 +1,661 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+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 org.apache.freemarker.core.ast.Environment;
+import org.apache.freemarker.core.ast.Expression;
+import org.apache.freemarker.core.ast.InvalidReferenceException;
+import org.apache.freemarker.core.ast.ParseException;
+import org.apache.freemarker.core.ast.TemplateElement;
+import org.apache.freemarker.core.ast.TemplateObject;
+import org.apache.freemarker.core.ast._CoreAPI;
+import org.apache.freemarker.core.ast._ErrorDescriptionBuilder;
+import org.apache.freemarker.core.util.CollectionUtils;
+
+/**
+ * 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.
+     */
+    public TemplateException(Environment env) {
+        this((String) null, null, env);
+    }
+
+    /**
+     * 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 {@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.
+     *
+     * @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 {@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.
+     *
+     * @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.
+     * 
+     * @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 {@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.
+     */
+    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;
+        }
+    }
+
+    /**
+     * @return the execution environment in which the exception occurred.
+     *    {@code null} if the exception was deserialized. 
+     */
+    public Environment getEnvironment() {
+        return env;
+    }
+
+    /**
+     * 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 {@link Throwable#printStackTrace(PrintWriter)} so that it will include the FTL stack trace.
+     */
+    @Override
+    public void printStackTrace(PrintWriter out) {
+        printStackTrace(out, true, true, true);
+    }
+    
+    /**
+     * @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);
+        }
+    }
+
+    /**
+     * @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 {@link TemplateException}. 
+     * @since 2.3.20
+     */
+    public void printStandardStackTrace(PrintStream ps) {
+        super.printStackTrace(ps);
+    }
+
+    /**
+     * 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 {@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 {@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 ({@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 ({@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 {@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 {@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 {@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
+     * {@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 {@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);
+            }
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
new file mode 100644
index 0000000..da1addc
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
@@ -0,0 +1,155 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.apache.freemarker.core.ast.Configurable;
+import org.apache.freemarker.core.ast.Environment;
+import org.apache.freemarker.core.ast.StopException;
+import org.apache.freemarker.core.util.StringUtil;
+
+/**
+ * Used for the {@code template_exception_handler} configuration setting;
+ * see {@link Configurable#setTemplateExceptionHandler(TemplateExceptionHandler)} for more.
+ */
+public interface TemplateExceptionHandler {
+    
+    /** 
+     * Method called after a {@link TemplateException} was raised inside a template. The exception should be re-thrown
+     * unless you want to suppress the exception.
+     * 
+     * <p>Note that you can check with {@link Environment#isInAttemptBlock()} if you are inside a {@code #attempt}
+     * block, which then will handle handle this exception and roll back the output generated inside it.
+     * 
+     * <p>Note that {@link StopException}-s (raised by {@code #stop}) won't be captured.
+     * 
+     * <p>Note that you shouldn't log the exception in this method unless you suppress it. If there's a concern that the
+     * exception might won't be logged after it bubbles up from {@link Template#process(Object, Writer)}, simply
+     * ensure that {@link Configuration#getLogTemplateExceptions()} is {@code true}. 
+     * 
+     * @param te The exception that occurred; don't forget to re-throw it unless you want to suppress it
+     * @param env The runtime environment of the template
+     * @param out This is where the output of the template is written
+     */
+    void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException;
+            
+   /**
+    * {@link TemplateExceptionHandler} that simply skips the failing instructions, letting the template continue
+    * executing. It does nothing to handle the event. Note that the exception is still logged, as with all
+    * other {@link TemplateExceptionHandler}-s.
+    */
+    TemplateExceptionHandler IGNORE_HANDLER = new TemplateExceptionHandler() {
+        public void handleTemplateException(TemplateException te, Environment env, Writer out) {
+            // Do nothing
+        }
+    };
+        
+    /**
+     * {@link TemplateExceptionHandler} that simply re-throws the exception; this should be used in most production
+     * systems.
+     */
+    TemplateExceptionHandler RETHROW_HANDLER = new TemplateExceptionHandler() {
+        public void handleTemplateException(TemplateException te, Environment env, Writer out)
+                throws TemplateException {
+            throw te;
+        }
+    };
+        
+    /**
+     * {@link TemplateExceptionHandler} useful when you developing non-HTML templates. This handler
+     * outputs the stack trace information to the client and then re-throws the exception.
+     */
+    TemplateExceptionHandler DEBUG_HANDLER = new TemplateExceptionHandler() {
+        public void handleTemplateException(TemplateException te, Environment env, Writer out)
+                throws TemplateException {
+            if (!env.isInAttemptBlock()) {
+                PrintWriter pw = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out);
+                pw.print("FreeMarker template error (DEBUG mode; use RETHROW in production!):\n");
+                te.printStackTrace(pw, false, true, true);
+                
+                pw.flush();  // To commit the HTTP response
+            }
+            throw te;
+        }
+    }; 
+    
+    /**
+     * {@link TemplateExceptionHandler} useful when you developing HTML templates. This handler
+     * outputs the stack trace information to the client, formatting it so that it will be usually well readable
+     * in the browser, and then re-throws the exception.
+     */
+    TemplateExceptionHandler HTML_DEBUG_HANDLER = new TemplateExceptionHandler() {
+        public void handleTemplateException(TemplateException te, Environment env, Writer out)
+                throws TemplateException {
+            if (!env.isInAttemptBlock()) {
+                boolean externalPw = out instanceof PrintWriter;
+                PrintWriter pw = externalPw ? (PrintWriter) out : new PrintWriter(out);
+                try {
+                    pw.print("<!-- FREEMARKER ERROR MESSAGE STARTS HERE -->"
+                            + "<!-- ]]> -->"
+                            + "<script language=javascript>//\"></script>"
+                            + "<script language=javascript>//'></script>"
+                            + "<script language=javascript>//\"></script>"
+                            + "<script language=javascript>//'></script>"
+                            + "</title></xmp></script></noscript></style></object>"
+                            + "</head></pre></table>"
+                            + "</form></table></table></table></a></u></i></b>"
+                            + "<div align='left' "
+                            + "style='background-color:#FFFF7C; "
+                            + "display:block; border-top:double; padding:4px; margin:0; "
+                            + "font-family:Arial,sans-serif; ");
+                    pw.print(FONT_RESET_CSS);
+                    pw.print("'>"
+                            + "<b style='font-size:12px; font-style:normal; font-weight:bold; "
+                            + "text-decoration:none; text-transform: none;'>FreeMarker template error "
+                            + " (HTML_DEBUG mode; use RETHROW in production!)</b>"
+                            + "<pre style='display:block; background: none; border: 0; margin:0; padding: 0;"
+                            + "font-family:monospace; ");
+                    pw.print(FONT_RESET_CSS);
+                    pw.println("; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; "
+                            + "white-space: -o-pre-wrap; word-wrap: break-word;'>");
+                    
+                    StringWriter stackTraceSW = new StringWriter();
+                    PrintWriter stackPW = new PrintWriter(stackTraceSW);
+                    te.printStackTrace(stackPW, false, true, true);
+                    stackPW.close();
+                    pw.println();
+                    pw.println(StringUtil.XMLEncNQG(stackTraceSW.toString()));
+                    
+                    pw.println("</pre></div></html>");
+                    pw.flush();  // To commit the HTTP response
+                } finally {
+                    if (!externalPw) pw.close();
+                }
+            }  // if (!env.isInAttemptBlock())
+            
+            throw te;
+        }
+        
+        private static final String FONT_RESET_CSS =
+                "color:#A80000; font-size:12px; font-style:normal; font-variant:normal; "
+                + "font-weight:normal; text-decoration:none; text-transform: none";
+        
+    };
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java b/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java
new file mode 100644
index 0000000..e70ab3d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import java.io.FileNotFoundException;
+
+import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+
+/**
+ * Thrown when {@link Configuration#getTemplate(String)} (or similar) doesn't find a template.
+ * This extends {@link FileNotFoundException} for backward compatibility, but in fact has nothing to do with files, as
+ * FreeMarker can load templates from many other sources.
+ *
+ * @since 2.3.22
+ * 
+ * @see MalformedTemplateNameException
+ * @see Configuration#getTemplate(String)
+ */
+public final class TemplateNotFoundException extends FileNotFoundException {
+    
+    private final String templateName;
+    private final Object customLookupCondition;
+
+    public TemplateNotFoundException(String templateName, Object customLookupCondition, String message) {
+        super(message);
+        this.templateName = templateName;
+        this.customLookupCondition = customLookupCondition;
+    }
+
+    /**
+     * The name (path) of the template that wasn't found.
+     */
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    /**
+     * The custom lookup condition with which the template was requested, or {@code null} if there's no such condition.
+     * See the {@code customLookupCondition} parameter of
+     * {@link Configuration#getTemplate(String, java.util.Locale, Object, String, boolean, boolean)}.
+     */
+    public Object getCustomLookupCondition() {
+        return customLookupCondition;
+    }
+
+}


Mime
View raw message