http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ast/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/Environment.java 
b/src/main/java/org/apache/freemarker/core/ast/Environment.java
deleted file mode 100644
index c0de302..0000000
--- a/src/main/java/org/apache/freemarker/core/ast/Environment.java
+++ /dev/null
@@ -1,2982 +0,0 @@
-/*
- * 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.ast;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.text.Collator;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.TemplateExceptionHandler;
-import org.apache.freemarker.core._CoreLogs;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
-import org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateNodeModel;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
-import org.apache.freemarker.core.model.TransformControl;
-import org.apache.freemarker.core.model.impl.SimpleHash;
-import org.apache.freemarker.core.model.impl.SimpleSequence;
-import 
org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
-import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
-import org.apache.freemarker.core.templateresolver.TemplateResolver;
-import org.apache.freemarker.core.templateresolver._CacheAPI;
-import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
-import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-import org.apache.freemarker.core.util._DateUtil;
-import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
-import org.apache.freemarker.core.util._NullWriter;
-import org.apache.freemarker.core.util._StringUtil;
-import org.slf4j.Logger;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Object that represents the runtime environment during template processing. 
For every invocation of a
- * <tt>Template.process()</tt> method, a new instance of this object is 
created, and then discarded when
- * <tt>process()</tt> returns. This object stores the set of temporary 
variables created by the template, the value of
- * settings set by the template, the reference to the data model root, etc. 
Everything that is needed to fulfill the
- * template processing job.
- *
- * <p>
- * Data models that need to access the <tt>Environment</tt> object that 
represents the template processing on the
- * current thread can use the {@link #getCurrentEnvironment()} method.
- *
- * <p>
- * If you need to modify or read this object before or after the 
<tt>process</tt> call, use
- * {@link Template#createProcessingEnvironment(Object rootMap, Writer out, 
ObjectWrapper wrapper)}
- */
-public final class Environment extends Configurable {
-    
-    private static final ThreadLocal threadEnv = new ThreadLocal();
-
-    private static final Logger LOG = _CoreLogs.RUNTIME;
-    private static final Logger LOG_ATTEMPT = _CoreLogs.ATTEMPT;
-
-    // Do not use this object directly; clone it first! DecimalFormat isn't
-    // thread-safe.
-    private static final DecimalFormat C_NUMBER_FORMAT = new DecimalFormat(
-            "0.################",
-            new DecimalFormatSymbols(Locale.US));
-
-    static {
-        C_NUMBER_FORMAT.setGroupingUsed(false);
-        C_NUMBER_FORMAT.setDecimalSeparatorAlwaysShown(false);
-    }
-
-    private final Configuration configuration;
-    private final TemplateHashModel rootDataModel;
-    private TemplateElement[] instructionStack = new TemplateElement[16];
-    private int instructionStackSize = 0;
-    private final ArrayList recoveredErrorStack = new ArrayList();
-
-    private TemplateNumberFormat cachedTemplateNumberFormat;
-    private Map<String, TemplateNumberFormat> cachedTemplateNumberFormats;
-
-    /**
-     * Stores the date/time/date-time formatters that are used when no format 
is explicitly given at the place of
-     * formatting. That is, in situations like ${lastModified} or even 
${lastModified?date}, but not in situations like
-     * ${lastModified?string.iso}.
-     * 
-     * <p>
-     * The index of the array is calculated from what kind of formatter we 
want (see
-     * {@link #getTemplateDateFormatCacheArrayIndex(int, boolean, 
boolean)}):<br>
-     * Zoned input: 0: U, 1: T, 2: D, 3: DT<br>
-     * Zoneless input: 4: U, 5: T, 6: D, 7: DT<br>
-     * SQL D T TZ + Zoned input: 8: U, 9: T, 10: D, 11: DT<br>
-     * SQL D T TZ + Zoneless input: 12: U, 13: T, 14: D, 15: DT
-     * 
-     * <p>
-     * This is a lazily filled cache. It starts out as {@code null}, then when 
first needed the array will be created.
-     * The array elements also start out as {@code null}-s, and they are 
filled as the particular kind of formatter is
-     * first needed.
-     */
-    private TemplateDateFormat[] cachedTempDateFormatArray;
-    /** Similar to {@link #cachedTempDateFormatArray}, but used when a 
formatting string was specified. */
-    private HashMap<String, TemplateDateFormat>[] 
cachedTempDateFormatsByFmtStrArray;
-    private static final int CACHED_TDFS_ZONELESS_INPUT_OFFS = 4;
-    private static final int CACHED_TDFS_SQL_D_T_TZ_OFFS = 
CACHED_TDFS_ZONELESS_INPUT_OFFS * 2;
-    private static final int CACHED_TDFS_LENGTH = CACHED_TDFS_SQL_D_T_TZ_OFFS 
* 2;
-
-    /** Caches the result of {@link #isSQLDateAndTimeTimeZoneSameAsNormal()}. 
*/
-    private Boolean cachedSQLDateAndTimeTimeZoneSameAsNormal;
-
-    private NumberFormat cNumberFormat;
-
-    /**
-     * Used by the "iso_" built-ins to accelerate formatting.
-     * 
-     * @see #getISOBuiltInCalendarFactory()
-     */
-    private DateToISO8601CalendarFactory isoBuiltInCalendarFactory;
-
-    private Collator cachedCollator;
-
-    private Writer out;
-    private Macro.Context currentMacroContext;
-    private LocalContextStack localContextStack;
-    private final Namespace mainNamespace;
-    private Namespace currentNamespace, globalNamespace;
-    private HashMap<String, Namespace> loadedLibs;
-
-    private boolean inAttemptBlock;
-    private Throwable lastThrowable;
-
-    private TemplateModel lastReturnValue;
-    private HashMap macroToNamespaceLookup = new HashMap();
-
-    private TemplateNodeModel currentVisitorNode;
-    private TemplateSequenceModel nodeNamespaces;
-    // Things we keep track of for the fallback mechanism.
-    private int nodeNamespaceIndex;
-    private String currentNodeName, currentNodeNS;
-
-    private String cachedURLEscapingCharset;
-    private boolean cachedURLEscapingCharsetSet;
-
-    private boolean fastInvalidReferenceExceptions;
-
-    /**
-     * Retrieves the environment object associated with the current thread, or 
{@code null} if there's no template
-     * processing going on in this thread. Data model implementations that 
need access to the environment can call this
-     * method to obtain the environment object that represents the template 
processing that is currently running on the
-     * current thread.
-     */
-    public static Environment getCurrentEnvironment() {
-        return (Environment) threadEnv.get();
-    }
-
-    static void setCurrentEnvironment(Environment env) {
-        threadEnv.set(env);
-    }
-
-    public Environment(Template template, final TemplateHashModel 
rootDataModel, Writer out) {
-        super(template);
-        configuration = template.getConfiguration();
-        globalNamespace = new Namespace(null);
-        currentNamespace = mainNamespace = new Namespace(template);
-        this.out = out;
-        this.rootDataModel = rootDataModel;
-        importMacros(template);
-    }
-
-    /**
-     * Returns the topmost {@link Template}, with other words, the one for 
which this {@link Environment} was created.
-     * That template will never change, like {@code #include} or macro calls 
don't change it.
-     * 
-     * @see #getCurrentNamespace()
-     * 
-     * @since 2.3.22
-     */
-    public Template getMainTemplate() {
-        return mainNamespace.getTemplate();
-    }
-
-    /**
-     * Returns the {@link Template} that we are "lexically" inside at the 
moment. This template will change when
-     * entering an {@code #include} or calling a macro or function in another 
template, or returning to yet another
-     * template with {@code #nested}. As such, it's useful in {@link 
TemplateDirectiveModel} to find out if from where
-     * the directive was called from.
-     * 
-     * @see #getMainTemplate()
-     * @see #getCurrentNamespace()
-     * 
-     * @since 2.3.23
-     */
-    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False 
alarm")
-    public Template getCurrentTemplate() {
-        int ln = instructionStackSize;
-        return ln == 0 ? getMainTemplate() : instructionStack[ln - 
1].getTemplate();
-    }
-
-    /**
-     * Gets the currently executing <em>custom</em> directive's call place 
information, or {@code null} if there's no
-     * executing custom directive. This currently only works for calls made 
from templates with the {@code <@...>}
-     * syntax. This should only be called from the {@link 
TemplateDirectiveModel} that was invoked with {@code <@...>},
-     * otherwise its return value is not defined by this API (it's usually 
{@code null}).
-     * 
-     * @since 2.3.22
-     */
-    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False 
alarm")
-    public DirectiveCallPlace getCurrentDirectiveCallPlace() {
-        int ln = instructionStackSize;
-        if (ln == 0) return null;
-        TemplateElement te = instructionStack[ln - 1];
-        if (te instanceof UnifiedCall) return (UnifiedCall) te;
-        if (te instanceof Macro && ln > 1 && instructionStack[ln - 2] 
instanceof UnifiedCall) {
-            return (UnifiedCall) instructionStack[ln - 2];
-        }
-        return null;
-    }
-
-    /**
-     * Deletes cached values that meant to be valid only during a single 
template execution.
-     */
-    private void clearCachedValues() {
-        cachedTemplateNumberFormats = null;
-        cachedTemplateNumberFormat = null;
-
-        cachedTempDateFormatArray = null;
-        cachedTempDateFormatsByFmtStrArray = null;
-
-        cachedCollator = null;
-        cachedURLEscapingCharset = null;
-        cachedURLEscapingCharsetSet = false;
-    }
-
-    /**
-     * Processes the template to which this environment belongs to.
-     */
-    public void process() throws TemplateException, IOException {
-        Object savedEnv = threadEnv.get();
-        threadEnv.set(this);
-        try {
-            // Cached values from a previous execution are possibly outdated.
-            clearCachedValues();
-            try {
-                doAutoImportsAndIncludes(this);
-                visit(getMainTemplate().getRootTreeNode());
-                // It's here as we must not flush if there was an exception.
-                if (getAutoFlush()) {
-                    out.flush();
-                }
-            } finally {
-                // It's just to allow the GC to free memory...
-                clearCachedValues();
-            }
-        } finally {
-            threadEnv.set(savedEnv);
-        }
-    }
-
-    /**
-     * "Visit" the template element.
-     */
-    void visit(TemplateElement element) throws IOException, TemplateException {
-        // ATTENTION: This method body is manually "inlined" into 
visit(TemplateElement[]); keep them in sync!
-        pushElement(element);
-        try {
-            TemplateElement[] templateElementsToVisit = element.accept(this);
-            if (templateElementsToVisit != null) {
-                for (TemplateElement el : templateElementsToVisit) {
-                    if (el == null) {
-                        break;  // Skip unused trailing buffer capacity 
-                    }
-                    visit(el);
-                }
-            }
-        } catch (TemplateException te) {
-            handleTemplateException(te);
-        } finally {
-            popElement();
-        }
-        // ATTENTION: This method body above is manually "inlined" into 
visit(TemplateElement[]); keep them in sync!
-    }
-    
-    /**
-     * @param elementBuffer
-     *            The elements to visit; might contains trailing {@code 
null}-s. Can be {@code null}.
-     * 
-     * @since 2.3.24
-     */
-    final void visit(TemplateElement[] elementBuffer) throws IOException, 
TemplateException {
-        if (elementBuffer == null) {
-            return;
-        }
-        for (TemplateElement element : elementBuffer) {
-            if (element == null) {
-                break;  // Skip unused trailing buffer capacity 
-            }
-            
-            // ATTENTION: This part is the manually "inlining" of 
visit(TemplateElement[]); keep them in sync!
-            // We don't just let Hotspot to do it, as we want a hard guarantee 
regarding maximum stack usage. 
-            pushElement(element);
-            try {
-                TemplateElement[] templateElementsToVisit = 
element.accept(this);
-                if (templateElementsToVisit != null) {
-                    for (TemplateElement el : templateElementsToVisit) {
-                        if (el == null) {
-                            break;  // Skip unused trailing buffer capacity 
-                        }
-                        visit(el);
-                    }
-                }
-            } catch (TemplateException te) {
-                handleTemplateException(te);
-            } finally {
-                popElement();
-            }
-            // ATTENTION: This part above is the manually "inlining" of 
visit(TemplateElement[]); keep them in sync!
-        }
-    }
-
-    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "Not 
called when stack is empty")
-    private TemplateElement replaceTopElement(TemplateElement element) {
-        return instructionStack[instructionStackSize - 1] = element;
-    }
-
-    private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
-
-    void visit(final TemplateElement element,
-            TemplateDirectiveModel directiveModel, Map args,
-            final List bodyParameterNames) throws TemplateException, 
IOException {
-        visit(new TemplateElement[] { element }, directiveModel, args, 
bodyParameterNames);
-    }
-    
-    void visit(final TemplateElement[] childBuffer,
-            TemplateDirectiveModel directiveModel, Map args,
-            final List bodyParameterNames) throws TemplateException, 
IOException {
-        TemplateDirectiveBody nested;
-        if (childBuffer == null) {
-            nested = null;
-        } else {
-            nested = new NestedElementTemplateDirectiveBody(childBuffer);
-        }
-        final TemplateModel[] outArgs;
-        if (bodyParameterNames == null || bodyParameterNames.isEmpty()) {
-            outArgs = NO_OUT_ARGS;
-        } else {
-            outArgs = new TemplateModel[bodyParameterNames.size()];
-        }
-        if (outArgs.length > 0) {
-            pushLocalContext(new LocalContext() {
-
-                @Override
-                public TemplateModel getLocalVariable(String name) {
-                    int index = bodyParameterNames.indexOf(name);
-                    return index != -1 ? outArgs[index] : null;
-                }
-
-                @Override
-                public Collection getLocalVariableNames() {
-                    return bodyParameterNames;
-                }
-            });
-        }
-        try {
-            directiveModel.execute(this, args, outArgs, nested);
-        } finally {
-            if (outArgs.length > 0) {
-                localContextStack.pop();
-            }
-        }
-    }
-
-    /**
-     * "Visit" the template element, passing the output through a 
TemplateTransformModel
-     * 
-     * @param elementBuffer
-     *            the element to visit through a transform; might contains 
trailing {@code null}-s
-     * @param transform
-     *            the transform to pass the element output through
-     * @param args
-     *            optional arguments fed to the transform
-     */
-    void visitAndTransform(TemplateElement[] elementBuffer,
-            TemplateTransformModel transform,
-            Map args)
-                    throws TemplateException, IOException {
-        try {
-            Writer tw = transform.getWriter(out, args);
-            if (tw == null) tw = EMPTY_BODY_WRITER;
-            TransformControl tc = tw instanceof TransformControl
-                    ? (TransformControl) tw
-                    : null;
-
-            Writer prevOut = out;
-            out = tw;
-            try {
-                if (tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
-                    do {
-                        visit(elementBuffer);
-                    } while (tc != null && tc.afterBody() == 
TransformControl.REPEAT_EVALUATION);
-                }
-            } catch (Throwable t) {
-                try {
-                    if (tc != null) {
-                        tc.onError(t);
-                    } else {
-                        throw t;
-                    }
-                } catch (TemplateException e) {
-                    throw e;
-                } catch (IOException e) {
-                    throw e;
-                } catch (RuntimeException e) {
-                    throw e;
-                } catch (Error e) {
-                    throw e;
-                } catch (Throwable e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-            } finally {
-                out = prevOut;
-                tw.close();
-            }
-        } catch (TemplateException te) {
-            handleTemplateException(te);
-        }
-    }
-
-    /**
-     * Visit a block using buffering/recovery
-     */
-     void visitAttemptRecover(
-             AttemptBlock attemptBlock, TemplateElement attemptedSection, 
RecoveryBlock recoverySection)
-             throws TemplateException, IOException {
-        Writer prevOut = out;
-        StringWriter sw = new StringWriter();
-         out = sw;
-        TemplateException thrownException = null;
-        boolean lastFIRE = setFastInvalidReferenceExceptions(false);
-        boolean lastInAttemptBlock = inAttemptBlock;
-        try {
-            inAttemptBlock = true;
-            visit(attemptedSection);
-        } catch (TemplateException te) {
-            thrownException = te;
-        } finally {
-            inAttemptBlock = lastInAttemptBlock;
-            setFastInvalidReferenceExceptions(lastFIRE);
-            out = prevOut;
-        }
-        if (thrownException != null) {
-            if (LOG_ATTEMPT.isDebugEnabled()) {
-                LOG_ATTEMPT.debug("Error in attempt block " +
-                        attemptBlock.getStartLocationQuoted(), 
thrownException);
-            }
-            try {
-                recoveredErrorStack.add(thrownException);
-                visit(recoverySection);
-            } finally {
-                recoveredErrorStack.remove(recoveredErrorStack.size() - 1);
-            }
-        } else {
-            out.write(sw.toString());
-        }
-    }
-
-    String getCurrentRecoveredErrorMessage() throws TemplateException {
-        if (recoveredErrorStack.isEmpty()) {
-            throw new _MiscTemplateException(this, ".error is not available 
outside of a #recover block");
-        }
-        return ((Throwable) recoveredErrorStack.get(recoveredErrorStack.size() 
- 1)).getMessage();
-    }
-
-    /**
-     * Tells if we are inside an <tt>#attempt</tt> block (but before 
<tt>#recover</tt>). This can be useful for
-     * {@link TemplateExceptionHandler}-s, as then they may don't want to 
print the error to the output, as
-     * <tt>#attempt</tt> will roll it back anyway.
-     * 
-     * @since 2.3.20
-     */
-    public boolean isInAttemptBlock() {
-        return inAttemptBlock;
-    }
-
-    /**
-     * Used for {@code #nested}.
-     */
-    void invokeNestedContent(BodyInstruction.Context bodyCtx) throws 
TemplateException, IOException {
-        Macro.Context invokingMacroContext = getCurrentMacroContext();
-        LocalContextStack prevLocalContextStack = localContextStack;
-        TemplateElement[] nestedContentBuffer = 
invokingMacroContext.nestedContentBuffer;
-        if (nestedContentBuffer != null) {
-            currentMacroContext = invokingMacroContext.prevMacroContext;
-            currentNamespace = invokingMacroContext.nestedContentNamespace;
-
-            localContextStack = invokingMacroContext.prevLocalContextStack;
-            if (invokingMacroContext.nestedContentParameterNames != null) {
-                pushLocalContext(bodyCtx);
-            }
-            try {
-                visit(nestedContentBuffer);
-            } finally {
-                if (invokingMacroContext.nestedContentParameterNames != null) {
-                    localContextStack.pop();
-                }
-                currentMacroContext = invokingMacroContext;
-                currentNamespace = 
getMacroNamespace(invokingMacroContext.getMacro());
-                localContextStack = prevLocalContextStack;
-            }
-        }
-    }
-
-    /**
-     * "visit" an IteratorBlock
-     */
-    boolean visitIteratorBlock(IteratorBlock.IterationContext ictxt)
-            throws TemplateException, IOException {
-        pushLocalContext(ictxt);
-        try {
-            return ictxt.accept(this);
-        } catch (TemplateException te) {
-            handleTemplateException(te);
-            return true;
-        } finally {
-            localContextStack.pop();
-        }
-    }
-
-    /**
-     * Used for {@code #visit} and {@code #recurse}.
-     */
-    void invokeNodeHandlerFor(TemplateNodeModel node, TemplateSequenceModel 
namespaces)
-            throws TemplateException, IOException {
-        if (nodeNamespaces == null) {
-            SimpleSequence ss = new SimpleSequence(1);
-            ss.add(currentNamespace);
-            nodeNamespaces = ss;
-        }
-        int prevNodeNamespaceIndex = nodeNamespaceIndex;
-        String prevNodeName = currentNodeName;
-        String prevNodeNS = currentNodeNS;
-        TemplateSequenceModel prevNodeNamespaces = nodeNamespaces;
-        TemplateNodeModel prevVisitorNode = currentVisitorNode;
-        currentVisitorNode = node;
-        if (namespaces != null) {
-            nodeNamespaces = namespaces;
-        }
-        try {
-            TemplateModel macroOrTransform = getNodeProcessor(node);
-            if (macroOrTransform instanceof Macro) {
-                invoke((Macro) macroOrTransform, null, null, null, null);
-            } else if (macroOrTransform instanceof TemplateTransformModel) {
-                visitAndTransform(null, (TemplateTransformModel) 
macroOrTransform, null);
-            } else {
-                String nodeType = node.getNodeType();
-                if (nodeType != null) {
-                    // If the node's type is 'text', we just output it.
-                    if ((nodeType.equals("text") && node instanceof 
TemplateScalarModel)) {
-                        out.write(((TemplateScalarModel) node).getAsString());
-                    } else if (nodeType.equals("document")) {
-                        recurse(node, namespaces);
-                    }
-                    // We complain here, unless the node's type is 'pi', or 
"comment" or "document_type", in which case
-                    // we just ignore it.
-                    else if (!nodeType.equals("pi")
-                            && !nodeType.equals("comment")
-                            && !nodeType.equals("document_type")) {
-                        throw new _MiscTemplateException(
-                                this, noNodeHandlerDefinedDescription(node, 
node.getNodeNamespace(), nodeType));
-                    }
-                } else {
-                    throw new _MiscTemplateException(
-                            this, noNodeHandlerDefinedDescription(node, 
node.getNodeNamespace(), "default"));
-                }
-            }
-        } finally {
-            currentVisitorNode = prevVisitorNode;
-            nodeNamespaceIndex = prevNodeNamespaceIndex;
-            currentNodeName = prevNodeName;
-            currentNodeNS = prevNodeNS;
-            nodeNamespaces = prevNodeNamespaces;
-        }
-    }
-
-    private Object[] noNodeHandlerDefinedDescription(
-            TemplateNodeModel node, String ns, String nodeType)
-                    throws TemplateModelException {
-        String nsPrefix;
-        if (ns != null) {
-            if (ns.length() > 0) {
-                nsPrefix = " and namespace ";
-            } else {
-                nsPrefix = " and no namespace";
-            }
-        } else {
-            nsPrefix = "";
-            ns = "";
-        }
-        return new Object[] { "No macro or directive is defined for node named 
",
-                new _DelayedJQuote(node.getNodeName()), nsPrefix, ns,
-                ", and there is no fallback handler called @", nodeType, " 
either." };
-    }
-
-    void fallback() throws TemplateException, IOException {
-        TemplateModel macroOrTransform = getNodeProcessor(currentNodeName, 
currentNodeNS, nodeNamespaceIndex);
-        if (macroOrTransform instanceof Macro) {
-            invoke((Macro) macroOrTransform, null, null, null, null);
-        } else if (macroOrTransform instanceof TemplateTransformModel) {
-            visitAndTransform(null, (TemplateTransformModel) macroOrTransform, 
null);
-        }
-    }
-
-    /**
-     * Calls the macro or function with the given arguments and nested block.
-     */
-    void invoke(Macro macro,
-            Map namedArgs, List positionalArgs,
-            List bodyParameterNames, TemplateElement[] childBuffer) throws 
TemplateException, IOException {
-        if (macro == Macro.DO_NOTHING_MACRO) {
-            return;
-        }
-
-        pushElement(macro);
-        try {
-            final Macro.Context macroCtx = macro.new Context(this, 
childBuffer, bodyParameterNames);
-            setMacroContextLocalsFromArguments(macroCtx, macro, namedArgs, 
positionalArgs);
-
-            final Macro.Context prevMacroCtx = currentMacroContext;
-            currentMacroContext = macroCtx;
-
-            final LocalContextStack prevLocalContextStack = localContextStack;
-            localContextStack = null;
-
-            final Namespace prevNamespace = currentNamespace;
-            currentNamespace = (Namespace) macroToNamespaceLookup.get(macro);
-
-            try {
-                macroCtx.sanityCheck(this);
-                visit(macro.getChildBuffer());
-            } catch (ReturnInstruction.Return re) {
-                // Not an error, just a <#return>
-            } catch (TemplateException te) {
-                handleTemplateException(te);
-            } finally {
-                currentMacroContext = prevMacroCtx;
-                localContextStack = prevLocalContextStack;
-                currentNamespace = prevNamespace;
-            }
-        } finally {
-            popElement();
-        }
-    }
-
-    /**
-     * Sets the local variables corresponding to the macro call arguments in 
the macro context.
-     */
-    private void setMacroContextLocalsFromArguments(
-            final Macro.Context macroCtx,
-            final Macro macro,
-            final Map namedArgs, final List positionalArgs) throws 
TemplateException, _MiscTemplateException {
-        String catchAllParamName = macro.getCatchAll();
-        if (namedArgs != null) {
-            final SimpleHash catchAllParamValue;
-            if (catchAllParamName != null) {
-                catchAllParamValue = new SimpleHash((ObjectWrapper) null);
-                macroCtx.setLocalVar(catchAllParamName, catchAllParamValue);
-            } else {
-                catchAllParamValue = null;
-            }
-
-             for (Map.Entry argNameAndValExp : (Set<Map.Entry>) 
namedArgs.entrySet()) {
-                final String argName = (String) argNameAndValExp.getKey();
-                final boolean isArgNameDeclared = macro.hasArgNamed(argName);
-                if (isArgNameDeclared || catchAllParamName != null) {
-                    Expression argValueExp = (Expression) 
argNameAndValExp.getValue();
-                    TemplateModel argValue = argValueExp.eval(this);
-                    if (isArgNameDeclared) {
-                        macroCtx.setLocalVar(argName, argValue);
-                    } else {
-                        catchAllParamValue.put(argName, argValue);
-                    }
-                } else {
-                    throw new _MiscTemplateException(this,
-                            (macro.isFunction() ? "Function " : "Macro "), new 
_DelayedJQuote(macro.getName()),
-                            " has no parameter with name ", new 
_DelayedJQuote(argName), ".");
-                }
-            }
-        } else if (positionalArgs != null) {
-            final SimpleSequence catchAllParamValue;
-            if (catchAllParamName != null) {
-                catchAllParamValue = new SimpleSequence((ObjectWrapper) null);
-                macroCtx.setLocalVar(catchAllParamName, catchAllParamValue);
-            } else {
-                catchAllParamValue = null;
-            }
-
-            String[] argNames = macro.getArgumentNamesInternal();
-            final int argsCnt = positionalArgs.size();
-            if (argNames.length < argsCnt && catchAllParamName == null) {
-                throw new _MiscTemplateException(this,
-                        (macro.isFunction() ? "Function " : "Macro "), new 
_DelayedJQuote(macro.getName()),
-                        " only accepts ", new 
_DelayedToString(argNames.length), " parameters, but got ",
-                        new _DelayedToString(argsCnt), ".");
-            }
-            for (int i = 0; i < argsCnt; i++) {
-                Expression argValueExp = (Expression) positionalArgs.get(i);
-                TemplateModel argValue = argValueExp.eval(this);
-                try {
-                    if (i < argNames.length) {
-                        String argName = argNames[i];
-                        macroCtx.setLocalVar(argName, argValue);
-                    } else {
-                        catchAllParamValue.add(argValue);
-                    }
-                } catch (RuntimeException re) {
-                    throw new _MiscTemplateException(re, this);
-                }
-            }
-        }
-    }
-
-    /**
-     * Defines the given macro in the current namespace (doesn't call it).
-     */
-    void visitMacroDef(Macro macro) {
-        macroToNamespaceLookup.put(macro, currentNamespace);
-        currentNamespace.put(macro.getName(), macro);
-    }
-
-    Namespace getMacroNamespace(Macro macro) {
-        return (Namespace) macroToNamespaceLookup.get(macro);
-    }
-
-    void recurse(TemplateNodeModel node, TemplateSequenceModel namespaces)
-            throws TemplateException, IOException {
-        if (node == null) {
-            node = getCurrentVisitorNode();
-            if (node == null) {
-                throw new _TemplateModelException(
-                        "The target node of recursion is missing or null.");
-            }
-        }
-        TemplateSequenceModel children = node.getChildNodes();
-        if (children == null) return;
-        for (int i = 0; i < children.size(); i++) {
-            TemplateNodeModel child = (TemplateNodeModel) children.get(i);
-            if (child != null) {
-                invokeNodeHandlerFor(child, namespaces);
-            }
-        }
-    }
-
-    Macro.Context getCurrentMacroContext() {
-        return currentMacroContext;
-    }
-
-    private void handleTemplateException(TemplateException templateException)
-            throws TemplateException {
-        // Logic to prevent double-handling of the exception in
-        // nested visit() calls.
-        if (lastThrowable == templateException) {
-            throw templateException;
-        }
-        lastThrowable = templateException;
-
-        // Log the exception, if logTemplateExceptions isn't false. However, 
even if it's false, if we are inside
-        // an #attempt block, it has to be logged, as it certainly won't 
bubble up to the caller of FreeMarker.
-        if (LOG.isErrorEnabled() && (isInAttemptBlock() || 
getLogTemplateExceptions())) {
-            LOG.error("Error executing FreeMarker template", 
templateException);
-        }
-
-        // Stop exception is not passed to the handler, but
-        // explicitly rethrown.
-        if (templateException instanceof StopException) {
-            throw templateException;
-        }
-
-        // Finally, pass the exception to the handler
-        
getTemplateExceptionHandler().handleTemplateException(templateException, this, 
out);
-    }
-
-    @Override
-    public void setTemplateExceptionHandler(TemplateExceptionHandler 
templateExceptionHandler) {
-        super.setTemplateExceptionHandler(templateExceptionHandler);
-        lastThrowable = null;
-    }
-
-    @Override
-    public void setLocale(Locale locale) {
-        Locale prevLocale = getLocale();
-        super.setLocale(locale);
-        if (!locale.equals(prevLocale)) {
-            cachedTemplateNumberFormats = null;
-            if (cachedTemplateNumberFormat != null && 
cachedTemplateNumberFormat.isLocaleBound()) {
-                cachedTemplateNumberFormat = null;
-            }
-
-            if (cachedTempDateFormatArray != null) {
-                for (int i = 0; i < CACHED_TDFS_LENGTH; i++) {
-                    final TemplateDateFormat f = cachedTempDateFormatArray[i];
-                    if (f != null && f.isLocaleBound()) {
-                        cachedTempDateFormatArray[i] = null;
-                    }
-                }
-            }
-
-            cachedTempDateFormatsByFmtStrArray = null;
-
-            cachedCollator = null;
-        }
-    }
-
-    @Override
-    public void setTimeZone(TimeZone timeZone) {
-        TimeZone prevTimeZone = getTimeZone();
-        super.setTimeZone(timeZone);
-
-        if (!timeZone.equals(prevTimeZone)) {
-            if (cachedTempDateFormatArray != null) {
-                for (int i = 0; i < CACHED_TDFS_SQL_D_T_TZ_OFFS; i++) {
-                    TemplateDateFormat f = cachedTempDateFormatArray[i];
-                    if (f != null && f.isTimeZoneBound()) {
-                        cachedTempDateFormatArray[i] = null;
-                    }
-                }
-            }
-            if (cachedTempDateFormatsByFmtStrArray != null) {
-                for (int i = 0; i < CACHED_TDFS_SQL_D_T_TZ_OFFS; i++) {
-                    cachedTempDateFormatsByFmtStrArray[i] = null;
-                }
-            }
-
-            cachedSQLDateAndTimeTimeZoneSameAsNormal = null;
-        }
-    }
-
-    @Override
-    public void setSQLDateAndTimeTimeZone(TimeZone timeZone) {
-        TimeZone prevTimeZone = getSQLDateAndTimeTimeZone();
-        super.setSQLDateAndTimeTimeZone(timeZone);
-
-        if (!nullSafeEquals(timeZone, prevTimeZone)) {
-            if (cachedTempDateFormatArray != null) {
-                for (int i = CACHED_TDFS_SQL_D_T_TZ_OFFS; i < 
CACHED_TDFS_LENGTH; i++) {
-                    TemplateDateFormat format = cachedTempDateFormatArray[i];
-                    if (format != null && format.isTimeZoneBound()) {
-                        cachedTempDateFormatArray[i] = null;
-                    }
-                }
-            }
-            if (cachedTempDateFormatsByFmtStrArray != null) {
-                for (int i = CACHED_TDFS_SQL_D_T_TZ_OFFS; i < 
CACHED_TDFS_LENGTH; i++) {
-                    cachedTempDateFormatsByFmtStrArray[i] = null;
-                }
-            }
-
-            cachedSQLDateAndTimeTimeZoneSameAsNormal = null;
-        }
-    }
-
-    // Replace with Objects.equals in Java 7
-    private static boolean nullSafeEquals(Object o1, Object o2) {
-        if (o1 == o2) return true;
-        if (o1 == null || o2 == null) return false;
-        return o1.equals(o2);
-    }
-
-    /**
-     * Tells if the same concrete time zone is used for SQL date-only and 
time-only values as for other
-     * date/time/date-time values.
-     */
-    boolean isSQLDateAndTimeTimeZoneSameAsNormal() {
-        if (cachedSQLDateAndTimeTimeZoneSameAsNormal == null) {
-            cachedSQLDateAndTimeTimeZoneSameAsNormal = Boolean.valueOf(
-                    getSQLDateAndTimeTimeZone() == null
-                            || 
getSQLDateAndTimeTimeZone().equals(getTimeZone()));
-        }
-        return cachedSQLDateAndTimeTimeZoneSameAsNormal.booleanValue();
-    }
-
-    @Override
-    public void setURLEscapingCharset(String urlEscapingCharset) {
-        cachedURLEscapingCharsetSet = false;
-        super.setURLEscapingCharset(urlEscapingCharset);
-    }
-
-    /*
-     * Note that altough it's not allowed to set this setting with the 
<tt>setting</tt> directive, it still must be
-     * allowed to set it from Java code while the template executes, since 
some frameworks allow templates to actually
-     * change the output encoding on-the-fly.
-     */
-    @Override
-    public void setOutputEncoding(String outputEncoding) {
-        cachedURLEscapingCharsetSet = false;
-        super.setOutputEncoding(outputEncoding);
-    }
-
-    /**
-     * Returns the name of the charset that should be used for URL encoding. 
This will be <code>null</code> if the
-     * information is not available. The function caches the return value, so 
it's quick to call it repeatedly.
-     */
-    String getEffectiveURLEscapingCharset() {
-        if (!cachedURLEscapingCharsetSet) {
-            cachedURLEscapingCharset = getURLEscapingCharset();
-            if (cachedURLEscapingCharset == null) {
-                cachedURLEscapingCharset = getOutputEncoding();
-            }
-            cachedURLEscapingCharsetSet = true;
-        }
-        return cachedURLEscapingCharset;
-    }
-
-    Collator getCollator() {
-        if (cachedCollator == null) {
-            cachedCollator = Collator.getInstance(getLocale());
-        }
-        return cachedCollator;
-    }
-
-    /**
-     * Compares two {@link TemplateModel}-s according the rules of the FTL 
"==" operator.
-     * 
-     * @since 2.3.20
-     */
-    public boolean applyEqualsOperator(TemplateModel leftValue, TemplateModel 
rightValue)
-            throws TemplateException {
-        return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_EQUALS, rightValue, 
this);
-    }
-
-    /**
-     * Compares two {@link TemplateModel}-s according the rules of the FTL 
"==" operator, except that if the two types
-     * are incompatible, they are treated as non-equal instead of throwing an 
exception. Comparing dates of different
-     * types (date-only VS time-only VS date-time) will still throw an 
exception, however.
-     * 
-     * @since 2.3.20
-     */
-    public boolean applyEqualsOperatorLenient(TemplateModel leftValue, 
TemplateModel rightValue)
-            throws TemplateException {
-        return EvalUtil.compareLenient(leftValue, EvalUtil.CMP_OP_EQUALS, 
rightValue, this);
-    }
-
-    /**
-     * Compares two {@link TemplateModel}-s according the rules of the FTL 
"&lt;" operator.
-     * 
-     * @since 2.3.20
-     */
-    public boolean applyLessThanOperator(TemplateModel leftValue, 
TemplateModel rightValue)
-            throws TemplateException {
-        return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_LESS_THAN, 
rightValue, this);
-    }
-
-    /**
-     * Compares two {@link TemplateModel}-s according the rules of the FTL 
"&lt;" operator.
-     * 
-     * @since 2.3.20
-     */
-    public boolean applyLessThanOrEqualsOperator(TemplateModel leftValue, 
TemplateModel rightValue)
-            throws TemplateException {
-        return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_LESS_THAN_EQUALS, 
rightValue, this);
-    }
-
-    /**
-     * Compares two {@link TemplateModel}-s according the rules of the FTL 
"&gt;" operator.
-     * 
-     * @since 2.3.20
-     */
-    public boolean applyGreaterThanOperator(TemplateModel leftValue, 
TemplateModel rightValue)
-            throws TemplateException {
-        return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_GREATER_THAN, 
rightValue, this);
-    }
-
-    /**
-     * Compares two {@link TemplateModel}-s according the rules of the FTL 
"&gt;=" operator.
-     * 
-     * @since 2.3.20
-     */
-    public boolean applyWithGreaterThanOrEqualsOperator(TemplateModel 
leftValue, TemplateModel rightValue)
-            throws TemplateException {
-        return EvalUtil.compare(leftValue, 
EvalUtil.CMP_OP_GREATER_THAN_EQUALS, rightValue, this);
-    }
-
-    public void setOut(Writer out) {
-        this.out = out;
-    }
-
-    public Writer getOut() {
-        return out;
-    }
-
-    @Override
-    public void setNumberFormat(String formatName) {
-        super.setNumberFormat(formatName);
-        cachedTemplateNumberFormat = null;
-    }
-
-    /**
-     * Format number with the default number format.
-     * 
-     * @param exp
-     *            The blamed expression if an error occurs; it's only needed 
for better error messages
-     */
-    String formatNumberToPlainText(TemplateNumberModel number, Expression exp, 
boolean useTempModelExc)
-            throws TemplateException {
-        return formatNumberToPlainText(number, getTemplateNumberFormat(exp, 
useTempModelExc), exp, useTempModelExc);
-    }
-
-    /**
-     * Format number with the number format specified as the parameter, with 
the current locale.
-     * 
-     * @param exp
-     *            The blamed expression if an error occurs; it's only needed 
for better error messages
-     */
-    String formatNumberToPlainText(
-            TemplateNumberModel number, TemplateNumberFormat format, 
Expression exp,
-            boolean useTempModelExc)
-            throws TemplateException {
-        try {
-            return 
EvalUtil.assertFormatResultNotNull(format.formatToPlainText(number));
-        } catch (TemplateValueFormatException e) {
-            throw MessageUtil.newCantFormatNumberException(format, exp, e, 
useTempModelExc);
-        }
-    }
-
-    /**
-     * Format number with the number format specified as the parameter, with 
the current locale.
-     * 
-     * @param exp
-     *            The blamed expression if an error occurs; it's only needed 
for better error messages
-     */
-    String formatNumberToPlainText(Number number, 
BackwardCompatibleTemplateNumberFormat format, Expression exp)
-            throws TemplateModelException, _MiscTemplateException {
-        try {
-            return format.format(number);
-        } catch (UnformattableValueException e) {
-            throw new _MiscTemplateException(exp, e, this,
-                    "Failed to format number with ", new 
_DelayedJQuote(format.getDescription()), ": ",
-                    e.getMessage());
-        }
-    }
-
-    /**
-     * Returns the current number format ({@link #getNumberFormat()}) as 
{@link TemplateNumberFormat}.
-     * 
-     * <p>
-     * Performance notes: The result is stored for reuse, so calling this 
method frequently is usually not a problem.
-     * However, at least as of this writing (2.3.24), changing the current 
locale {@link #setLocale(Locale)} or changing
-     * the current number format ({@link #setNumberFormat(String)}) will drop 
the stored value, so it will have to be
-     * recalculated.
-     * 
-     * @since 2.3.24
-     */
-    public TemplateNumberFormat getTemplateNumberFormat() throws 
TemplateValueFormatException {
-        TemplateNumberFormat format = cachedTemplateNumberFormat;
-        if (format == null) {
-            format = getTemplateNumberFormat(getNumberFormat(), false);
-            cachedTemplateNumberFormat = format;
-        }
-        return format;
-    }
-
-    /**
-     * Returns the number format as {@link TemplateNumberFormat} for the given 
format string and the current locale.
-     * (The current locale is the locale returned by {@link #getLocale()}.) 
Note that the result will be cached in the
-     * {@link Environment} instance (though at least in 2.3.24 the cache will 
be flushed if the current locale of the
-     * {@link Environment} is changed).
-     * 
-     * @param formatString
-     *            A string that you could also use as the value of the {@code 
numberFormat} configuration setting. Can't
-     *            be {@code null}.
-     * 
-     * @since 2.3.24
-     */
-    public TemplateNumberFormat getTemplateNumberFormat(String formatString) 
throws TemplateValueFormatException {
-        return getTemplateNumberFormat(formatString, true);
-    }
-
-    /**
-     * Returns the number format as {@link TemplateNumberFormat}, for the 
given format string and locale. To get a
-     * number format for the current locale, use {@link 
#getTemplateNumberFormat(String)} instead.
-     * 
-     * <p>
-     * Note on performance (which was true at least for 2.3.24): Unless the 
locale happens to be equal to the current
-     * locale, the {@link Environment}-level format cache can't be used, so 
the format string has to be parsed and the
-     * matching factory has to be get an invoked, which is much more expensive 
than getting the format from the cache.
-     * Thus the returned format should be stored by the caller for later reuse 
(but only within the current thread and
-     * in relation to the current {@link Environment}), if it will be needed 
frequently.
-     * 
-     * @param formatString
-     *            A string that you could also use as the value of the {@code 
numberFormat} configuration setting.
-     * @param locale
-     *            The locale of the number format; not {@code null}.
-     * 
-     * @since 2.3.24
-     */
-    public TemplateNumberFormat getTemplateNumberFormat(String formatString, 
Locale locale)
-            throws TemplateValueFormatException {
-        if (locale.equals(getLocale())) {
-            getTemplateNumberFormat(formatString);
-        }
-
-        return getTemplateNumberFormatWithoutCache(formatString, locale);
-    }
-
-    /**
-     * Convenience wrapper around {@link #getTemplateNumberFormat()} to be 
called during expression evaluation.
-     */
-    TemplateNumberFormat getTemplateNumberFormat(Expression exp, boolean 
useTempModelExc) throws TemplateException {
-        TemplateNumberFormat format;
-        try {
-            format = getTemplateNumberFormat();
-        } catch (TemplateValueFormatException e) {
-            _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "Failed to get number format object for the current number 
format string, ",
-                    new _DelayedJQuote(getNumberFormat()), ": ", 
e.getMessage())
-                    .blame(exp); 
-            throw useTempModelExc
-                    ? new _TemplateModelException(e, this, desc) : new 
_MiscTemplateException(e, this, desc);
-        }
-        return format;
-    }
-
-    /**
-     * Convenience wrapper around {@link #getTemplateNumberFormat(String)} to 
be called during expression evaluation.
-     * 
-     * @param exp
-     *            The blamed expression if an error occurs; it's only needed 
for better error messages
-     */
-    TemplateNumberFormat getTemplateNumberFormat(String formatString, 
Expression exp, boolean useTempModelExc)
-            throws TemplateException {
-        TemplateNumberFormat format;
-        try {
-            format = getTemplateNumberFormat(formatString);
-        } catch (TemplateValueFormatException e) {
-            _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "Failed to get number format object for the ", new 
_DelayedJQuote(formatString),
-                    " number format string: ", e.getMessage())
-                    .blame(exp);
-            throw useTempModelExc
-                    ? new _TemplateModelException(e, this, desc) : new 
_MiscTemplateException(e, this, desc);
-        }
-        return format;
-    }
-
-    /**
-     * Gets the {@link TemplateNumberFormat} <em>for the current locale</em>.
-     * 
-     * @param formatString
-     *            Not {@code null}
-     * @param cacheResult
-     *            If the results should stored in the {@link 
Environment}-level cache. It will still try to get the
-     *            result from the cache regardless of this parameter.
-     */
-    private TemplateNumberFormat getTemplateNumberFormat(String formatString, 
boolean cacheResult)
-            throws TemplateValueFormatException {
-        if (cachedTemplateNumberFormats == null) {
-            if (cacheResult) {
-                cachedTemplateNumberFormats = new HashMap<>();
-            }
-        } else {
-            TemplateNumberFormat format = 
cachedTemplateNumberFormats.get(formatString);
-            if (format != null) {
-                return format;
-            }
-        }
-
-        TemplateNumberFormat format = 
getTemplateNumberFormatWithoutCache(formatString, getLocale());
-
-        if (cacheResult) {
-            cachedTemplateNumberFormats.put(formatString, format);
-        }
-        return format;
-    }
-
-    /**
-     * Returns the {@link TemplateNumberFormat} for the given parameters 
without using the {@link Environment}-level
-     * cache. Of course, the {@link TemplateNumberFormatFactory} involved 
might still uses its own cache.
-     * 
-     * @param formatString
-     *            Not {@code null}
-     * @param locale
-     *            Not {@code null}
-     */
-    private TemplateNumberFormat getTemplateNumberFormatWithoutCache(String 
formatString, Locale locale)
-            throws TemplateValueFormatException {
-        int formatStringLen = formatString.length();
-        if (formatStringLen > 1
-                && formatString.charAt(0) == '@'
-                && Character.isLetter(formatString.charAt(1))) {
-            final String name;
-            final String params;
-            {
-                int endIdx;
-                findParamsStart: for (endIdx = 1; endIdx < formatStringLen; 
endIdx++) {
-                    char c = formatString.charAt(endIdx);
-                    if (c == ' ' || c == '_') {
-                        break findParamsStart;
-                    }
-                }
-                name = formatString.substring(1, endIdx);
-                params = endIdx < formatStringLen ? 
formatString.substring(endIdx + 1) : "";
-            }
-
-            TemplateNumberFormatFactory formatFactory = 
getCustomNumberFormat(name);
-            if (formatFactory == null) {
-                throw new UndefinedCustomFormatException(
-                        "No custom number format was defined with name " + 
_StringUtil.jQuote(name));
-            }
-
-            return formatFactory.get(params, locale, this);
-        } else {
-            return JavaTemplateNumberFormatFactory.INSTANCE.get(formatString, 
locale, this);
-        }
-    }
-
-    /**
-     * Returns the {@link NumberFormat} used for the <tt>c</tt> built-in. This 
is always US English
-     * <code>"0.################"</code>, without grouping and without 
superfluous decimal separator.
-     */
-    public NumberFormat getCNumberFormat() {
-        // It can't be cached in a static field, because DecimalFormat-s aren't
-        // thread-safe.
-        if (cNumberFormat == null) {
-            cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT.clone();
-        }
-        return cNumberFormat;
-    }
-
-    @Override
-    public void setTimeFormat(String timeFormat) {
-        String prevTimeFormat = getTimeFormat();
-        super.setTimeFormat(timeFormat);
-        if (!timeFormat.equals(prevTimeFormat)) {
-            if (cachedTempDateFormatArray != null) {
-                for (int i = 0; i < CACHED_TDFS_LENGTH; i += 
CACHED_TDFS_ZONELESS_INPUT_OFFS) {
-                    cachedTempDateFormatArray[i + TemplateDateModel.TIME] = 
null;
-                }
-            }
-        }
-    }
-
-    @Override
-    public void setDateFormat(String dateFormat) {
-        String prevDateFormat = getDateFormat();
-        super.setDateFormat(dateFormat);
-        if (!dateFormat.equals(prevDateFormat)) {
-            if (cachedTempDateFormatArray != null) {
-                for (int i = 0; i < CACHED_TDFS_LENGTH; i += 
CACHED_TDFS_ZONELESS_INPUT_OFFS) {
-                    cachedTempDateFormatArray[i + TemplateDateModel.DATE] = 
null;
-                }
-            }
-        }
-    }
-
-    @Override
-    public void setDateTimeFormat(String dateTimeFormat) {
-        String prevDateTimeFormat = getDateTimeFormat();
-        super.setDateTimeFormat(dateTimeFormat);
-        if (!dateTimeFormat.equals(prevDateTimeFormat)) {
-            if (cachedTempDateFormatArray != null) {
-                for (int i = 0; i < CACHED_TDFS_LENGTH; i += 
CACHED_TDFS_ZONELESS_INPUT_OFFS) {
-                    cachedTempDateFormatArray[i + TemplateDateModel.DATETIME] 
= null;
-                }
-            }
-        }
-    }
-
-    public Configuration getConfiguration() {
-        return configuration;
-    }
-
-    TemplateModel getLastReturnValue() {
-        return lastReturnValue;
-    }
-
-    void setLastReturnValue(TemplateModel lastReturnValue) {
-        this.lastReturnValue = lastReturnValue;
-    }
-
-    void clearLastReturnValue() {
-        lastReturnValue = null;
-    }
-
-    /**
-     * @param tdmSourceExpr
-     *            The blamed expression if an error occurs; only used for 
error messages.
-     */
-    String formatDateToPlainText(TemplateDateModel tdm, Expression 
tdmSourceExpr,
-            boolean useTempModelExc) throws TemplateException {
-        TemplateDateFormat format = getTemplateDateFormat(tdm, tdmSourceExpr, 
useTempModelExc);
-        
-        try {
-            return 
EvalUtil.assertFormatResultNotNull(format.formatToPlainText(tdm));
-        } catch (TemplateValueFormatException e) {
-            throw MessageUtil.newCantFormatDateException(format, 
tdmSourceExpr, e, useTempModelExc);
-        }
-    }
-
-    /**
-     * @param blamedDateSourceExp
-     *            The blamed expression if an error occurs; only used for 
error messages.
-     * @param blamedFormatterExp
-     *            The blamed expression if an error occurs; only used for 
error messages.
-     */
-    String formatDateToPlainText(TemplateDateModel tdm, String formatString,
-            Expression blamedDateSourceExp, Expression blamedFormatterExp,
-            boolean useTempModelExc) throws TemplateException {
-        Date date = EvalUtil.modelToDate(tdm, blamedDateSourceExp);
-        
-        TemplateDateFormat format = getTemplateDateFormat(
-                formatString, tdm.getDateType(), date.getClass(),
-                blamedDateSourceExp, blamedFormatterExp,
-                useTempModelExc);
-        
-        try {
-            return 
EvalUtil.assertFormatResultNotNull(format.formatToPlainText(tdm));
-        } catch (TemplateValueFormatException e) {
-            throw MessageUtil.newCantFormatDateException(format, 
blamedDateSourceExp, e, useTempModelExc);
-        }
-    }
-
-    /**
-     * Gets a {@link TemplateDateFormat} using the date/time/datetime format 
settings and the current locale and time
-     * zone. (The current locale is the locale returned by {@link 
#getLocale()}. The current time zone is
-     * {@link #getTimeZone()} or {@link #getSQLDateAndTimeTimeZone()}).
-     * 
-     * @param dateType
-     *            The FTL date type; see the similar parameter of
-     *            {@link TemplateDateFormatFactory#get(String, int, Locale, 
TimeZone, boolean, Environment)}
-     * @param dateClass
-     *            The exact {@link Date} class, like {@link java.sql.Date} or 
{@link java.sql.Time}; this can influences
-     *            time zone selection. See also: {@link 
#setSQLDateAndTimeTimeZone(TimeZone)}
-     */
-    public TemplateDateFormat getTemplateDateFormat(int dateType, Class<? 
extends Date> dateClass)
-            throws TemplateValueFormatException {
-        boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
-        return getTemplateDateFormat(dateType, 
shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime);
-    }
-    
-    /**
-     * Gets a {@link TemplateDateFormat} for the specified format string and 
the current locale and time zone. (The
-     * current locale is the locale returned by {@link #getLocale()}. The 
current time zone is {@link #getTimeZone()} or
-     * {@link #getSQLDateAndTimeTimeZone()}).
-     * 
-     * <p>
-     * Note on performance: The result will be cached in the {@link 
Environment} instance. However, at least in 2.3.24
-     * the cached entries that depend on the current locale or the current 
time zone or the current date/time/datetime
-     * format of the {@link Environment} will be lost when those settings are 
changed.
-     * 
-     * @param formatString
-     *            Like {@code "iso m"} or {@code "dd.MM.yyyy HH:mm"} or {@code 
"@somethingCustom"} or
-     *            {@code "@somethingCustom params"}
-     * 
-     * @since 2.3.24
-     */
-    public TemplateDateFormat getTemplateDateFormat(
-            String formatString, int dateType, Class<? extends Date> dateClass)
-                    throws TemplateValueFormatException {
-        boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
-        return getTemplateDateFormat(
-                formatString, dateType,
-                shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime, 
true);
-    }
-
-    /**
-     * Like {@link #getTemplateDateFormat(String, int, Class)}, but allows you 
to use a different locale than the
-     * current one. If you want to use the current locale, use {@link 
#getTemplateDateFormat(String, int, Class)}
-     * instead.
-     * 
-     * <p>
-     * Performance notes regarding the locale and time zone parameters of
-     * {@link #getTemplateDateFormat(String, int, Locale, TimeZone, boolean)} 
apply.
-     * 
-     * @param locale
-     *            Can't be {@code null}; See the similar parameter of
-     *            {@link TemplateDateFormatFactory#get(String, int, Locale, 
TimeZone, boolean, Environment)}
-     * 
-     * @see #getTemplateDateFormat(String, int, Class)
-     * 
-     * @since 2.4
-     */
-    public TemplateDateFormat getTemplateDateFormat(
-            String formatString,
-            int dateType, Class<? extends Date> dateClass,
-            Locale locale)
-                    throws TemplateValueFormatException {
-        boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
-        boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
-        return getTemplateDateFormat(
-                formatString,
-                dateType, locale, useSQLDTTZ ? getSQLDateAndTimeTimeZone() : 
getTimeZone(), isSQLDateOrTime);        
-    }
-
-    /**
-     * Like {@link #getTemplateDateFormat(String, int, Class)}, but allows you 
to use a different locale and time zone
-     * than the current one. If you want to use the current locale and time 
zone, use
-     * {@link #getTemplateDateFormat(String, int, Class)} instead.
-     * 
-     * <p>
-     * Performance notes regarding the locale and time zone parameters of
-     * {@link #getTemplateDateFormat(String, int, Locale, TimeZone, boolean)} 
apply.
-     * 
-     * @param timeZone
-     *            The {@link TimeZone} used if {@code dateClass} is not an SQL 
date-only or time-only type. Can't be
-     *            {@code null}.
-     * @param sqlDateAndTimeTimeZone
-     *            The {@link TimeZone} used if {@code dateClass} is an SQL 
date-only or time-only type. Can't be
-     *            {@code null}.
-     * 
-     * @see #getTemplateDateFormat(String, int, Class)
-     * 
-     * @since 2.4
-     */
-    public TemplateDateFormat getTemplateDateFormat(
-            String formatString,
-            int dateType, Class<? extends Date> dateClass,
-            Locale locale, TimeZone timeZone, TimeZone sqlDateAndTimeTimeZone)
-                    throws TemplateValueFormatException {
-        boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
-        boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
-        return getTemplateDateFormat(
-                formatString,
-                dateType, locale, useSQLDTTZ ? sqlDateAndTimeTimeZone : 
timeZone, isSQLDateOrTime);        
-    }
-    
-    /**
-     * Gets a {@link TemplateDateFormat} for the specified parameters. This is 
mostly meant to be used by
-     * {@link TemplateDateFormatFactory} implementations to delegate to a 
format based on a specific format string. It
-     * works well for that, as its parameters are the same low level values as 
the parameters of
-     * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, 
boolean, Environment)}. For other tasks
-     * consider the other overloads of this method.
-     * 
-     * <p>
-     * Note on performance (which was true at least for 2.3.24): Unless the 
locale happens to be equal to the current
-     * locale and the time zone with one of the current time zones ({@link 
#getTimeZone()} or
-     * {@link #getSQLDateAndTimeTimeZone()}), the {@link Environment}-level 
format cache can't be used, so the format
-     * string has to be parsed and the matching factory has to be get an 
invoked, which is much more expensive than
-     * getting the format from the cache. Thus the returned format should be 
stored by the caller for later reuse (but
-     * only within the current thread and in relation to the current {@link 
Environment}), if it will be needed
-     * frequently.
-     * 
-     * @param formatString
-     *            Like {@code "iso m"} or {@code "dd.MM.yyyy HH:mm"} or {@code 
"@somethingCustom"} or
-     *            {@code "@somethingCustom params"}
-     * @param dateType
-     *            The FTL date type; see the similar parameter of
-     *            {@link TemplateDateFormatFactory#get(String, int, Locale, 
TimeZone, boolean, Environment)}
-     * @param timeZone
-     *            Not {@code null}; See the similar parameter of
-     *            {@link TemplateDateFormatFactory#get(String, int, Locale, 
TimeZone, boolean, Environment)}
-     * @param locale
-     *            Not {@code null}; See the similar parameter of
-     *            {@link TemplateDateFormatFactory#get(String, int, Locale, 
TimeZone, boolean, Environment)}
-     * @param zonelessInput
-     *            See the similar parameter of
-     *            {@link TemplateDateFormatFactory#get(String, int, Locale, 
TimeZone, boolean, Environment)}
-     * 
-     * @since 2.3.24
-     */
-    public TemplateDateFormat getTemplateDateFormat(
-            String formatString,
-            int dateType, Locale locale, TimeZone timeZone, boolean 
zonelessInput)
-                    throws TemplateValueFormatException {
-        Locale currentLocale = getLocale();
-        if (locale.equals(currentLocale)) {
-            int equalCurrentTZ;
-            TimeZone currentTimeZone = getTimeZone();
-            if (timeZone.equals(currentTimeZone)) {
-                equalCurrentTZ = 1;
-            } else {
-                TimeZone currentSQLDTTimeZone = getSQLDateAndTimeTimeZone();
-                if (timeZone.equals(currentSQLDTTimeZone)) {
-                    equalCurrentTZ = 2;
-                } else {
-                    equalCurrentTZ = 0;
-                }
-            }
-            if (equalCurrentTZ != 0) {
-                return getTemplateDateFormat(formatString, dateType, 
equalCurrentTZ == 2, zonelessInput, true);
-            }
-            // Falls through
-        }
-        return getTemplateDateFormatWithoutCache(formatString, dateType, 
locale, timeZone, zonelessInput);
-    }
-    
-    TemplateDateFormat getTemplateDateFormat(TemplateDateModel tdm, Expression 
tdmSourceExpr, boolean useTempModelExc)
-            throws TemplateModelException, TemplateException {
-        Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr);
-        
-        TemplateDateFormat format = getTemplateDateFormat(
-                tdm.getDateType(), date.getClass(), tdmSourceExpr,
-                useTempModelExc);
-        return format;
-    }
-
-    /**
-     * Same as {@link #getTemplateDateFormat(int, Class)}, but translates the 
exceptions to {@link TemplateException}-s.
-     */
-    TemplateDateFormat getTemplateDateFormat(
-            int dateType, Class<? extends Date> dateClass, Expression 
blamedDateSourceExp, boolean useTempModelExc)
-                    throws TemplateException {
-        try {
-            return getTemplateDateFormat(dateType, dateClass);
-        } catch (UnknownDateTypeFormattingUnsupportedException e) {
-            throw 
MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
-        } catch (TemplateValueFormatException e) {
-            String settingName;
-            String settingValue;
-            switch (dateType) {
-            case TemplateDateModel.TIME:
-                settingName = Configurable.TIME_FORMAT_KEY;
-                settingValue = getTimeFormat();
-                break;
-            case TemplateDateModel.DATE:
-                settingName = Configurable.DATE_FORMAT_KEY;
-                settingValue = getDateFormat();
-                break;
-            case TemplateDateModel.DATETIME:
-                settingName = Configurable.DATETIME_FORMAT_KEY;
-                settingValue = getDateTimeFormat();
-                break;
-            default:
-                settingName = "???";
-                settingValue = "???";
-            }
-            
-            _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "The value of the \"", settingName,
-                    "\" FreeMarker configuration setting is a malformed 
date/time/datetime format string: ",
-                    new _DelayedJQuote(settingValue), ". Reason given: ",
-                    e.getMessage());                    
-            throw useTempModelExc ? new _TemplateModelException(e, desc) : new 
_MiscTemplateException(e, desc);
-        }
-    }
-
-    /**
-     * Same as {@link #getTemplateDateFormat(String, int, Class)}, but 
translates the exceptions to
-     * {@link TemplateException}-s.
-     */
-    TemplateDateFormat getTemplateDateFormat(
-            String formatString, int dateType, Class<? extends Date> dateClass,
-            Expression blamedDateSourceExp, Expression blamedFormatterExp,
-            boolean useTempModelExc)
-                    throws TemplateException {
-        try {
-            return getTemplateDateFormat(formatString, dateType, dateClass);
-        } catch (UnknownDateTypeFormattingUnsupportedException e) {
-            throw 
MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
-        } catch (TemplateValueFormatException e) {
-            _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "Can't create date/time/datetime format based on format 
string ",
-                    new _DelayedJQuote(formatString), ". Reason given: ",
-                    e.getMessage())
-                    .blame(blamedFormatterExp);
-            throw useTempModelExc ? new _TemplateModelException(e, desc) : new 
_MiscTemplateException(e, desc);
-        }
-    }
-
-    /**
-     * Used to get the {@link TemplateDateFormat} according the 
date/time/datetime format settings, for the current
-     * locale and time zone. See {@link #getTemplateDateFormat(String, int, 
Locale, TimeZone, boolean)} for the meaning
-     * of some of the parameters.
-     */
-    private TemplateDateFormat getTemplateDateFormat(int dateType, boolean 
useSQLDTTZ, boolean zonelessInput)
-            throws TemplateValueFormatException {
-        if (dateType == TemplateDateModel.UNKNOWN) {
-            throw new UnknownDateTypeFormattingUnsupportedException();
-        }
-        int cacheIdx = getTemplateDateFormatCacheArrayIndex(dateType, 
zonelessInput, useSQLDTTZ);
-        TemplateDateFormat[] cachedTempDateFormatArray = 
this.cachedTempDateFormatArray;
-        if (cachedTempDateFormatArray == null) {
-            cachedTempDateFormatArray = new 
TemplateDateFormat[CACHED_TDFS_LENGTH];
-            this.cachedTempDateFormatArray = cachedTempDateFormatArray;
-        }
-        TemplateDateFormat format = cachedTempDateFormatArray[cacheIdx];
-        if (format == null) {
-            final String formatString;
-            switch (dateType) {
-            case TemplateDateModel.TIME:
-                formatString = getTimeFormat();
-                break;
-            case TemplateDateModel.DATE:
-                formatString = getDateFormat();
-                break;
-            case TemplateDateModel.DATETIME:
-                formatString = getDateTimeFormat();
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid date type enum: " 
+ Integer.valueOf(dateType));
-            }
-
-            format = getTemplateDateFormat(formatString, dateType, useSQLDTTZ, 
zonelessInput, false);
-            
-            cachedTempDateFormatArray[cacheIdx] = format;
-        }
-        return format;
-    }
-
-    /**
-     * Used to get the {@link TemplateDateFormat} for the specified 
parameters, using the {@link Environment}-level
-     * cache. As the {@link Environment}-level cache currently only stores 
formats for the current locale and time zone,
-     * there's no parameter to specify those.
-     * 
-     * @param cacheResult
-     *            If the results should stored in the {@link 
Environment}-level cache. It will still try to get the
-     *            result from the cache regardless of this parameter.
-     */
-    private TemplateDateFormat getTemplateDateFormat(
-            String formatString, int dateType, boolean useSQLDTTimeZone, 
boolean zonelessInput,
-            boolean cacheResult)
-                    throws TemplateValueFormatException {
-        HashMap<String, TemplateDateFormat> cachedFormatsByFormatString;
-        readFromCache: do {
-            HashMap<String, TemplateDateFormat>[] 
cachedTempDateFormatsByFmtStrArray = this.cachedTempDateFormatsByFmtStrArray;
-            if (cachedTempDateFormatsByFmtStrArray == null) {
-                if (cacheResult) {
-                    cachedTempDateFormatsByFmtStrArray = new 
HashMap[CACHED_TDFS_LENGTH];
-                    this.cachedTempDateFormatsByFmtStrArray = 
cachedTempDateFormatsByFmtStrArray;
-                } else {
-                    cachedFormatsByFormatString = null;
-                    break readFromCache;
-                }
-            }
-
-            TemplateDateFormat format;
-            {
-                int cacheArrIdx = 
getTemplateDateFormatCacheArrayIndex(dateType, zonelessInput, useSQLDTTimeZone);
-                cachedFormatsByFormatString = 
cachedTempDateFormatsByFmtStrArray[cacheArrIdx];
-                if (cachedFormatsByFormatString == null) {
-                    if (cacheResult) {
-                        cachedFormatsByFormatString = new HashMap<>(4);
-                        cachedTempDateFormatsByFmtStrArray[cacheArrIdx] = 
cachedFormatsByFormatString;
-                        format = null;
-                    } else {
-                        break readFromCache;
-                    }
-                } else {
-                    format = cachedFormatsByFormatString.get(formatString);
-                }
-            }
-
-            if (format != null) {
-                return format;
-            }
-            // Cache miss; falls through
-        } while (false);
-
-        TemplateDateFormat format = getTemplateDateFormatWithoutCache(
-                formatString,
-                dateType, getLocale(), useSQLDTTimeZone ? 
getSQLDateAndTimeTimeZone() : getTimeZone(),
-                zonelessInput);
-        if (cacheResult) {
-            // We know here that cachedFormatsByFormatString != null
-            cachedFormatsByFormatString.put(formatString, format);
-        }
-        return format;
-    }
-
-    /**
-     * Returns the {@link TemplateDateFormat} for the given parameters without 
using the {@link Environment}-level
-     * cache. Of course, the {@link TemplateDateFormatFactory} involved might 
still uses its own cache, which can be
-     * global (class-loader-level) or {@link Environment}-level.
-     * 
-     * @param formatString
-     *            See the similar parameter of {@link 
TemplateDateFormatFactory#get}
-     * @param dateType
-     *            See the similar parameter of {@link 
TemplateDateFormatFactory#get}
-     * @param zonelessInput
-     *            See the similar parameter of {@link 
TemplateDateFormatFactory#get}
-     */
-    private TemplateDateFormat getTemplateDateFormatWithoutCache(
-            String formatString, int dateType, Locale locale, TimeZone 
timeZone, boolean zonelessInput)
-                    throws TemplateValueFormatException {
-        final int formatStringLen = formatString.length();
-        final String formatParams;
-
-        TemplateDateFormatFactory formatFactory;
-        char firstChar = formatStringLen != 0 ? formatString.charAt(0) : 0;
-
-        // As of Java 8, 'x' and 'i' (lower case) are illegal date format 
letters, so this is backward-compatible.
-        if (firstChar == 'x'
-                && formatStringLen > 1
-                && formatString.charAt(1) == 's') {
-            formatFactory = XSTemplateDateFormatFactory.INSTANCE;
-            formatParams = formatString; // for speed, we don't remove the 
prefix
-        } else if (firstChar == 'i'
-                && formatStringLen > 2
-                && formatString.charAt(1) == 's'
-                && formatString.charAt(2) == 'o') {
-            formatFactory = ISOTemplateDateFormatFactory.INSTANCE;
-            formatParams = formatString; // for speed, we don't remove the 
prefix
-        } else if (firstChar == '@'
-                && formatStringLen > 1
-                && Character.isLetter(formatString.charAt(1))) {
-            final String name;
-            {
-                int endIdx;
-                findParamsStart: for (endIdx = 1; endIdx < formatStringLen; 
endIdx++) {
-                    char c = formatString.charAt(endIdx);
-                    if (c == ' ' || c == '_') {
-                        break findParamsStart;
-                    }
-                }
-                name = formatString.substring(1, endIdx);
-                formatParams = endIdx < formatStringLen ? 
formatString.substring(endIdx + 1) : "";
-            }
-
-            formatFactory = getCustomDateFormat(name);
-            if (formatFactory == null) {
-                throw new UndefinedCustomFormatException(
-                        "No custom date format was defined with name " + 
_StringUtil.jQuote(name));
-            }
-        } else {
-            formatParams = formatString;
-            formatFactory = JavaTemplateDateFormatFactory.INSTANCE;
-        }
-
-        return formatFactory.get(formatParams, dateType, locale, timeZone,
-                zonelessInput, this);
-    }
-
-    boolean shouldUseSQLDTTZ(Class dateClass) {
-        // Attention! If you update this method, update all overloads of it!
-        return dateClass != Date.class // This pre-condition is only for speed
-                && !isSQLDateAndTimeTimeZoneSameAsNormal()
-                && isSQLDateOrTimeClass(dateClass);
-    }
-
-    private boolean shouldUseSQLDTTimeZone(boolean sqlDateOrTime) {
-        // Attention! If you update this method, update all overloads of it!
-        return sqlDateOrTime && !isSQLDateAndTimeTimeZoneSameAsNormal();
-    }
-
-    /**
-     * Tells if the given class is or is subclass of {@link java.sql.Date} or 
{@link java.sql.Time}.
-     */
-    private static boolean isSQLDateOrTimeClass(Class dateClass) {
-        // We do shortcuts for the most common cases.
-        return dateClass != java.util.Date.class
-                && (dateClass == java.sql.Date.class || dateClass == Time.class
-                        || (dateClass != Timestamp.class
-                                && 
(java.sql.Date.class.isAssignableFrom(dateClass)
-                                        || 
Time.class.isAssignableFrom(dateClass))));
-    }
-
-    private int getTemplateDateFormatCacheArrayIndex(int dateType, boolean 
zonelessInput, boolean sqlDTTZ) {
-        return dateType
-                + (zonelessInput ? CACHED_TDFS_ZONELESS_INPUT_OFFS : 0)
-                + (sqlDTTZ ? CACHED_TDFS_SQL_D_T_TZ_OFFS : 0);
-    }
-
-    /**
-     * Returns the {@link DateToISO8601CalendarFactory} used by the the "iso_" 
built-ins. Be careful when using this; it
-     * should only by used with
-     * {@link _DateUtil#dateToISO8601String(Date, boolean, boolean, boolean, 
int, TimeZone, DateToISO8601CalendarFactory)}
-     * and {@link _DateUtil#dateToXSString(Date, boolean, boolean, boolean, 
int, TimeZone, DateToISO8601CalendarFactory)}
-     * .
-     */
-    DateToISO8601CalendarFactory getISOBuiltInCalendarFactory() {
-        if (isoBuiltInCalendarFactory == null) {
-            isoBuiltInCalendarFactory = new 
_DateUtil.TrivialDateToISO8601CalendarFactory();
-        }
-        return isoBuiltInCalendarFactory;
-    }
-
-    TemplateTransformModel getTransform(Expression exp) throws 
TemplateException {
-        TemplateTransformModel ttm = null;
-        TemplateModel tm = exp.eval(this);
-        if (tm instanceof TemplateTransformModel) {
-            ttm = (TemplateTransformModel) tm;
-        } else if (exp instanceof Identifier) {
-            tm = configuration.getSharedVariable(exp.toString());
-            if (tm instanceof TemplateTransformModel) {
-                ttm = (TemplateTransformModel) tm;
-            }
-        }
-        return ttm;
-    }
-
-    /**
-     * Returns the loop or macro local variable corresponding to this variable 
name. Possibly null. (Note that the
-     * misnomer is kept for backward compatibility: loop variables are not 
local variables according to our
-     * terminology.)
-     */
-    public TemplateModel getLocalVariable(String name) throws 
TemplateModelException {
-        if (localContextStack != null) {
-            for (int i = localContextStack.size() - 1; i >= 0; i--) {
-                LocalContext lc = localContextStack.get(i);
-                TemplateModel tm = lc.getLocalVariable(name);
-                if (tm != null) {
-                    return tm;
-                }
-            }
-        }
-        return currentMacroContext == null ? null : 
currentMacroContext.getLocalVariable(name);
-    }
-
-    /**
-     * Returns the variable that is visible in this context, or {@code null} 
if the variable is not found. This is the
-     * correspondent to an FTL top-level variable reading expression. That is, 
it tries to find the the variable in this
-     * order:
-     * <ol>
-     * <li>An loop variable (if we're in a loop or user defined directive 
body) such as foo_has_next
-     * <li>A local variable (if we're in a macro)
-     * <li>A variable defined in the current namespace (say, via &lt;#assign 
...&gt;)
-     * <li>A variable defined globally (say, via &lt;#global ....&gt;)
-     * <li>Variable in the data model:
-     * <ol>
-     * <li>A variable in the root hash that was exposed to this rendering 
environment in the Template.process(...) call
-     * <li>A shared variable set in the configuration via a call to 
Configuration.setSharedVariable(...)
-     * </ol>
-     * </li>
-     * </ol>
-     */
-    public TemplateModel getVariable(String name) throws 
TemplateModelException {
-        TemplateModel result = getLocalVariable(name);
-        if (result == null) {
-            result = currentNamespace.get(name);
-        }
-        if (result == null) {
-            result = getGlobalVariable(name);
-        }
-        return result;
-    }
-
-    /**
-     * Returns the globally visible variable of the given name (or null). This 
is correspondent to FTL
-     * <code>.globals.<i>name</i></code>. This will first look at variables 
that were assigned globally via: &lt;#global
-     * ...&gt; and then at the data model exposed to the template.
-     */
-    public TemplateModel getGlobalVariable(String name) throws 
TemplateModelException {
-        TemplateModel result = globalNamespace.get(name);
-        if (result == null) {
-            result = rootDataModel.get(name);
-        }
-        if (result == null) {
-            result = configuration.getSharedVariable(name);
-        }
-        return result;
-    }
-
-    /**
-     * Sets a variable that is visible globally. This is correspondent to FTL
-     * <code>&lt;#global <i>name</i>=<i>model</i>&gt;</code>. This can be 
considered a convenient shorthand for:
-     * getGlobalNamespace().put(name, model)
-     */
-    public void setGlobalVariable(String name, TemplateModel model) {
-        globalNamespace.put(name, model);
-    }
-
-    /**
-     * Sets a variable in the current namespace. This is correspondent to FTL
-     * <code>&lt;#assign <i>name</i>=<i>model</i>&gt;</code>. This can be 
considered a convenient shorthand for:
-     * getCurrentNamespace().put(name, model)
-     */
-    public void setVariable(String name, TemplateModel model) {
-        currentNamespace.put(name, model);
-    }
-
-    /**
-     * Sets a local variable (one effective only during a macro invocation). 
This is correspondent to FTL
-     * <code>&lt;#local <i>name</i>=<i>model</i>&gt;</code>.
-     * 
-     * @param name
-     *            the identifier of the variable
-     * @param model
-     *            the value of the variable.
-     * @throws IllegalStateException
-     *             if the environment is not executing a macro body.
-     */
-    public void setLocalVariable(String name, TemplateModel model) {
-        if (currentMacroContext == null) {
-            throw new IllegalStateException("Not executing macro body");
-        }
-        currentMacroContext.setLocalVar(name, model);
-    }
-
-    /**
-     * Returns a set of variable names that are known at the time of call. 
This includes names of all shared variables
-     * in the {@link Configuration}, names of all global variables that were 
assigned during the template processing,
-     * names of all variables in the current name-space, names of all local 
variables and loop variables. If the passed
-     * root data model implements the {@link TemplateHashModelEx} interface, 
then all names it retrieves through a call
-     * to {@link TemplateHashModelEx#keys()} method are returned as well. The 
method returns a new Set object on each
-     * call that is completely disconnected from the Environment. That is, 
modifying the set will have no effect on the
-     * Environment object.
-     */
-    public Set getKnownVariableNames() throws TemplateModelException {
-        // shared vars.
-        Set set = configuration.getSharedVariableNames();
-
-        // root hash
-        if (rootDataModel instanceof TemplateHashModelEx) {
-            TemplateModelIterator rootNames = ((TemplateHashModelEx) 
rootDataModel).keys().iterator();
-            while (rootNames.hasNext()) {
-                set.add(((TemplateScalarModel) 
rootNames.next()).getAsString());
-            }
-        }
-
-        // globals
-        for (TemplateModelIterator tmi = globalNamespace.keys().iterator(); 
tmi.hasNext();) {
-            set.add(((TemplateScalarModel) tmi.next()).getAsString());
-        }
-
-        // current name-space
-        for (TemplateModelIterator tmi = currentNamespace.keys().iterator(); 
tmi.hasNext();) {
-            set.add(((TemplateScalarModel) tmi.next()).getAsString());
-        }
-
-        // locals and loop vars
-        if (currentMacroContext != null) {
-            set.addAll(currentMacroContext.getLocalVariableNames());
-        }
-        if (localContextStack != null) {
-            for (int i = localContextStack.size() - 1; i >= 0; i--) {
-                LocalContext lc = localContextStack.get(i);
-                set.addAll(lc.getLocalVariableNames());
-            }
-        }
-        return set;
-    }
-
-    /**
-     * Prints the current FTL stack trace. Useful for debugging. {@link 
TemplateException}s incorporate this information
-     * in their stack traces.
-     */
-    public void outputInstructionStack(PrintWriter pw) {
-        outputInstructionStack(getInstructionStackSnapshot(), false, pw);
-        pw.flush();
-    }
-
-    private static final int TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT = 10;
-
-    /**
-     * Prints an FTL stack trace based on a stack trace snapshot.
-     * 
-     * @param w
-     *            If it's a {@link PrintWriter}, {@link PrintWriter#println()} 
will be used for line-breaks.
-     * @see #getInstructionStackSnapshot()
-     * @since 2.3.21
-     */
-    static void outputInstructionStack(
-            TemplateElement[] instructionStackSnapshot, boolean terseMode, 
Writer w) {
-        final PrintWriter pw = (PrintWriter) (w instanceof PrintWriter ? w : 
null);
-        try {
-            if (instructionStackSnapshot != null) {
-                final int totalFrames = instructionStackSnapshot.length;
-                int framesToPrint = terseMode
-                        ? (totalFrames <= 
TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT
-                                ? totalFrames
-                                : TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT - 1)
-                        : totalFrames;
-                boolean hideNestringRelatedFrames = terseMode && framesToPrint 
< totalFrames;
-                int nestingRelatedFramesHidden = 0;
-                int trailingFramesHidden = 0;
-                int framesPrinted = 0;
-                for (int frameIdx = 0; frameIdx < totalFrames; frameIdx++) {
-                    TemplateElement stackEl = 
instructionStackSnapshot[frameIdx];
-                    final boolean nestingRelatedElement = (frameIdx > 0 && 
stackEl instanceof BodyInstruction)
-                            || (frameIdx > 1 && 
instructionStackSnapshot[frameIdx - 1] instanceof BodyInstruction);
-                    if (framesPrinted < framesToPrint) {
-                        if (!nestingRelatedElement || 
!hideNestringRelatedFrames) {
-                            w.write(frameIdx == 0
-                                    ? "\t- Failed at: "
-                                    : (nestingRelatedElement
-                                            ? "\t~ Reached through: "
-                                            : "\t- Reached through: "));
-                            w.write(instructionStackItemToString(stackEl));
-                            if (pw != null) pw.println();
-                   

<TRUNCATED>

Reply via email to