http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java 
b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
new file mode 100644
index 0000000..78d8025
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
@@ -0,0 +1,82 @@
+/*
+ * 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 org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * Used by built-ins and other template language features that get a class
+ * based on a string. This can be handy both for implementing security
+ * restrictions and for working around local class-loader issues. 
+ * 
+ * The implementation should be thread-safe, unless an
+ * instance is always only used in a single {@link Environment} object.
+ * 
+ * @see Configurable#setNewBuiltinClassResolver(TemplateClassResolver)
+ * 
+ * @since 2.3.17
+ */
+public interface TemplateClassResolver {
+    
+    /**
+     * Simply calls {@link _ClassUtil#forName(String)}.
+     */
+    TemplateClassResolver UNRESTRICTED_RESOLVER = new TemplateClassResolver() {
+
+        @Override
+        public Class resolve(String className, Environment env, Template 
template)
+        throws TemplateException {
+            try {
+                return _ClassUtil.forName(className);
+            } catch (ClassNotFoundException e) {
+                throw new _MiscTemplateException(e, env);
+            }
+        }
+        
+    };
+    
+    /**
+     * Doesn't allow resolving any classes.
+     */
+    TemplateClassResolver ALLOWS_NOTHING_RESOLVER =  new 
TemplateClassResolver() {
+
+        @Override
+        public Class resolve(String className, Environment env, Template 
template)
+        throws TemplateException {
+            throw 
MessageUtil.newInstantiatingClassNotAllowedException(className, env);
+        }
+        
+    };
+
+    /**
+     * Gets a {@link Class} based on the class name.
+     * 
+     * @param className the full-qualified class name
+     * @param env the environment in which the template executes
+     * @param template the template where the operation that require the
+     *        class resolution resides in. This is <code>null</code> if the
+     *        call doesn't come from a template.
+     *        
+     * @throws TemplateException if the class can't be found or shouldn't be
+     *   accessed from a template for security reasons.
+     */
+    Class resolve(String className, Environment env, Template template) throws 
TemplateException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java
 
b/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java
new file mode 100644
index 0000000..fef06d6
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Stores combined markup to be printed; used with {@link 
CombinedMarkupOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateCombinedMarkupOutputModel
+        extends 
CommonTemplateMarkupOutputModel<TemplateCombinedMarkupOutputModel> {
+    
+    private final CombinedMarkupOutputFormat outputFormat;
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     * 
+     * @param outputFormat
+     *            The {@link CombinedMarkupOutputFormat} format this value is 
bound to. Because
+     *            {@link CombinedMarkupOutputFormat} has no singleton, we have 
to pass it in, unlike with most other
+     *            {@link CommonTemplateMarkupOutputModel}-s.
+     */
+    TemplateCombinedMarkupOutputModel(String plainTextContent, String 
markupContent,
+            CombinedMarkupOutputFormat outputFormat) {
+        super(plainTextContent, markupContent);
+        this.outputFormat = outputFormat; 
+    }
+
+    @Override
+    public CombinedMarkupOutputFormat getOutputFormat() {
+        return outputFormat;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java 
b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
new file mode 100644
index 0000000..583f064
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -0,0 +1,921 @@
+/*
+ * 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.Reader;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+/**
+ * Used for customizing the configuration settings for individual {@link 
Template}-s (or rather groups of templates),
+ * relatively to the common setting values coming from the {@link 
Configuration}. This was designed with the standard
+ * template loading mechanism of FreeMarker in mind ({@link 
Configuration#getTemplate(String)}
+ * and {@link DefaultTemplateResolver}), though can also be reused for custom 
template loading and caching solutions.
+ * 
+ * <p>
+ * Note on the {@code locale} setting: When used with the standard template 
loading/caching mechanism (
+ * {@link Configuration#getTemplate(String)} and its overloads), localized 
lookup happens before the {@code locale}
+ * specified here could have effect. The {@code locale} will be only set in 
the template that the localized lookup has
+ * already found.
+ * 
+ * <p>
+ * Note on the encoding setting {@code encoding}: See {@link 
#setEncoding(String)}.
+ * 
+ * <p>
+ * Note that the result value of the reader methods (getter and "is" methods) 
is usually not useful unless the value of
+ * that setting was already set on this object. Otherwise you will get the 
value from the parent {@link Configuration},
+ * or an {@link IllegalStateException} before this object is associated to a 
{@link Configuration}.
+ * 
+ * <p>
+ * If you are using this class for your own template loading and caching 
solution, rather than with the standard one,
+ * you should be aware of a few more details:
+ * 
+ * <ul>
+ * <li>This class implements both {@link Configurable} and {@link 
ParserConfiguration}. This means that it can influence
+ * both the template parsing phase and the runtime settings. For both aspects 
(i.e., {@link ParserConfiguration} and
+ * {@link Configurable}) to take effect, you have first pass this object to 
the {@link Template} constructor
+ * (this is where the {@link ParserConfiguration} interface is used), and then 
you have to call {@link #apply(Template)}
+ * on the resulting {@link Template} object (this is where the {@link 
Configurable} aspect is used).
+ * 
+ * <li>{@link #apply(Template)} only change the settings that weren't yet set 
on the {@link Template} (but are inherited
+ * from the {@link Configuration}). This is primarily because if the template 
configures itself via the {@code #ftl}
+ * header, those values should have precedence. A consequence of this is that 
if you want to configure the same
+ * {@link Template} with multiple {@link TemplateConfiguration}-s, you either 
should merge them to a single one before
+ * that (with {@link #merge(TemplateConfiguration)}), or you have to apply 
them in reverse order of their intended
+ * precedence.
+ * </ul>
+ * 
+ * @see Template#Template(String, String, Reader, Configuration, 
ParserConfiguration, String)
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateConfiguration extends Configurable implements 
ParserConfiguration {
+
+    private Integer tagSyntax;
+    private Integer namingConvention;
+    private Boolean whitespaceStripping;
+    private Integer autoEscapingPolicy;
+    private Boolean recognizeStandardFileExtensions;
+    private OutputFormat outputFormat;
+    private String encoding;
+    private Integer tabSize;
+
+    /**
+     * Creates a new instance. The parent will be {@code null} initially, but 
it will
+     * be changed to the real parent {@link Configuration} when this object is 
added to the {@link Configuration}. (It's
+     * not allowed to add the same instance to multiple {@link 
Configuration}-s).
+     */
+    public TemplateConfiguration() {
+        super((Configuration) null);
+    }
+
+    /**
+     * Same as {@link #setParentConfiguration(Configuration)}.
+     */
+    @Override
+    void setParent(Configurable cfg) {
+        _NullArgumentException.check("cfg", cfg);
+        if (!(cfg instanceof Configuration)) {
+            throw new IllegalArgumentException("The parent of a 
TemplateConfiguration can only be a Configuration");
+        }
+        
+        Configurable parent = getParent();
+        if (parent != null) {
+            if (parent != cfg) {
+                throw new IllegalStateException(
+                        "This TemplateConfiguration is already associated with 
a different Configuration instance.");
+            }
+            return;
+        }
+        
+        super.setParent(cfg);
+    }
+
+    /**
+     * Associates this instance with a {@link Configuration}; usually you 
don't call this, as it's called internally
+     * when this instance is added to a {@link Configuration}. This method can 
be called only once (except with the same
+     * {@link Configuration} parameter again, as that changes nothing anyway).
+     * 
+     * @throws IllegalArgumentException
+     *             if the argument is {@code null} or not a {@link 
Configuration}
+     * @throws IllegalStateException
+     *             if this object is already associated to a different {@link 
Configuration} object,
+     *             or if the {@code Configuration} has {@code 
#getIncompatibleImprovements()} less than 2.3.22 and
+     *             this object tries to change any non-parser settings  
+     */
+    public void setParentConfiguration(Configuration cfg) {
+        setParent(cfg);
+    }
+
+    /**
+     * Returns the parent {@link Configuration}, or {@code null} if none was 
associated yet.
+     */
+    public Configuration getParentConfiguration() {
+        return (Configuration) getParent();
+    }
+
+    private Configuration getNonNullParentConfiguration() {
+        Configurable parent = getParent();
+        if (parent == null) {
+            throw new IllegalStateException("The TemplateConfiguration wasn't 
associated with a Configuration yet.");
+        }
+        return (Configuration) parent;
+    }
+    
+    /**
+     * Set all settings in this {@link TemplateConfiguration} that were set in 
the parameter
+     * {@link TemplateConfiguration}, possibly overwriting the earlier value 
in this object. (A setting is said to be
+     * set in a {@link TemplateConfiguration} if it was explicitly set via a 
setter method, as opposed to be inherited.)
+     */
+    public void merge(TemplateConfiguration tc) {
+        if (tc.isAPIBuiltinEnabledSet()) {
+            setAPIBuiltinEnabled(tc.isAPIBuiltinEnabled());
+        }
+        if (tc.isArithmeticEngineSet()) {
+            setArithmeticEngine(tc.getArithmeticEngine());
+        }
+        if (tc.isAutoEscapingPolicySet()) {
+            setAutoEscapingPolicy(tc.getAutoEscapingPolicy());
+        }
+        if (tc.isAutoFlushSet()) {
+            setAutoFlush(tc.getAutoFlush());
+        }
+        if (tc.isBooleanFormatSet()) {
+            setBooleanFormat(tc.getBooleanFormat());
+        }
+        if (tc.isCustomDateFormatsSet()) {
+            setCustomDateFormats(mergeMaps(
+                    isCustomDateFormatsSet() ? getCustomDateFormats() : null, 
tc.getCustomDateFormats(), false));
+        }
+        if (tc.isCustomNumberFormatsSet()) {
+            setCustomNumberFormats(mergeMaps(
+                    isCustomNumberFormatsSet() ? getCustomNumberFormats() : 
null, tc.getCustomNumberFormats(), false));
+        }
+        if (tc.isDateFormatSet()) {
+            setDateFormat(tc.getDateFormat());
+        }
+        if (tc.isDateTimeFormatSet()) {
+            setDateTimeFormat(tc.getDateTimeFormat());
+        }
+        if (tc.isEncodingSet()) {
+            setEncoding(tc.getEncoding());
+        }
+        if (tc.isLocaleSet()) {
+            setLocale(tc.getLocale());
+        }
+        if (tc.isLogTemplateExceptionsSet()) {
+            setLogTemplateExceptions(tc.getLogTemplateExceptions());
+        }
+        if (tc.isNamingConventionSet()) {
+            setNamingConvention(tc.getNamingConvention());
+        }
+        if (tc.isNewBuiltinClassResolverSet()) {
+            setNewBuiltinClassResolver(tc.getNewBuiltinClassResolver());
+        }
+        if (tc.isNumberFormatSet()) {
+            setNumberFormat(tc.getNumberFormat());
+        }
+        if (tc.isObjectWrapperSet()) {
+            setObjectWrapper(tc.getObjectWrapper());
+        }
+        if (tc.isOutputEncodingSet()) {
+            setOutputEncoding(tc.getOutputEncoding());
+        }
+        if (tc.isOutputFormatSet()) {
+            setOutputFormat(tc.getOutputFormat());
+        }
+        if (tc.isRecognizeStandardFileExtensionsSet()) {
+            
setRecognizeStandardFileExtensions(tc.getRecognizeStandardFileExtensions());
+        }
+        if (tc.isShowErrorTipsSet()) {
+            setShowErrorTips(tc.getShowErrorTips());
+        }
+        if (tc.isSQLDateAndTimeTimeZoneSet()) {
+            setSQLDateAndTimeTimeZone(tc.getSQLDateAndTimeTimeZone());
+        }
+        if (tc.isTagSyntaxSet()) {
+            setTagSyntax(tc.getTagSyntax());
+        }
+        if (tc.isTemplateExceptionHandlerSet()) {
+            setTemplateExceptionHandler(tc.getTemplateExceptionHandler());
+        }
+        if (tc.isTimeFormatSet()) {
+            setTimeFormat(tc.getTimeFormat());
+        }
+        if (tc.isTimeZoneSet()) {
+            setTimeZone(tc.getTimeZone());
+        }
+        if (tc.isURLEscapingCharsetSet()) {
+            setURLEscapingCharset(tc.getURLEscapingCharset());
+        }
+        if (tc.isWhitespaceStrippingSet()) {
+            setWhitespaceStripping(tc.getWhitespaceStripping());
+        }
+        if (tc.isTabSizeSet()) {
+            setTabSize(tc.getTabSize());
+        }
+        if (tc.isLazyImportsSet()) {
+            setLazyImports(tc.getLazyImports());
+        }
+        if (tc.isLazyAutoImportsSet()) {
+            setLazyAutoImports(tc.getLazyAutoImports());
+        }
+        if (tc.isAutoImportsSet()) {
+            setAutoImports(mergeMaps(isAutoImportsSet() ? getAutoImports() : 
null, tc.getAutoImports(), true));
+        }
+        if (tc.isAutoIncludesSet()) {
+            setAutoIncludes(mergeLists(isAutoIncludesSet() ? getAutoIncludes() 
: null, tc.getAutoIncludes()));
+        }
+        
+        tc.copyDirectCustomAttributes(this, true);
+    }
+
+    /**
+     * Sets those settings of the {@link Template} which aren't yet set in the 
{@link Template} and are set in this
+     * {@link TemplateConfiguration}, leaves the other settings as is. A 
setting is said to be set in a
+     * {@link TemplateConfiguration} or {@link Template} if it was explicitly 
set via a setter method on that object, as
+     * opposed to be inherited from the {@link Configuration}.
+     * 
+     * <p>
+     * Note that this method doesn't deal with settings that influence the 
parser, as those are already baked in at this
+     * point via the {@link ParserConfiguration}. 
+     * 
+     * <p>
+     * Note that the {@code encoding} setting of the {@link Template} counts 
as unset if it's {@code null},
+     * even if {@code null} was set via {@link Template#setEncoding(String)}.
+     *
+     * @throws IllegalStateException
+     *             If the parent configuration wasn't yet set.
+     */
+    public void apply(Template template) {
+        Configuration cfg = getNonNullParentConfiguration();
+        if (template.getConfiguration() != cfg) {
+            // This is actually not a problem right now, but for future BC we 
enforce this.
+            throw new IllegalArgumentException(
+                    "The argument Template doesn't belong to the same 
Configuration as the TemplateConfiguration");
+        }
+
+        if (isAPIBuiltinEnabledSet() && !template.isAPIBuiltinEnabledSet()) {
+            template.setAPIBuiltinEnabled(isAPIBuiltinEnabled());
+        }
+        if (isArithmeticEngineSet() && !template.isArithmeticEngineSet()) {
+            template.setArithmeticEngine(getArithmeticEngine());
+        }
+        if (isAutoFlushSet() && !template.isAutoFlushSet()) {
+            template.setAutoFlush(getAutoFlush());
+        }
+        if (isBooleanFormatSet() && !template.isBooleanFormatSet()) {
+            template.setBooleanFormat(getBooleanFormat());
+        }
+        if (isCustomDateFormatsSet()) {
+            template.setCustomDateFormats(
+                    mergeMaps(getCustomDateFormats(), 
template.getCustomDateFormatsWithoutFallback(), false));
+        }
+        if (isCustomNumberFormatsSet()) {
+            template.setCustomNumberFormats(
+                    mergeMaps(getCustomNumberFormats(), 
template.getCustomNumberFormatsWithoutFallback(), false));
+        }
+        if (isDateFormatSet() && !template.isDateFormatSet()) {
+            template.setDateFormat(getDateFormat());
+        }
+        if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) {
+            template.setDateTimeFormat(getDateTimeFormat());
+        }
+        if (isEncodingSet() && template.getEncoding() == null) {
+            template.setEncoding(getEncoding());
+        }
+        if (isLocaleSet() && !template.isLocaleSet()) {
+            template.setLocale(getLocale());
+        }
+        if (isLogTemplateExceptionsSet() && 
!template.isLogTemplateExceptionsSet()) {
+            template.setLogTemplateExceptions(getLogTemplateExceptions());
+        }
+        if (isNewBuiltinClassResolverSet() && 
!template.isNewBuiltinClassResolverSet()) {
+            template.setNewBuiltinClassResolver(getNewBuiltinClassResolver());
+        }
+        if (isNumberFormatSet() && !template.isNumberFormatSet()) {
+            template.setNumberFormat(getNumberFormat());
+        }
+        if (isObjectWrapperSet() && !template.isObjectWrapperSet()) {
+            template.setObjectWrapper(getObjectWrapper());
+        }
+        if (isOutputEncodingSet() && !template.isOutputEncodingSet()) {
+            template.setOutputEncoding(getOutputEncoding());
+        }
+        if (isShowErrorTipsSet() && !template.isShowErrorTipsSet()) {
+            template.setShowErrorTips(getShowErrorTips());
+        }
+        if (isSQLDateAndTimeTimeZoneSet() && 
!template.isSQLDateAndTimeTimeZoneSet()) {
+            template.setSQLDateAndTimeTimeZone(getSQLDateAndTimeTimeZone());
+        }
+        if (isTemplateExceptionHandlerSet() && 
!template.isTemplateExceptionHandlerSet()) {
+            
template.setTemplateExceptionHandler(getTemplateExceptionHandler());
+        }
+        if (isTimeFormatSet() && !template.isTimeFormatSet()) {
+            template.setTimeFormat(getTimeFormat());
+        }
+        if (isTimeZoneSet() && !template.isTimeZoneSet()) {
+            template.setTimeZone(getTimeZone());
+        }
+        if (isURLEscapingCharsetSet() && !template.isURLEscapingCharsetSet()) {
+            template.setURLEscapingCharset(getURLEscapingCharset());
+        }
+        if (isLazyImportsSet() && !template.isLazyImportsSet()) {
+            template.setLazyImports(getLazyImports());
+        }
+        if (isLazyAutoImportsSet() && !template.isLazyAutoImportsSet()) {
+            template.setLazyAutoImports(getLazyAutoImports());
+        }
+        if (isAutoImportsSet()) {
+            // Regarding the order of the maps in the merge:
+            // - Existing template-level imports have precedence over those 
coming from the TC (just as with the others
+            //   apply()-ed settings), thus for clashing import prefixes they 
must win.
+            // - Template-level imports count as more specific, and so come 
after the more generic ones from TC.
+            template.setAutoImports(mergeMaps(getAutoImports(), 
template.getAutoImportsWithoutFallback(), true));
+        }
+        if (isAutoIncludesSet()) {
+            template.setAutoIncludes(mergeLists(getAutoIncludes(), 
template.getAutoIncludesWithoutFallback()));
+        }
+        
+        copyDirectCustomAttributes(template, false);
+    }
+
+    /**
+     * See {@link Configuration#setTagSyntax(int)}.
+     */
+    public void setTagSyntax(int tagSyntax) {
+        _TemplateAPI.valideTagSyntaxValue(tagSyntax);
+        this.tagSyntax = Integer.valueOf(tagSyntax);
+    }
+
+    /**
+     * The getter pair of {@link #setTagSyntax(int)}.
+     */
+    @Override
+    public int getTagSyntax() {
+        return tagSyntax != null ? tagSyntax.intValue() : 
getNonNullParentConfiguration().getTagSyntax();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     */
+    public boolean isTagSyntaxSet() {
+        return tagSyntax != null;
+    }
+
+    /**
+     * See {@link Configuration#setNamingConvention(int)}.
+     */
+    public void setNamingConvention(int namingConvention) {
+        _TemplateAPI.validateNamingConventionValue(namingConvention);
+        this.namingConvention = Integer.valueOf(namingConvention);
+    }
+
+    /**
+     * The getter pair of {@link #setNamingConvention(int)}.
+     */
+    @Override
+    public int getNamingConvention() {
+        return namingConvention != null ? namingConvention.intValue()
+                : getNonNullParentConfiguration().getNamingConvention();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     */
+    public boolean isNamingConventionSet() {
+        return namingConvention != null;
+    }
+
+    /**
+     * See {@link Configuration#setWhitespaceStripping(boolean)}.
+     */
+    public void setWhitespaceStripping(boolean whitespaceStripping) {
+        this.whitespaceStripping = Boolean.valueOf(whitespaceStripping);
+    }
+
+    /**
+     * The getter pair of {@link #getWhitespaceStripping()}.
+     */
+    @Override
+    public boolean getWhitespaceStripping() {
+        return whitespaceStripping != null ? whitespaceStripping.booleanValue()
+                : getNonNullParentConfiguration().getWhitespaceStripping();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     */
+    public boolean isWhitespaceStrippingSet() {
+        return whitespaceStripping != null;
+    }
+
+    /**
+     * Sets the output format of the template; see {@link 
Configuration#setAutoEscapingPolicy(int)} for more.
+     */
+    public void setAutoEscapingPolicy(int autoEscapingPolicy) {
+        _TemplateAPI.validateAutoEscapingPolicyValue(autoEscapingPolicy);
+        this.autoEscapingPolicy = Integer.valueOf(autoEscapingPolicy);
+    }
+
+    /**
+     * The getter pair of {@link #setAutoEscapingPolicy(int)}.
+     */
+    @Override
+    public int getAutoEscapingPolicy() {
+        return autoEscapingPolicy != null ? autoEscapingPolicy.intValue()
+                : getNonNullParentConfiguration().getAutoEscapingPolicy();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     */
+    public boolean isAutoEscapingPolicySet() {
+        return autoEscapingPolicy != null;
+    }
+
+    /**
+     * Sets the output format of the template; see {@link 
Configuration#setOutputFormat(OutputFormat)} for more.
+     */
+    public void setOutputFormat(OutputFormat outputFormat) {
+        _NullArgumentException.check("outputFormat", outputFormat);
+        this.outputFormat = outputFormat;
+    }
+
+    /**
+     * The getter pair of {@link #setOutputFormat(OutputFormat)}.
+     */
+    @Override
+    public OutputFormat getOutputFormat() {
+        return outputFormat != null ? outputFormat : 
getNonNullParentConfiguration().getOutputFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     */
+    public boolean isOutputFormatSet() {
+        return outputFormat != null;
+    }
+    
+    /**
+     * See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}. 
+     */
+    public void setRecognizeStandardFileExtensions(boolean 
recognizeStandardFileExtensions) {
+        this.recognizeStandardFileExtensions = 
Boolean.valueOf(recognizeStandardFileExtensions);
+    }
+
+    /**
+     * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
+     */
+    @Override
+    public boolean getRecognizeStandardFileExtensions() {
+        return recognizeStandardFileExtensions != null ? 
recognizeStandardFileExtensions.booleanValue()
+                : 
getNonNullParentConfiguration().getRecognizeStandardFileExtensions();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     */
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return recognizeStandardFileExtensions != null;
+    }
+
+    public String getEncoding() {
+        return encoding != null ? encoding : 
getNonNullParentConfiguration().getDefaultEncoding();
+    }
+
+    /**
+     * When the standard template loading/caching mechanism is used, this 
forces the charset used for reading the
+     * template "file", overriding everything but the encoding coming from the 
{@code #ftl} header. This setting
+     * overrides the locale-specific encodings set via {@link 
Configuration#setEncoding(java.util.Locale, String)}. It
+     * also overrides the {@code encoding} parameter of {@link 
Configuration#getTemplate(String, String)} (and of its
+     * overloads) and the {@code encoding} parameter of the {@code #include} 
directive. This works like that because
+     * specifying the encoding where you are requesting the template is error 
prone and deprecated.
+     * 
+     * <p>
+     * If you are developing your own template loading/caching mechanism 
instead of the standard one, note that the
+     * above behavior is not guaranteed by this class alone; you have to 
ensure it. Also, read the note on
+     * {@code encoding} in the documentation of {@link #apply(Template)}.
+     */
+    public void setEncoding(String encoding) {
+        _NullArgumentException.check("encoding", encoding);
+        this.encoding = encoding;
+    }
+
+    public boolean isEncodingSet() {
+        return encoding != null;
+    }
+    
+    /**
+     * See {@link Configuration#setTabSize(int)}. 
+     * 
+     * @since 2.3.25
+     */
+    public void setTabSize(int tabSize) {
+        this.tabSize = Integer.valueOf(tabSize);
+    }
+
+    /**
+     * Getter pair of {@link #setTabSize(int)}.
+     * 
+     * @since 2.3.25
+     */
+    @Override
+    public int getTabSize() {
+        return tabSize != null ? tabSize.intValue()
+                : getNonNullParentConfiguration().getTabSize();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.25
+     */
+    public boolean isTabSizeSet() {
+        return tabSize != null;
+    }
+    
+    /**
+     * Returns {@link Configuration#getIncompatibleImprovements()} from the 
parent {@link Configuration}. This mostly
+     * just exist to satisfy the {@link ParserConfiguration} interface.
+     * 
+     * @throws IllegalStateException
+     *             If the parent configuration wasn't yet set.
+     */
+    @Override
+    public Version getIncompatibleImprovements() {
+        return getNonNullParentConfiguration().getIncompatibleImprovements();
+    }
+    
+    
+
+    @Override
+    public Locale getLocale() {
+        try {
+            return super.getLocale();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public TimeZone getTimeZone() {
+        try {
+            return super.getTimeZone();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public TimeZone getSQLDateAndTimeTimeZone() {
+        try {
+            return super.getSQLDateAndTimeTimeZone();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getNumberFormat() {
+        try {
+            return super.getNumberFormat();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public Map<String, ? extends TemplateNumberFormatFactory> 
getCustomNumberFormats() {
+        try {
+            return super.getCustomNumberFormats();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+        try {
+            return super.getCustomNumberFormat(name);
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public boolean hasCustomFormats() {
+        try {
+            return super.hasCustomFormats();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getBooleanFormat() {
+        try {
+            return super.getBooleanFormat();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getTimeFormat() {
+        try {
+            return super.getTimeFormat();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getDateFormat() {
+        try {
+            return super.getDateFormat();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getDateTimeFormat() {
+        try {
+            return super.getDateTimeFormat();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public Map<String, ? extends TemplateDateFormatFactory> 
getCustomDateFormats() {
+        try {
+            return super.getCustomDateFormats();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public TemplateDateFormatFactory getCustomDateFormat(String name) {
+        try {
+            return super.getCustomDateFormat(name);
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        try {
+            return super.getTemplateExceptionHandler();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public ArithmeticEngine getArithmeticEngine() {
+        try {
+            return super.getArithmeticEngine();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public ObjectWrapper getObjectWrapper() {
+        try {
+            return super.getObjectWrapper();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getOutputEncoding() {
+        try {
+            return super.getOutputEncoding();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String getURLEscapingCharset() {
+        try {
+            return super.getURLEscapingCharset();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public TemplateClassResolver getNewBuiltinClassResolver() {
+        try {
+            return super.getNewBuiltinClassResolver();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public boolean getAutoFlush() {
+        try {
+            return super.getAutoFlush();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public boolean getShowErrorTips() {
+        try {
+            return super.getShowErrorTips();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public boolean isAPIBuiltinEnabled() {
+        try {
+            return super.isAPIBuiltinEnabled();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public boolean getLogTemplateExceptions() {
+        try {
+            return super.getLogTemplateExceptions();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public boolean getLazyImports() {
+        try {
+            return super.getLazyImports();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public Boolean getLazyAutoImports() {
+        try {
+            return super.getLazyAutoImports();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public Map<String, String> getAutoImports() {
+        try {
+            return super.getAutoImports();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public List<String> getAutoIncludes() {
+        try {
+            return super.getAutoIncludes();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public String[] getCustomAttributeNames() {
+        try {
+            return super.getCustomAttributeNames();
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    @Override
+    public Object getCustomAttribute(String name) {
+        try {
+            return super.getCustomAttribute(name);
+        } catch (NullPointerException e) {
+            getNonNullParentConfiguration();
+            throw e;
+        }
+    }
+
+    private boolean hasAnyConfigurableSet() {
+        return
+                isAPIBuiltinEnabledSet()
+                || isArithmeticEngineSet()
+                || isAutoFlushSet()
+                || isAutoImportsSet()
+                || isAutoIncludesSet()
+                || isBooleanFormatSet()
+                || isCustomDateFormatsSet()
+                || isCustomNumberFormatsSet()
+                || isDateFormatSet()
+                || isDateTimeFormatSet()
+                || isLazyImportsSet()
+                || isLazyAutoImportsSet()
+                || isLocaleSet()
+                || isLogTemplateExceptionsSet()
+                || isNewBuiltinClassResolverSet()
+                || isNumberFormatSet()
+                || isObjectWrapperSet()
+                || isOutputEncodingSet()
+                || isShowErrorTipsSet()
+                || isSQLDateAndTimeTimeZoneSet()
+                || isTemplateExceptionHandlerSet()
+                || isTimeFormatSet()
+                || isTimeZoneSet()
+                || isURLEscapingCharsetSet();
+    }
+    
+    private Map mergeMaps(Map m1, Map m2, boolean overwriteUpdatesOrder) {
+        if (m1 == null) return m2;
+        if (m2 == null) return m1;
+        if (m1.isEmpty()) return m2 != null ? m2 : m1;
+        if (m2.isEmpty()) return m1 != null ? m1 : m2;
+        
+        LinkedHashMap mergedM = new LinkedHashMap((m1.size() + m2.size()) * 4 
/ 3 + 1, 0.75f);
+        mergedM.putAll(m1);
+        for (Object m2Key : m2.keySet()) {
+            mergedM.remove(m2Key); // So that duplicate keys are moved after 
m1 keys
+        }
+        mergedM.putAll(m2);
+        return mergedM;
+    }
+
+    private List<String> mergeLists(List<String> list1, List<String> list2) {
+        if (list1 == null) return list2;
+        if (list2 == null) return list1;
+        if (list1.isEmpty()) return list2 != null ? list2 : list1;
+        if (list2.isEmpty()) return list1 != null ? list1 : list2;
+        
+        ArrayList<String> mergedList = new ArrayList<>(list1.size() + 
list2.size());
+        mergedList.addAll(list1);
+        mergedList.addAll(list2);
+        return mergedList;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java 
b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java
new file mode 100644
index 0000000..07ee8ee
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java
@@ -0,0 +1,108 @@
+/*
+ * 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.text.DateFormat;
+import java.util.Date;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Represents a date/time/dateTime format; used in templates for formatting 
and parsing with that format. This is
+ * similar to Java's {@link DateFormat}, but made to fit the requirements of 
FreeMarker. Also, it makes easier to define
+ * formats that can't be represented with Java's existing {@link DateFormat} 
implementations.
+ * 
+ * <p>
+ * Implementations need not be thread-safe if the {@link 
TemplateNumberFormatFactory} doesn't recycle them among
+ * different {@link Environment}-s. As far as FreeMarker's concerned, 
instances are bound to a single
+ * {@link Environment}, and {@link Environment}-s are thread-local objects.
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateDateFormat extends TemplateValueFormat {
+    
+    /**
+     * @param dateModel
+     *            The date/time/dateTime to format; not {@code null}. Most 
implementations will just work with the return value of
+     *            {@link TemplateDateModel#getAsDate()}, but some may format 
differently depending on the properties of
+     *            a custom {@link TemplateDateModel} implementation.
+     * 
+     * @return The date/time/dateTime as text, with no escaping (like no HTML 
escaping); can't be {@code null}.
+     * 
+     * @throws TemplateValueFormatException
+     *             When a problem occurs during the formatting of the value. 
Notable subclass:
+     *             {@link UnknownDateTypeFormattingUnsupportedException}
+     * @throws TemplateModelException
+     *             Exception thrown by the {@code dateModel} object when 
calling its methods.
+     */
+    public abstract String formatToPlainText(TemplateDateModel dateModel)
+            throws TemplateValueFormatException, TemplateModelException;
+
+    /**
+     * Formats the model to markup instead of to plain text if the result 
markup will be more than just plain text
+     * escaped, otherwise falls back to formatting to plain text. If the 
markup result would be just the result of
+     * {@link #formatToPlainText(TemplateDateModel)} escaped, it must return 
the {@link String} that
+     * {@link #formatToPlainText(TemplateDateModel)} does.
+     * 
+     * <p>The implementation in {@link TemplateDateFormat} simply calls {@link 
#formatToPlainText(TemplateDateModel)}.
+     * 
+     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not 
{@code null}.
+     */
+    public Object format(TemplateDateModel dateModel) throws 
TemplateValueFormatException, TemplateModelException {
+        return formatToPlainText(dateModel);
+    }
+
+    /**
+     * Parsers a string to date/time/datetime, according to this format. Some 
format implementations may throw
+     * {@link ParsingNotSupportedException} here. 
+     * 
+     * @param s
+     *            The string to parse
+     * @param dateType
+     *            The expected date type of the result. Not all {@link 
TemplateDateFormat}-s will care about this;
+     *            though those who return a {@link TemplateDateModel} instead 
of {@link Date} often will. When strings
+     *            are parsed via {@code ?date}, {@code ?time}, or {@code 
?datetime}, then this parameter is
+     *            {@link TemplateDateModel#DATE}, {@link 
TemplateDateModel#TIME}, or {@link TemplateDateModel#DATETIME},
+     *            respectively. This parameter rarely if ever {@link 
TemplateDateModel#UNKNOWN}, but the implementation
+     *            that cares about this parameter should be prepared for that. 
If nothing else, it should throw
+     *            {@link UnknownDateTypeParsingUnsupportedException} then.
+     * 
+     * @return The interpretation of the text either as a {@link Date} or 
{@link TemplateDateModel}. Typically, a
+     *         {@link Date}. {@link TemplateDateModel} is used if you have to 
attach some application-specific
+     *         meta-information thats also extracted during {@link 
#formatToPlainText(TemplateDateModel)} (so if you format
+     *         something and then parse it, you get back an equivalent 
result). It can't be {@code null}. Known issue
+     *         (at least in FTL 2): {@code ?date}/{@code ?time}/{@code 
?datetime}, when not invoked as a method, can't
+     *         return the {@link TemplateDateModel}, only the {@link Date} 
from inside it, hence the additional
+     *         application-specific meta-info will be lost.
+     */
+    public abstract Object parse(String s, int dateType) throws 
TemplateValueFormatException;
+    
+    /**
+     * Tells if this formatter should be re-created if the locale changes.
+     */
+    public abstract boolean isLocaleBound();
+
+    /**
+     * Tells if this formatter should be re-created if the time zone changes. 
Currently always {@code true}.
+     */
+    public abstract boolean isTimeZoneBound();
+        
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java
new file mode 100644
index 0000000..6a6fb14
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java
@@ -0,0 +1,91 @@
+/*
+ * 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.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+
+/**
+ * Factory for a certain kind of date/time/dateTime formatting ({@link 
TemplateDateFormat}). Usually a singleton
+ * (one-per-VM or one-per-{@link Configuration}), and so must be thread-safe.
+ * 
+ * @see Configurable#setCustomDateFormats(java.util.Map)
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateDateFormatFactory extends 
TemplateValueFormatFactory {
+    
+    /**
+     * Returns a formatter for the given parameters.
+     * 
+     * <p>
+     * The returned formatter can be a new instance or a reused (cached) 
instance. Note that {@link Environment} itself
+     * caches the returned instances, though that cache is lost with the 
{@link Environment} (i.e., when the top-level
+     * template execution ends), also it might flushes lot of entries if the 
locale or time zone is changed during
+     * template execution. So caching on the factory level is still useful, 
unless creating the formatters is
+     * sufficiently cheap.
+     * 
+     * @param params
+     *            The string that further describes how the format should 
look. For example, when the
+     *            {@link Configurable#getDateFormat() dateFormat} is {@code 
"@fooBar 1, 2"}, then it will be
+     *            {@code "1, 2"} (and {@code "@fooBar"} selects the factory). 
The format of this string is up to the
+     *            {@link TemplateDateFormatFactory} implementation. Not {@code 
null}, often an empty string.
+     * @param dateType
+     *            {@link TemplateDateModel#DATE}, {@link 
TemplateDateModel#TIME}, {@link TemplateDateModel#DATETIME} or
+     *            {@link TemplateDateModel#UNKNOWN}. Supporting {@link 
TemplateDateModel#UNKNOWN} is not necessary, in
+     *            which case the method should throw an {@link 
UnknownDateTypeFormattingUnsupportedException} exception.
+     * @param locale
+     *            The locale to format for. Not {@code null}. The resulting 
format should be bound to this locale
+     *            forever (i.e. locale changes in the {@link Environment} must 
not be followed).
+     * @param timeZone
+     *            The time zone to format for. Not {@code null}. The resulting 
format must be bound to this time zone
+     *            forever (i.e. time zone changes in the {@link Environment} 
must not be followed).
+     * @param zonelessInput
+     *            Indicates that the input Java {@link Date} is not from a 
time zone aware source. When this is
+     *            {@code true}, the formatters shouldn't override the time 
zone provided to its constructor (most
+     *            formatters don't do that anyway), and it shouldn't show the 
time zone, if it can hide it (like a
+     *            {@link SimpleDateFormat} pattern-based formatter may can't 
do that, as the pattern prescribes what to
+     *            show).
+     *            <p>
+     *            As of FreeMarker 2.3.21, this is {@code true} exactly when 
the date is an SQL "date without time of
+     *            the day" (i.e., a {@link java.sql.Date java.sql.Date}) or an 
SQL "time of the day" value (i.e., a
+     *            {@link java.sql.Time java.sql.Time}, although this rule can 
change in future, depending on
+     *            configuration settings and such, so you shouldn't rely on 
this rule, just accept what this parameter
+     *            says.
+     * @param env
+     *            The runtime environment from which the formatting was 
called. This is mostly meant to be used for
+     *            {@link Environment#setCustomState(Object, Object)}/{@link 
Environment#getCustomState(Object)}.
+     * 
+     * @throws TemplateValueFormatException
+     *             If any problem occurs while parsing/getting the format. 
Notable subclasses:
+     *             {@link InvalidFormatParametersException} if {@code params} 
is malformed;
+     *             {@link UnknownDateTypeFormattingUnsupportedException} if 
{@code dateType} is
+     *             {@link TemplateDateModel#UNKNOWN} and that's unsupported by 
this factory.
+     */
+    public abstract TemplateDateFormat get(
+            String params,
+            int dateType, Locale locale, TimeZone timeZone, boolean 
zonelessInput,
+            Environment env)
+                    throws TemplateValueFormatException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java 
b/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
new file mode 100644
index 0000000..cbe745c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java
@@ -0,0 +1,102 @@
+/*
+ * 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 org.apache.freemarker.core.util._CollectionUtil;
+
+/**
+ * Holds an buffer (array) of {@link _ASTElement}-s with the count of the 
utilized items in it. The un-utilized tail
+ * of the array must only contain {@code null}-s.
+ * 
+ * @since 2.3.24
+ */
+class TemplateElements {
+    
+    static final TemplateElements EMPTY = new TemplateElements(null, 0);
+
+    private final _ASTElement[] buffer;
+    private final int count;
+
+    /**
+     * @param buffer
+     *            The buffer; {@code null} exactly if {@code count} is 0.
+     * @param count
+     *            The number of utilized buffer elements; if 0, then {@code 
null} must be {@code null}.
+     */
+    TemplateElements(_ASTElement[] buffer, int count) {
+        /*
+        // Assertion:
+        if (count == 0 && buffer != null) {
+            throw new IllegalArgumentException(); 
+        }
+        */
+        
+        this.buffer = buffer;
+        this.count = count;
+    }
+
+    _ASTElement[] getBuffer() {
+        return buffer;
+    }
+
+    int getCount() {
+        return count;
+    }
+
+    _ASTElement getFirst() {
+        return buffer != null ? buffer[0] : null;
+    }
+    
+    _ASTElement getLast() {
+        return buffer != null ? buffer[count - 1] : null;
+    }
+    
+    /**
+     * Used for some backward compatibility hacks.
+     */
+    _ASTElement asSingleElement() {
+        if (count == 0) {
+            return new ASTStaticText(_CollectionUtil.EMPTY_CHAR_ARRAY, false);
+        } else {
+            _ASTElement first = buffer[0];
+            if (count == 1) {
+                return first;
+            } else {
+                ASTImplicitParent mixedContent = new ASTImplicitParent();
+                mixedContent.setChildren(this);
+                mixedContent.setLocation(first.getTemplate(), first, 
getLast());
+                return mixedContent;
+            }
+        }
+    }
+    
+    /**
+     * Used for some backward compatibility hacks.
+     */
+    ASTImplicitParent asMixedContent() {
+        ASTImplicitParent mixedContent = new ASTImplicitParent();
+        if (count != 0) {
+            _ASTElement first = buffer[0];
+            mixedContent.setChildren(this);
+            mixedContent.setLocation(first.getTemplate(), first, getLast());
+        }
+        return mixedContent;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java 
b/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
new file mode 100644
index 0000000..bd9d473
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java
@@ -0,0 +1,48 @@
+/*
+ * 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.util.Collection;
+import java.util.Collections;
+
+/**
+ * Used as the return value of {@link _ASTElement#accept(Environment)} when 
the invoked element has nested elements
+ * to invoke. It would be more natural to invoke child elements before 
returning from
+ * {@link _ASTElement#accept(Environment)}, however, if there's nothing to do 
after the child elements were invoked,
+ * that would mean wasting stack space.
+ * 
+ * @since 2.3.24
+ */
+class TemplateElementsToVisit {
+
+    private final Collection<_ASTElement> templateElements;
+
+    TemplateElementsToVisit(Collection<_ASTElement> templateElements) {
+        this.templateElements = null != templateElements ? templateElements : 
Collections.<_ASTElement> emptyList();
+    }
+
+    TemplateElementsToVisit(_ASTElement nestedBlock) {
+        this(Collections.singleton(nestedBlock));
+    }
+
+    Collection<_ASTElement> getTemplateElements() {
+        return templateElements;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/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
index d1d9a02..4ed9beb 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateException.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateException.java
@@ -27,14 +27,6 @@ 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._CollectionUtil;
 
 /**
@@ -49,8 +41,8 @@ public class TemplateException extends Exception {
     // Set in constructor:
     private transient _ErrorDescriptionBuilder descriptionBuilder;
     private final transient Environment env;
-    private final transient Expression blamedExpression;
-    private transient TemplateElement[] ftlInstructionStackSnapshot;
+    private final transient ASTExpression blamedExpression;
+    private transient _ASTElement[] ftlInstructionStackSnapshot;
     
     // Calculated on demand:
     private String renderedFtlInstructionStackSnapshot;  // clalc. from 
ftlInstructionStackSnapshot 
@@ -140,7 +132,7 @@ public class TemplateException extends Exception {
      *          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,
+    protected TemplateException(Throwable cause, Environment env, 
ASTExpression blamedExpr,
             _ErrorDescriptionBuilder descriptionBuilder) {
         this(null, cause, env, blamedExpr, descriptionBuilder);
     }
@@ -148,7 +140,7 @@ public class TemplateException extends Exception {
     private TemplateException(
             String renderedDescription,
             Throwable cause,            
-            Environment env, Expression blamedExpression,
+            Environment env, ASTExpression blamedExpression,
             _ErrorDescriptionBuilder descriptionBuilder) {
         // Note: Keep this constructor lightweight.
         
@@ -194,7 +186,7 @@ public class TemplateException extends Exception {
         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
+                ASTNode templateObject = blamedExpression != null
                         ? blamedExpression
                         : (
                                 ftlInstructionStackSnapshot != null && 
ftlInstructionStackSnapshot.length != 0
@@ -284,7 +276,7 @@ public class TemplateException extends Exception {
         }
     }
 
-    private TemplateElement getFailingInstruction() {
+    private _ASTElement getFailingInstruction() {
         if (ftlInstructionStackSnapshot != null && 
ftlInstructionStackSnapshot.length > 0) {
             return ftlInstructionStackSnapshot[0];
         } else {
@@ -539,7 +531,7 @@ public class TemplateException extends Exception {
         }
     }
     
-    Expression getBlamedExpression() {
+    ASTExpression getBlamedExpression() {
         return blamedExpression;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/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
index ef871c1..baec030 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
@@ -23,9 +23,6 @@ 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;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java 
b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java
new file mode 100644
index 0000000..c43d7f2
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util.Date;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * Utility classes for implementing {@link TemplateValueFormat}-s.
+ * 
+ * @since 2.3.24 
+ */
+public final class TemplateFormatUtil {
+    
+    private TemplateFormatUtil() {
+        // Not meant to be instantiated
+    }
+
+    public static void checkHasNoParameters(String params) throws 
InvalidFormatParametersException
+             {
+        if (params.length() != 0) {
+            throw new InvalidFormatParametersException(
+                    "This number format doesn't support any parameters.");
+        }
+    }
+
+    /**
+     * Utility method to extract the {@link Number} from an {@link 
TemplateNumberModel}, and throws
+     * {@link TemplateModelException} with a standard error message if that's 
{@code null}. {@link TemplateNumberModel}
+     * that store {@code null} are in principle not allowed, and so are 
considered to be bugs in the
+     * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation.
+     */
+    public static Number getNonNullNumber(TemplateNumberModel numberModel)
+            throws TemplateModelException, UnformattableValueException {
+        Number number = numberModel.getAsNumber();
+        if (number == null) {
+            throw EvalUtil.newModelHasStoredNullException(Number.class, 
numberModel, null);
+        }
+        return number;
+    }
+
+    /**
+     * Utility method to extract the {@link Date} from an {@link 
TemplateDateModel}, and throw
+     * {@link TemplateModelException} with a standard error message if that's 
{@code null}. {@link TemplateDateModel}
+     * that store {@code null} are in principle not allowed, and so are 
considered to be bugs in the
+     * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation.
+     */
+    public static Date getNonNullDate(TemplateDateModel dateModel) throws 
TemplateModelException {
+        Date date = dateModel.getAsDate();
+        if (date == null) {
+            throw EvalUtil.newModelHasStoredNullException(Date.class, 
dateModel, null);
+        }
+        return date;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java 
b/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java
new file mode 100644
index 0000000..f4acd01
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Stores HTML markup to be printed; used with {@link HTMLOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateHTMLOutputModel extends 
CommonTemplateMarkupOutputModel<TemplateHTMLOutputModel> {
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     */
+    TemplateHTMLOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public HTMLOutputFormat getOutputFormat() {
+        return HTMLOutputFormat.INSTANCE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java 
b/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java
new file mode 100644
index 0000000..1d4c5af
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java
@@ -0,0 +1,52 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * "markup output" template language data-type; stores markup (some kind of 
"rich text" / structured format, as opposed
+ * to plain text) that meant to be printed as template output. This type is 
related to the {@link OutputFormat}
+ * mechanism. Values of this kind are exempt from {@link OutputFormat}-based 
automatic escaping.
+ * 
+ * <p>
+ * Each implementation of this type has a {@link OutputFormat} subclass pair, 
whose singleton instance is returned by
+ * {@link #getOutputFormat()}. See more about how markup output values work at 
{@link OutputFormat}.
+ * 
+ * <p>
+ * Note that {@link TemplateMarkupOutputModel}-s are by design not treated 
like {@link TemplateScalarModel}-s, and so
+ * the implementations of this interface usually shouldn't implement {@link 
TemplateScalarModel}. (Because, operations
+ * applicable on plain strings, like converting to upper case, substringing, 
etc., can corrupt markup.) If the template
+ * author wants to pass in the "source" of the markup as string somewhere, he 
should use {@code ?markup_string}.
+ * 
+ * @param <MO>
+ *            Refers to the interface's own type, which is useful in 
interfaces that extend
+ *            {@link TemplateMarkupOutputModel} (Java Generics trick).
+ * 
+ * @since 2.3.24
+ */
+public interface TemplateMarkupOutputModel<MO extends 
TemplateMarkupOutputModel<MO>> extends TemplateModel {
+
+    /**
+     * Returns the singleton {@link OutputFormat} object that implements the 
operations for the "markup output" value.
+     */
+    MarkupOutputFormat<MO> getOutputFormat();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java 
b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java
new file mode 100644
index 0000000..f4f4e70
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java
@@ -0,0 +1,90 @@
+/*
+ * 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.text.NumberFormat;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * Represents a number format; used in templates for formatting and parsing 
with that format. This is similar to Java's
+ * {@link NumberFormat}, but made to fit the requirements of FreeMarker. Also, 
it makes easier to define formats that
+ * can't be represented with Java's existing {@link NumberFormat} 
implementations.
+ * 
+ * <p>
+ * Implementations need not be thread-safe if the {@link 
TemplateNumberFormatFactory} doesn't recycle them among
+ * different {@link Environment}-s. As far as FreeMarker's concerned, 
instances are bound to a single
+ * {@link Environment}, and {@link Environment}-s are thread-local objects.
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateNumberFormat extends TemplateValueFormat {
+
+    /**
+     * @param numberModel
+     *            The number to format; not {@code null}. Most implementations 
will just work with the return value of
+     *            {@link TemplateDateModel#getAsDate()}, but some may format 
differently depending on the properties of
+     *            a custom {@link TemplateDateModel} implementation.
+     *            
+     * @return The number as text, with no escaping (like no HTML escaping); 
can't be {@code null}.
+     * 
+     * @throws TemplateValueFormatException
+     *             If any problem occurs while parsing/getting the format. 
Notable subclass:
+     *             {@link UnformattableValueException}.
+     * @throws TemplateModelException
+     *             Exception thrown by the {@code dateModel} object when 
calling its methods.
+     */
+    public abstract String formatToPlainText(TemplateNumberModel numberModel)
+            throws TemplateValueFormatException, TemplateModelException;
+
+    /**
+     * Formats the model to markup instead of to plain text if the result 
markup will be more than just plain text
+     * escaped, otherwise falls back to formatting to plain text. If the 
markup result would be just the result of
+     * {@link #formatToPlainText(TemplateNumberModel)} escaped, it must return 
the {@link String} that
+     * {@link #formatToPlainText(TemplateNumberModel)} does.
+     * 
+     * <p>
+     * The implementation in {@link TemplateNumberFormat} simply calls {@link 
#formatToPlainText(TemplateNumberModel)}.
+     * 
+     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not 
{@code null}.
+     */
+    public Object format(TemplateNumberModel numberModel)
+            throws TemplateValueFormatException, TemplateModelException {
+        return formatToPlainText(numberModel);
+    }
+    
+    /**
+     * Tells if this formatter should be re-created if the locale changes.
+     */
+    public abstract boolean isLocaleBound();
+
+    /**
+     * This method is reserved for future purposes; currently it always throws 
{@link ParsingNotSupportedException}. We
+     * don't yet support number parsing with {@link TemplateNumberFormat}-s, 
because currently FTL parses strings to
+     * number with the {@link ArithmeticEngine} ({@link TemplateNumberFormat} 
were only introduced in 2.3.24). If it
+     * will be support, it will be similar to {@link 
TemplateDateFormat#parse(String, int)}.
+     */
+    public final Object parse(String s) throws TemplateValueFormatException {
+        throw new ParsingNotSupportedException("Number formats currenly don't 
support parsing");
+    }
+    
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java
new file mode 100644
index 0000000..4decb91
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.util.Locale;
+
+/**
+ * Factory for a certain kind of number formatting ({@link 
TemplateNumberFormat}). Usually a singleton (one-per-VM or
+ * one-per-{@link Configuration}), and so must be thread-safe.
+ * 
+ * @see Configurable#setCustomNumberFormats(java.util.Map)
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateNumberFormatFactory extends 
TemplateValueFormatFactory {
+
+    /**
+     * Returns a formatter for the given parameters.
+     * 
+     * <p>
+     * The returned formatter can be a new instance or a reused (cached) 
instance. Note that {@link Environment} itself
+     * caches the returned instances, though that cache is lost with the 
{@link Environment} (i.e., when the top-level
+     * template execution ends), also it might flushes lot of entries if the 
locale or time zone is changed during
+     * template execution. So caching on the factory level is still useful, 
unless creating the formatters is
+     * sufficiently cheap.
+     * 
+     * @param params
+     *            The string that further describes how the format should 
look. For example, when the
+     *            {@link Configurable#getNumberFormat() numberFormat} is 
{@code "@fooBar 1, 2"}, then it will be
+     *            {@code "1, 2"} (and {@code "@fooBar"} selects the factory). 
The format of this string is up to the
+     *            {@link TemplateNumberFormatFactory} implementation. Not 
{@code null}, often an empty string.
+     * @param locale
+     *            The locale to format for. Not {@code null}. The resulting 
format must be bound to this locale
+     *            forever (i.e. locale changes in the {@link Environment} must 
not be followed).
+     * @param env
+     *            The runtime environment from which the formatting was 
called. This is mostly meant to be used for
+     *            {@link Environment#setCustomState(Object, Object)}/{@link 
Environment#getCustomState(Object)}.
+     *            
+     * @throws TemplateValueFormatException
+     *             if any problem occurs while parsing/getting the format. 
Notable subclasses:
+     *             {@link InvalidFormatParametersException} if the {@code 
params} is malformed.
+     */
+    public abstract TemplateNumberFormat get(String params, Locale locale, 
Environment env)
+            throws TemplateValueFormatException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java 
b/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java
new file mode 100644
index 0000000..f49a7b5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Note yet public; will change in 2.4 (as it has to process {@code 
UnboundTemplate}-s).
+ */
+abstract class TemplatePostProcessor {
+
+    public abstract void postProcess(Template e) throws 
TemplatePostProcessorException;
+    
+    // TODO: getPriority, getPhase, getMustBeBefore, getMustBeAfter
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java 
b/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java
new file mode 100644
index 0000000..1904aa5
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Not yet public; subject to change.
+ */
+class TemplatePostProcessorException extends Exception {
+
+    public TemplatePostProcessorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public TemplatePostProcessorException(String message) {
+        super(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java 
b/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java
new file mode 100644
index 0000000..008737a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Stores RTF markup to be printed; used with {@link RTFOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateRTFOutputModel extends 
CommonTemplateMarkupOutputModel<TemplateRTFOutputModel> {
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     */
+    TemplateRTFOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public RTFOutputFormat getOutputFormat() {
+        return RTFOutputFormat.INSTANCE;
+    }
+
+}

Reply via email to