You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/04/05 19:55:47 UTC

[1/4] incubator-freemarker git commit: (Added TemplateLanguage to auto-import for object builder expressions. Some JavaDoc cleanup.)

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 27a62ce01 -> 88baea20c


(Added TemplateLanguage to auto-import for object builder expressions. Some JavaDoc cleanup.)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/23f66ebc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/23f66ebc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/23f66ebc

Branch: refs/heads/3
Commit: 23f66ebcab168cb37513107f67ec00236dfe9517
Parents: 27a62ce
Author: ddekany <dd...@apache.org>
Authored: Wed Mar 29 11:02:18 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Wed Mar 29 11:02:18 2017 +0200

----------------------------------------------------------------------
 .../freemarker/core/MutableProcessingConfiguration.java     | 9 ++++-----
 .../freemarker/core/_ObjectBuilderSettingEvaluator.java     | 4 +++-
 2 files changed, 7 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/23f66ebc/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
index d9200b0..a1e756a 100644
--- a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -356,11 +356,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     private boolean lazyAutoImportsSet;
     
     /**
-     * Intended to be called from inside FreeMarker only.
-     * Creates a top-level configurable, one that doesn't inherit from a parent, and thus stores the default values.
-     * Called by the {@link Configuration} constructor.
+     * Called by the {@link Configuration} constructor, initializes the fields to their {@link Configuration}-level
+     * default without marking them as set.
      */
-    protected MutableProcessingConfiguration(Version incompatibleImprovements) {
+    MutableProcessingConfiguration(Version incompatibleImprovements) {
         _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
         parent = null;
         locale = Configuration.getDefaultLocale();
@@ -2263,7 +2262,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      *     {@link AndMatcher}, {@link OrMatcher}, {@link NotMatcher}, {@link ConditionalTemplateConfigurationFactory},
      *     {@link MergingTemplateConfigurationFactory}, {@link FirstMatchTemplateConfigurationFactory},
      *     {@link HTMLOutputFormat}, {@link XMLOutputFormat}, {@link RTFOutputFormat}, {@link PlainTextOutputFormat},
-     *     {@link UndefinedOutputFormat}, {@link Configuration}.
+     *     {@link UndefinedOutputFormat}, {@link Configuration}, {@link TemplateLanguage}.
      *   </li>
      *   <li>
      *     <p>{@link TimeZone} objects can be created like {@code TimeZone("UTC")}, despite that there's no a such

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/23f66ebc/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
index c817517..cc96d81 100644
--- a/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -679,7 +679,9 @@ public class _ObjectBuilderSettingEvaluator {
             addWithSimpleName(SHORTHANDS, RTFOutputFormat.class);
             addWithSimpleName(SHORTHANDS, PlainTextOutputFormat.class);
             addWithSimpleName(SHORTHANDS, UndefinedOutputFormat.class);
-            
+
+            addWithSimpleName(SHORTHANDS, TemplateLanguage.class);
+
             addWithSimpleName(SHORTHANDS, Locale.class);
 
             {


[3/4] incubator-freemarker git commit: Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder. (This is not the final version of TemplateConfiguration; it will be become cleaner as Template also becomes immutable.)

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/TemplateBooleanFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateBooleanFormat.java b/src/main/java/org/apache/freemarker/core/TemplateBooleanFormat.java
new file mode 100644
index 0000000..52f753d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateBooleanFormat.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 org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateValueFormat;
+
+// TODO Should be public and moved over to core.valueformat?
+final class TemplateBooleanFormat extends TemplateValueFormat {
+
+    static final String C_TRUE_FALSE = "true,false";
+    static final TemplateBooleanFormat C_TRUE_FALSE_FORMAT = new TemplateBooleanFormat();
+
+    static TemplateBooleanFormat getInstance(String format) {
+        return format.equals(C_TRUE_FALSE) ? C_TRUE_FALSE_FORMAT : new TemplateBooleanFormat(format);
+    }
+
+    private final String formatString;
+    private final String trueStringValue;  // deduced from booleanFormat
+    private final String falseStringValue;  // deduced from booleanFormat
+
+    /**
+     * Use for {@link #C_TRUE_FALSE} only!
+     */
+    private TemplateBooleanFormat() {
+        formatString = C_TRUE_FALSE;
+        trueStringValue = null;
+        falseStringValue = null;
+    }
+
+    private TemplateBooleanFormat(String formatString) {
+        int commaIdx = formatString.indexOf(',');
+        if (commaIdx == -1) {
+            throw new IllegalArgumentException(
+                    "Setting value must be string that contains two comma-separated values for true and false, " +
+                            "respectively.");
+        }
+
+        this.formatString = formatString;
+        trueStringValue = formatString.substring(0, commaIdx);
+        falseStringValue = formatString.substring(commaIdx + 1);
+    }
+
+    public String getFormatString() {
+        return formatString;
+    }
+
+    /**
+     * Returns the string to which {@code true} is converted to for human audience, or {@code null} if automatic
+     * coercion to string is not allowed. The default value is {@code null}.
+     *
+     * <p>This value is deduced from the {@code "boolean_format"} setting.
+     * Confusingly, for backward compatibility (at least until 2.4) that defaults to {@code "true,false"}, yet this
+     * defaults to {@code null}. That's so because {@code "true,false"} is treated exceptionally, as that default is a
+     * historical mistake in FreeMarker, since it targets computer language output, not human writing. Thus it's
+     * ignored.
+     */
+    public String getTrueStringValue() {
+        return trueStringValue;
+    }
+
+    /**
+     * Same as {@link #getTrueStringValue()} but with {@code false}.
+     */
+    public String getFalseStringValue() {
+        return falseStringValue;
+    }
+
+    @Override
+    public String getDescription() {
+        return _StringUtil.jQuote(formatString);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/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
index 54b524e..6d1ff13 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -31,6 +31,7 @@ import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
@@ -48,7 +49,7 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * already found.
  * 
  * <p>
- * Note on the sourceEncoding setting {@code sourceEncoding}: See {@link #setSourceEncoding(Charset)}.
+ * Note on the sourceEncoding setting {@code sourceEncoding}: See {@link Builder#setSourceEncoding(Charset)}.
  * 
  * <p>
  * Note that the result value of the reader methods (getter and "is" methods) is usually not useful unless the value of
@@ -70,208 +71,134 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * 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.
+ * that (with {@link Builder#merge(ParserAndProcessingConfiguration)}), or you have to apply them in reverse order of
+ * their intended precedence.
  * </ul>
  * 
  * @see Template#Template(String, String, Reader, Configuration, ParserConfiguration, Charset)
  * 
  * @since 2.3.24
  */
-public final class TemplateConfiguration extends MutableProcessingConfiguration<TemplateConfiguration>
-        implements ParserConfiguration {
-
-    private TemplateLanguage templateLanguage;
-    private Integer tagSyntax;
-    private Integer namingConvention;
-    private Boolean whitespaceStripping;
-    private Integer autoEscapingPolicy;
-    private Boolean recognizeStandardFileExtensions;
-    private OutputFormat outputFormat;
-    private Charset sourceEncoding;
-    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(MutableProcessingConfiguration cfg) {
-        _NullArgumentException.check("cfg", cfg);
-        if (!(cfg instanceof Configuration)) {
-            throw new IllegalArgumentException("The parent of a TemplateConfiguration can only be a Configuration");
-        }
-        
-        MutableProcessingConfiguration 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);
+public final class TemplateConfiguration implements ParserAndProcessingConfiguration {
+    private Configuration configuration;
+
+    private final Locale locale;
+    private final String numberFormat;
+    private final String timeFormat;
+    private final String dateFormat;
+    private final String dateTimeFormat;
+    private final TimeZone timeZone;
+    private final TimeZone sqlDateAndTimeTimeZone;
+    private final boolean sqlDateAndTimeTimeZoneSet;
+    private final String booleanFormat;
+    private final TemplateExceptionHandler templateExceptionHandler;
+    private final ArithmeticEngine arithmeticEngine;
+    private final ObjectWrapper objectWrapper;
+    private final Charset outputEncoding;
+    private final boolean outputEncodingSet;
+    private final Charset urlEscapingCharset;
+    private final boolean urlEscapingCharsetSet;
+    private final Boolean autoFlush;
+    private final TemplateClassResolver newBuiltinClassResolver;
+    private final Boolean showErrorTips;
+    private final Boolean apiBuiltinEnabled;
+    private final Boolean logTemplateExceptions;
+    private final Map<String, TemplateDateFormatFactory> customDateFormats;
+    private final Map<String, TemplateNumberFormatFactory> customNumberFormats;
+    private final Map<String, String> autoImports;
+    private final List<String> autoIncludes;
+    private final Boolean lazyImports;
+    private final Boolean lazyAutoImports;
+    private final boolean lazyAutoImportsSet;
+    private final Map<Object, Object> customAttributes;
+    
+    private final TemplateLanguage templateLanguage;
+    private final Integer tagSyntax;
+    private final Integer namingConvention;
+    private final Boolean whitespaceStripping;
+    private final Integer autoEscapingPolicy;
+    private final Boolean recognizeStandardFileExtensions;
+    private final OutputFormat outputFormat;
+    private final Charset sourceEncoding;
+    private final Integer tabSize;
+
+    private TemplateConfiguration(Builder builder) {
+        locale = builder.isLocaleSet() ? builder.getLocale() : null;
+        numberFormat = builder.isNumberFormatSet() ? builder.getNumberFormat() : null;
+        timeFormat = builder.isTimeFormatSet() ? builder.getTimeFormat() : null;
+        dateFormat = builder.isDateFormatSet() ? builder.getDateFormat() : null;
+        dateTimeFormat = builder.isDateTimeFormatSet() ? builder.getDateTimeFormat() : null;
+        timeZone = builder.isTimeZoneSet() ? builder.getTimeZone() : null;
+        sqlDateAndTimeTimeZoneSet = builder.isSQLDateAndTimeTimeZoneSet();
+        sqlDateAndTimeTimeZone = sqlDateAndTimeTimeZoneSet ? builder.getSQLDateAndTimeTimeZone() : null;
+        booleanFormat = builder.isBooleanFormatSet() ? builder.getBooleanFormat() : null;
+        templateExceptionHandler = builder.isTemplateExceptionHandlerSet() ? builder.getTemplateExceptionHandler() : null;
+        arithmeticEngine = builder.isArithmeticEngineSet() ? builder.getArithmeticEngine() : null;
+        objectWrapper = builder.isObjectWrapperSet() ? builder.getObjectWrapper() : null;
+        outputEncodingSet = builder.isOutputEncodingSet();
+        outputEncoding = outputEncodingSet ? builder.getOutputEncoding() : null;
+        urlEscapingCharsetSet = builder.isURLEscapingCharsetSet();
+        urlEscapingCharset = urlEscapingCharsetSet ? builder.getURLEscapingCharset() : null;
+        autoFlush = builder.isAutoFlushSet() ? builder.getAutoFlush() : null;
+        newBuiltinClassResolver = builder.isNewBuiltinClassResolverSet() ? builder.getNewBuiltinClassResolver() : null;
+        showErrorTips = builder.isShowErrorTipsSet() ? builder.getShowErrorTips() : null;
+        apiBuiltinEnabled = builder.isAPIBuiltinEnabledSet() ? builder.getAPIBuiltinEnabled() : null;
+        logTemplateExceptions = builder.isLogTemplateExceptionsSet() ? builder.getLogTemplateExceptions() : null;
+        customDateFormats = builder.isCustomDateFormatsSet() ? builder.getCustomDateFormats() : null;
+        customNumberFormats = builder.isCustomNumberFormatsSet() ? builder.getCustomNumberFormats() : null;
+        autoImports = builder.isAutoImportsSet() ? builder.getAutoImports() : null;
+        autoIncludes = builder.isAutoIncludesSet() ? builder.getAutoIncludes() : null;
+        lazyImports = builder.isLazyImportsSet() ? builder.getLazyImports() : null;
+        lazyAutoImportsSet = builder.isLazyAutoImportsSet();
+        lazyAutoImports = lazyAutoImportsSet ? builder.getLazyAutoImports() : null;
+        customAttributes = builder.isCustomAttributesSet() ? builder.getCustomAttributes() : null;
+
+        templateLanguage = builder.isTemplateLanguageSet() ? builder.getTemplateLanguage() : null;
+        tagSyntax = builder.isTagSyntaxSet() ? builder.getTagSyntax() : null;
+        namingConvention = builder.isNamingConventionSet() ? builder.getNamingConvention() : null;
+        whitespaceStripping = builder.isWhitespaceStrippingSet() ? builder.getWhitespaceStripping() : null;
+        autoEscapingPolicy = builder.isAutoEscapingPolicySet() ? builder.getAutoEscapingPolicy() : null;
+        recognizeStandardFileExtensions = builder.isRecognizeStandardFileExtensionsSet() ? builder.getRecognizeStandardFileExtensions() : null;
+        outputFormat = builder.isOutputFormatSet() ? builder.getOutputFormat() : null;
+        sourceEncoding = builder.isSourceEncodingSet() ? builder.getSourceEncoding() : null;
+        tabSize = builder.isTabSizeSet() ? builder.getTabSize() : null;
     }
 
     /**
      * 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  
+     *             this object tries to change any non-parser settings
      */
-    public void setParentConfiguration(Configuration cfg) {
-        setParent(cfg);
+    public void setParentConfiguration(Configuration configuration) {
+        _NullArgumentException.check(configuration);
+        synchronized (this) {
+            if (this.configuration != null && this.configuration != configuration) {
+                throw new IllegalStateException(
+                        "This TemplateConfiguration was already associated to another Configuration");
+            }
+            this.configuration = configuration;
+        }
     }
 
     /**
      * Returns the parent {@link Configuration}, or {@code null} if none was associated yet.
      */
     public Configuration getParentConfiguration() {
-        return (Configuration) getParent();
+        return configuration;
     }
 
     private Configuration getNonNullParentConfiguration() {
-        MutableProcessingConfiguration parent = getParent();
-        if (parent == null) {
+        if (configuration == 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.isSourceEncodingSet()) {
-            setSourceEncoding(tc.getSourceEncoding());
-        }
-        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.isTemplateLanguageSet()) {
-            setTemplateLanguage(tc.getTemplateLanguage());
-        }
-        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.isAutoImportsSet() ? tc.getAutoImports() : null,
-                    true));
-        }
-        if (tc.isAutoIncludesSet()) {
-            setAutoIncludes(mergeLists(
-                    isAutoIncludesSet() ? getAutoIncludes() : null,
-                    tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null));
-        }
-
-        if (tc.isCustomAttributesSet()) {
-            setCustomAttributes(mergeMaps(
-                    isCustomAttributesSet() ? getCustomAttributes() : null,
-                    tc.isCustomAttributesSet() ? tc.getCustomAttributes() : null,
-                    true));
-        }
+        return configuration;
     }
 
     /**
@@ -300,7 +227,7 @@ public final class TemplateConfiguration extends MutableProcessingConfiguration<
         }
 
         if (isAPIBuiltinEnabledSet() && !template.isAPIBuiltinEnabledSet()) {
-            template.setAPIBuiltinEnabled(isAPIBuiltinEnabled());
+            template.setAPIBuiltinEnabled(getAPIBuiltinEnabled());
         }
         if (isArithmeticEngineSet() && !template.isArithmeticEngineSet()) {
             template.setArithmeticEngine(getArithmeticEngine());
@@ -393,19 +320,58 @@ public final class TemplateConfiguration extends MutableProcessingConfiguration<
         }
         
         copyDirectCustomAttributes(template, false);
+
     }
 
-    /**
-     * See {@link Configuration#setTagSyntax(int)}.
-     */
-    public void setTagSyntax(int tagSyntax) {
-        Configuration.valideTagSyntaxValue(tagSyntax);
-        this.tagSyntax = tagSyntax;
+    private static <K,V> Map<K,V> mergeMaps(Map<K,V> m1, Map<K,V> m2, boolean overwriteUpdatesOrder) {
+        if (m1 == null) return m2;
+        if (m2 == null) return m1;
+        if (m1.isEmpty()) return m2;
+        if (m2.isEmpty()) return m1;
+
+        LinkedHashMap<K, V> mergedM = new LinkedHashMap<>((m1.size() + m2.size()) * 4 / 3 + 1, 0.75f);
+        mergedM.putAll(m1);
+        if (overwriteUpdatesOrder) {
+            for (K m2Key : m2.keySet()) {
+                mergedM.remove(m2Key); // So that duplicate keys are moved after m1 keys
+            }
+        }
+        mergedM.putAll(m2);
+        return mergedM;
+    }
+
+    private static List<String> mergeLists(List<String> list1, List<String> list2) {
+        if (list1 == null) return list2;
+        if (list2 == null) return list1;
+        if (list1.isEmpty()) return list2;
+        if (list2.isEmpty()) return list1;
+
+        ArrayList<String> mergedList = new ArrayList<>(list1.size() + list2.size());
+        mergedList.addAll(list1);
+        mergedList.addAll(list2);
+        return mergedList;
     }
 
     /**
-     * The getter pair of {@link #setTagSyntax(int)}.
+     * For internal usage only, copies the custom attributes set directly on this objects into another
+     * {@link MutableProcessingConfiguration}. The target {@link MutableProcessingConfiguration} is assumed to be not seen be other thread than the current
+     * one yet. (That is, the operation is not synchronized on the target {@link MutableProcessingConfiguration}, only on the source
+     * {@link MutableProcessingConfiguration})
+     *
+     * @since 2.3.24
      */
+    private void copyDirectCustomAttributes(MutableProcessingConfiguration<?> target, boolean overwriteExisting) {
+        if (customAttributes == null) {
+            return;
+        }
+        for (Map.Entry<?, ?> custAttrEnt : customAttributes.entrySet()) {
+            Object custAttrKey = custAttrEnt.getKey();
+            if (overwriteExisting || !target.isCustomAttributeSet(custAttrKey)) {
+                target.setCustomAttribute(custAttrKey, custAttrEnt.getValue());
+            }
+        }
+    }
+
     @Override
     public int getTagSyntax() {
         return tagSyntax != null ? tagSyntax : getNonNullParentConfiguration().getTagSyntax();
@@ -416,196 +382,96 @@ public final class TemplateConfiguration extends MutableProcessingConfiguration<
         return tagSyntax != null;
     }
 
-    /**
-     * See {@link Configuration#getTemplateLanguage()}
-     */
     @Override
     public TemplateLanguage getTemplateLanguage() {
         return templateLanguage != null ? templateLanguage : getNonNullParentConfiguration().getTemplateLanguage();
     }
 
-    /**
-     * See {@link Configuration#setTemplateLanguage(TemplateLanguage)}
-     */
-    public void setTemplateLanguage(TemplateLanguage templateLanguage) {
-        _NullArgumentException.check("templateLanguage", templateLanguage);
-        this.templateLanguage = templateLanguage;
-    }
-
+    @Override
     public boolean isTemplateLanguageSet() {
         return templateLanguage != null;
     }
 
-    /**
-     * See {@link Configuration#setNamingConvention(int)}.
-     */
-    public void setNamingConvention(int namingConvention) {
-        Configuration.validateNamingConventionValue(namingConvention);
-        this.namingConvention = namingConvention;
-    }
-
-    /**
-     * The getter pair of {@link #setNamingConvention(int)}.
-     */
     @Override
     public int getNamingConvention() {
         return namingConvention != null ? namingConvention
                 : getNonNullParentConfiguration().getNamingConvention();
     }
 
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     */
     @Override
     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()
+        return whitespaceStripping != null ? whitespaceStripping
                 : getNonNullParentConfiguration().getWhitespaceStripping();
     }
 
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     */
     @Override
     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) {
-        Configuration.validateAutoEscapingPolicyValue(autoEscapingPolicy);
-
-        this.autoEscapingPolicy = Integer.valueOf(autoEscapingPolicy);
-    }
-
-    /**
-     * The getter pair of {@link #setAutoEscapingPolicy(int)}.
-     */
     @Override
     public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy.intValue()
+        return autoEscapingPolicy != null ? autoEscapingPolicy
                 : getNonNullParentConfiguration().getAutoEscapingPolicy();
     }
 
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     */
     @Override
     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}.
-     */
+    @Override
+    public ArithmeticEngine getArithmeticEngine() {
+        return isArithmeticEngineSet() ? arithmeticEngine : getNonNullParentConfiguration().getArithmeticEngine();
+    }
+
+    @Override
+    public boolean isArithmeticEngineSet() {
+        return arithmeticEngine != null;
+    }
+
     @Override
     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()
+        return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions
                 : getNonNullParentConfiguration().getRecognizeStandardFileExtensions();
     }
     
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     */
     @Override
     public boolean isRecognizeStandardFileExtensionsSet() {
         return recognizeStandardFileExtensions != null;
     }
 
+    @Override
     public Charset getSourceEncoding() {
-        return sourceEncoding != null ? sourceEncoding : getNonNullParentConfiguration().getSourceEncoding();
-    }
-
-    /**
-     * 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.
-     * 
-     * <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 sourceEncoding} in the documentation of {@link #apply(Template)}.
-     */
-    public void setSourceEncoding(Charset sourceEncoding) {
-        _NullArgumentException.check("sourceEncoding", sourceEncoding);
-        this.sourceEncoding = sourceEncoding;
+        return isSourceEncodingSet() ? sourceEncoding : getNonNullParentConfiguration().getSourceEncoding();
     }
 
+    @Override
     public boolean isSourceEncodingSet() {
         return sourceEncoding != 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()
+        return isTabSizeSet() ? tabSize
                 : 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
-     */
     @Override
     public boolean isTabSizeSet() {
         return tabSize != null;
@@ -622,322 +488,617 @@ public final class TemplateConfiguration extends MutableProcessingConfiguration<
     public Version getIncompatibleImprovements() {
         return getNonNullParentConfiguration().getIncompatibleImprovements();
     }
-    
+
     @Override
     public Locale getLocale() {
-        try {
-            return super.getLocale();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isLocaleSet() ? locale : getNonNullParentConfiguration().getLocale();
+    }
+
+    @Override
+    public boolean isLocaleSet() {
+        return locale != null;
     }
 
     @Override
     public TimeZone getTimeZone() {
-        try {
-            return super.getTimeZone();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isTimeZoneSet() ? timeZone : getNonNullParentConfiguration().getTimeZone();
+    }
+
+    @Override
+    public boolean isTimeZoneSet() {
+        return timeZone != null;
     }
 
     @Override
     public TimeZone getSQLDateAndTimeTimeZone() {
-        try {
-            return super.getSQLDateAndTimeTimeZone();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isSQLDateAndTimeTimeZoneSet() ? sqlDateAndTimeTimeZone : getNonNullParentConfiguration()
+                .getSQLDateAndTimeTimeZone();
+    }
+
+    @Override
+    public boolean isSQLDateAndTimeTimeZoneSet() {
+        return sqlDateAndTimeTimeZoneSet;
     }
 
     @Override
     public String getNumberFormat() {
-        try {
-            return super.getNumberFormat();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isNumberFormatSet() ? numberFormat : getNonNullParentConfiguration().getNumberFormat();
     }
 
     @Override
-    public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats() {
-        try {
-            return super.getCustomNumberFormats();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+    public boolean isNumberFormatSet() {
+        return numberFormat != null;
+    }
+
+    @Override
+    public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
+        return isCustomNumberFormatsSet() ? customNumberFormats : getNonNullParentConfiguration().getCustomNumberFormats();
     }
 
     @Override
     public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
-        try {
-            return super.getCustomNumberFormat(name);
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
+        if (isCustomNumberFormatsSet()) {
+            TemplateNumberFormatFactory format = customNumberFormats.get(name);
+            if (format != null) {
+                return  format;
+            }
         }
+        return getNonNullParentConfiguration().getCustomNumberFormat(name);
+    }
+
+    @Override
+    public boolean isCustomNumberFormatsSet() {
+        return customNumberFormats != null;
     }
 
     @Override
     public boolean hasCustomFormats() {
-        try {
-            return super.hasCustomFormats();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isCustomNumberFormatsSet() ? !customNumberFormats.isEmpty()
+                : getNonNullParentConfiguration().hasCustomFormats();
     }
 
     @Override
     public String getBooleanFormat() {
-        try {
-            return super.getBooleanFormat();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isBooleanFormatSet() ? booleanFormat : getNonNullParentConfiguration().getBooleanFormat();
+    }
+
+    @Override
+    public boolean isBooleanFormatSet() {
+        return booleanFormat != null;
     }
 
     @Override
     public String getTimeFormat() {
-        try {
-            return super.getTimeFormat();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isTimeFormatSet() ? timeFormat : getNonNullParentConfiguration().getTimeFormat();
+    }
+
+    @Override
+    public boolean isTimeFormatSet() {
+        return timeFormat != null;
     }
 
     @Override
     public String getDateFormat() {
-        try {
-            return super.getDateFormat();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isDateFormatSet() ? dateFormat : getNonNullParentConfiguration().getDateFormat();
+    }
+
+    @Override
+    public boolean isDateFormatSet() {
+        return dateFormat != null;
     }
 
     @Override
     public String getDateTimeFormat() {
-        try {
-            return super.getDateTimeFormat();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isDateTimeFormatSet() ? dateTimeFormat : getNonNullParentConfiguration().getDateTimeFormat();
     }
 
     @Override
-    public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats() {
-        try {
-            return super.getCustomDateFormats();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+    public boolean isDateTimeFormatSet() {
+        return dateTimeFormat != null;
+    }
+
+    @Override
+    public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
+        return isCustomDateFormatsSet() ? customDateFormats : getNonNullParentConfiguration().getCustomDateFormats();
     }
 
     @Override
     public TemplateDateFormatFactory getCustomDateFormat(String name) {
-        try {
-            return super.getCustomDateFormat(name);
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
+        if (isCustomDateFormatsSet()) {
+            TemplateDateFormatFactory format = customDateFormats.get(name);
+            if (format != null) {
+                return  format;
+            }
         }
+        return getNonNullParentConfiguration().getCustomDateFormat(name);
+    }
+
+    @Override
+    public boolean isCustomDateFormatsSet() {
+        return customDateFormats != null;
     }
 
     @Override
     public TemplateExceptionHandler getTemplateExceptionHandler() {
-        try {
-            return super.getTemplateExceptionHandler();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isTemplateExceptionHandlerSet() ? templateExceptionHandler : getNonNullParentConfiguration().getTemplateExceptionHandler();
     }
 
     @Override
-    public ArithmeticEngine getArithmeticEngine() {
-        try {
-            return super.getArithmeticEngine();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+    public boolean isTemplateExceptionHandlerSet() {
+        return templateExceptionHandler != null;
     }
 
     @Override
     public ObjectWrapper getObjectWrapper() {
-        try {
-            return super.getObjectWrapper();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isObjectWrapperSet() ? objectWrapper : getNonNullParentConfiguration().getObjectWrapper();
+    }
+
+    @Override
+    public boolean isObjectWrapperSet() {
+        return objectWrapper != null;
     }
 
     @Override
     public Charset getOutputEncoding() {
-        try {
-            return super.getOutputEncoding();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isOutputEncodingSet() ? outputEncoding : getNonNullParentConfiguration().getOutputEncoding();
+    }
+
+    @Override
+    public boolean isOutputEncodingSet() {
+        return outputEncodingSet;
     }
 
     @Override
     public Charset getURLEscapingCharset() {
-        try {
-            return super.getURLEscapingCharset();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isURLEscapingCharsetSet() ? urlEscapingCharset : getNonNullParentConfiguration().getURLEscapingCharset();
+    }
+
+    @Override
+    public boolean isURLEscapingCharsetSet() {
+        return urlEscapingCharsetSet;
     }
 
     @Override
     public TemplateClassResolver getNewBuiltinClassResolver() {
-        try {
-            return super.getNewBuiltinClassResolver();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isNewBuiltinClassResolverSet() ? newBuiltinClassResolver : getNonNullParentConfiguration().getNewBuiltinClassResolver();
+    }
+
+    @Override
+    public boolean isNewBuiltinClassResolverSet() {
+        return newBuiltinClassResolver != null;
+    }
+
+    @Override
+    public boolean getAPIBuiltinEnabled() {
+        return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getNonNullParentConfiguration().getAPIBuiltinEnabled();
+    }
+
+    @Override
+    public boolean isAPIBuiltinEnabledSet() {
+        return apiBuiltinEnabled != null;
     }
 
     @Override
     public boolean getAutoFlush() {
-        try {
-            return super.getAutoFlush();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isAutoFlushSet() ? autoFlush : getNonNullParentConfiguration().getAutoFlush();
+    }
+
+    @Override
+    public boolean isAutoFlushSet() {
+        return autoFlush != null;
     }
 
     @Override
     public boolean getShowErrorTips() {
-        try {
-            return super.getShowErrorTips();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isShowErrorTipsSet() ? showErrorTips : getNonNullParentConfiguration().getShowErrorTips();
     }
 
     @Override
-    public boolean isAPIBuiltinEnabled() {
-        try {
-            return super.isAPIBuiltinEnabled();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+    public boolean isShowErrorTipsSet() {
+        return showErrorTips != null;
     }
 
     @Override
     public boolean getLogTemplateExceptions() {
-        try {
-            return super.getLogTemplateExceptions();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isLogTemplateExceptionsSet() ? logTemplateExceptions : getNonNullParentConfiguration().getLogTemplateExceptions();
+    }
+
+    @Override
+    public boolean isLogTemplateExceptionsSet() {
+        return logTemplateExceptions != null;
     }
 
     @Override
     public boolean getLazyImports() {
-        try {
-            return super.getLazyImports();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isLazyImportsSet() ? lazyImports : getNonNullParentConfiguration().getLazyImports();
+    }
+
+    @Override
+    public boolean isLazyImportsSet() {
+        return lazyImports != null;
     }
 
     @Override
     public Boolean getLazyAutoImports() {
-        try {
-            return super.getLazyAutoImports();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isLazyAutoImportsSet() ? lazyAutoImports : getNonNullParentConfiguration().getLazyAutoImports();
+    }
+
+    @Override
+    public boolean isLazyAutoImportsSet() {
+        return lazyAutoImportsSet;
     }
 
     @Override
     public Map<String, String> getAutoImports() {
-        try {
-            return super.getAutoImports();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isAutoImportsSet() ? autoImports : getNonNullParentConfiguration().getAutoImports();
+    }
+
+    @Override
+    public boolean isAutoImportsSet() {
+        return autoImports != null;
     }
 
     @Override
     public List<String> getAutoIncludes() {
-        try {
-            return super.getAutoIncludes();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+        return isAutoIncludesSet() ? autoIncludes : getNonNullParentConfiguration().getAutoIncludes();
     }
 
     @Override
-    public String[] getCustomAttributeNames() {
-        try {
-            return super.getCustomAttributeNames();
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
-        }
+    public boolean isAutoIncludesSet() {
+        return autoIncludes != null;
+    }
+
+    @Override
+    public Map<Object, Object> getCustomAttributes() {
+        return isCustomAttributesSet() ? customAttributes : getNonNullParentConfiguration().getCustomAttributes();
+    }
+
+    @Override
+    public boolean isCustomAttributesSet() {
+        return customAttributes != null;
     }
 
     @Override
     public Object getCustomAttribute(Object name) {
-        try {
-            return super.getCustomAttribute(name);
-        } catch (NullPointerException e) {
-            getNonNullParentConfiguration();
-            throw e;
+        Object attValue;
+        if (isCustomAttributesSet()) {
+            attValue = customAttributes.get(name);
+            if (attValue != null || customAttributes.containsKey(name)) {
+                return attValue;
+            }
         }
+        return getNonNullParentConfiguration().getCustomAttribute(name);
     }
 
-    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
+    public static final class Builder extends MutableProcessingAndParseConfiguration<Builder>
+            implements CommonBuilder<TemplateConfiguration> {
+
+        public Builder() {
+            super((Configuration) null);
+        }
+
+        @Override
+        public TemplateConfiguration build() {
+            return new TemplateConfiguration(this);
+        }
+
+        // TODO This will be removed
+        @Override
+        void setParent(ProcessingConfiguration cfg) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected Locale getInheritedLocale() {
+            throw new SettingValueNotSetException("locale");
+        }
+
+        @Override
+        protected TimeZone getInheritedTimeZone() {
+            throw new SettingValueNotSetException("timeZone");
+        }
+
+        @Override
+        protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
+            throw new SettingValueNotSetException("SQLDateAndTimeTimeZone");
+        }
+
+        @Override
+        protected String getInheritedNumberFormat() {
+            throw new SettingValueNotSetException("numberFormat");
+        }
+
+        @Override
+        protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
+            throw new SettingValueNotSetException("customNumberFormats");
+        }
+
+        @Override
+        protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
+            return null;
+        }
+
+        @Override
+        protected boolean getInheritedHasCustomFormats() {
+            return false;
+        }
+
+        @Override
+        protected String getInheritedBooleanFormat() {
+            throw new SettingValueNotSetException("booleanFormat");
+        }
+
+        @Override
+        protected String getInheritedTimeFormat() {
+            throw new SettingValueNotSetException("timeFormat");
+        }
+
+        @Override
+        protected String getInheritedDateFormat() {
+            throw new SettingValueNotSetException("dateFormat");
+        }
+
+        @Override
+        protected String getInheritedDateTimeFormat() {
+            throw new SettingValueNotSetException("dateTimeFormat");
+        }
+
+        @Override
+        protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
+            throw new SettingValueNotSetException("customDateFormats");
+        }
+
+        @Override
+        protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
+            throw new SettingValueNotSetException("customDateFormat");
+        }
+
+        @Override
+        protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
+            throw new SettingValueNotSetException("templateExceptionHandler");
+        }
+
+        @Override
+        protected ArithmeticEngine getInheritedArithmeticEngine() {
+            throw new SettingValueNotSetException("arithmeticEngine");
+        }
+
+        @Override
+        protected ObjectWrapper getInheritedObjectWrapper() {
+            throw new SettingValueNotSetException("objectWrapper");
+        }
+
+        @Override
+        protected Charset getInheritedOutputEncoding() {
+            throw new SettingValueNotSetException("outputEncoding");
+        }
+
+        @Override
+        protected Charset getInheritedURLEscapingCharset() {
+            throw new SettingValueNotSetException("URLEscapingCharset");
+        }
+
+        @Override
+        protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
+            throw new SettingValueNotSetException("newBuiltinClassResolver");
+        }
+
+        @Override
+        protected boolean getInheritedAutoFlush() {
+            throw new SettingValueNotSetException("autoFlush");
+        }
+
+        @Override
+        protected boolean getInheritedShowErrorTips() {
+            throw new SettingValueNotSetException("showErrorTips");
+        }
+
+        @Override
+        protected boolean getInheritedAPIBuiltinEnabled() {
+            throw new SettingValueNotSetException("APIBuiltinEnabled");
+        }
+
+        @Override
+        protected boolean getInheritedLogTemplateExceptions() {
+            throw new SettingValueNotSetException("logTemplateExceptions");
+        }
+
+        @Override
+        protected boolean getInheritedLazyImports() {
+            throw new SettingValueNotSetException("lazyImports");
+        }
+
+        @Override
+        protected Boolean getInheritedLazyAutoImports() {
+            throw new SettingValueNotSetException("lazyAutoImports");
+        }
+
+        @Override
+        protected Map<String, String> getInheritedAutoImports() {
+            throw new SettingValueNotSetException("autoImports");
+        }
+
+        @Override
+        protected List<String> getInheritedAutoIncludes() {
+            throw new SettingValueNotSetException("autoIncludes");
+        }
+
+        @Override
+        protected Object getInheritedCustomAttribute(Object name) {
+            return null;
+        }
+
+        /**
+         * Set all settings in this {@link Builder} 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(ParserAndProcessingConfiguration tc) {
+            if (tc.isAPIBuiltinEnabledSet()) {
+                setAPIBuiltinEnabled(tc.getAPIBuiltinEnabled());
+            }
+            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.isSourceEncodingSet()) {
+                setSourceEncoding(tc.getSourceEncoding());
+            }
+            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.isTemplateLanguageSet()) {
+                setTemplateLanguage(tc.getTemplateLanguage());
+            }
+            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.isAutoImportsSet() ? tc.getAutoImports() : null,
+                        true));
+            }
+            if (tc.isAutoIncludesSet()) {
+                setAutoIncludes(mergeLists(
+                        isAutoIncludesSet() ? getAutoIncludes() : null,
+                        tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null));
+            }
+
+            if (tc.isCustomAttributesSet()) {
+                setCustomAttributes(mergeMaps(
+                        isCustomAttributesSet() ? getCustomAttributes() : null,
+                        tc.isCustomAttributesSet() ? tc.getCustomAttributes() : null,
+                        true));
+            }
+        }
+
+        @Override
+        public Version getIncompatibleImprovements() {
+            throw new SettingValueNotSetException("incompatibleImprovements");
+        }
+
+        @Override
+        protected int getInheritedTagSyntax() {
+            throw new SettingValueNotSetException("tagSyntax");
+        }
+
+        @Override
+        protected TemplateLanguage getInheritedTemplateLanguage() {
+            throw new SettingValueNotSetException("templateLanguage");
+        }
+
+        @Override
+        protected int getInheritedNamingConvention() {
+            throw new SettingValueNotSetException("namingConvention");
+        }
+
+        @Override
+        protected boolean getInheritedWhitespaceStripping() {
+            throw new SettingValueNotSetException("whitespaceStripping");
+        }
+
+        @Override
+        protected int getInheritedAutoEscapingPolicy() {
+            throw new SettingValueNotSetException("autoEscapingPolicy");
+        }
+
+        @Override
+        protected OutputFormat getInheritedOutputFormat() {
+            throw new SettingValueNotSetException("outputFormat");
+        }
+
+        @Override
+        protected boolean getInheritedRecognizeStandardFileExtensions() {
+            throw new SettingValueNotSetException("recognizeStandardFileExtensions");
+        }
+
+        @Override
+        protected Charset getInheritedSourceEncoding() {
+            throw new SettingValueNotSetException("sourceEncoding");
+        }
+
+        @Override
+        protected int getInheritedTabSize() {
+            throw new SettingValueNotSetException("tabSize");
         }
-        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/88baea20/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
index bfaf93f..c9c7380 100644
--- a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
+++ b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
@@ -65,6 +65,11 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
     }
 
     @Override
+    public boolean isTemplateLanguageSet() {
+        return wrappedPCfg.isTemplateLanguageSet();
+    }
+
+    @Override
     public OutputFormat getOutputFormat() {
         return outputFormat != null ? outputFormat : wrappedPCfg.getOutputFormat();
     }
@@ -101,7 +106,7 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
 
     @Override
     public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy.intValue() : wrappedPCfg.getAutoEscapingPolicy();
+        return autoEscapingPolicy != null ? autoEscapingPolicy : wrappedPCfg.getAutoEscapingPolicy();
     }
 
     @Override
@@ -129,8 +134,14 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
         return wrappedPCfg.isTabSizeSet();
     }
 
+    @Override
     public Charset getSourceEncoding() {
         return wrappedPCfg.getSourceEncoding();
     }
 
+    @Override
+    public boolean isSourceEncodingSet() {
+        return wrappedPCfg.isSourceEncodingSet();
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index c7f9963..2a54618 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -50,7 +50,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core._CoreLogs;
 import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.BuilderBase;
+import org.apache.freemarker.core.util.FluentBuilder;
 import org.apache.freemarker.core.util._JavaVersions;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.slf4j.Logger;
@@ -1073,7 +1073,7 @@ class ClassIntrospector {
         }
     }
 
-    static final class Builder extends BuilderBase<ClassIntrospector, Builder> implements Cloneable {
+    static final class Builder extends FluentBuilder<ClassIntrospector, Builder> implements Cloneable {
 
         private static final Map/*<PropertyAssignments, Reference<ClassIntrospector>>*/ INSTANCE_CACHE = new HashMap();
         private static final ReferenceQueue INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
index 6cde3ee..d01c902 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
@@ -64,7 +64,7 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.BuilderBase;
+import org.apache.freemarker.core.util.FluentBuilder;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.dom.NodeModel;
 import org.slf4j.Logger;
@@ -1326,7 +1326,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
      */
     protected abstract static class ExtendableBuilder<
             ProductT extends DefaultObjectWrapper, SelfT extends ExtendableBuilder<ProductT, SelfT>>
-            extends BuilderBase<ProductT, SelfT> implements Cloneable {
+            extends FluentBuilder<ProductT, SelfT> implements Cloneable {
 
         private final Version incompatibleImprovements;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
index 3945770..231c20a 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
@@ -26,7 +26,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
-import org.apache.freemarker.core.util.BuilderBase;
+import org.apache.freemarker.core.util.FluentBuilder;
 
 /**
  * Utility method for caching {@link DefaultObjectWrapper} (and subclasses) sigletons per Thread Context Class
@@ -119,7 +119,7 @@ final class DefaultObjectWrapperTCCLSingletonUtil {
     /**
      * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
      * Used when the builder delegates the product creation to something else (typically, an instance cache). Calling
-     * {@link BuilderBase#build()} would be infinite recursion in such cases.
+     * {@link FluentBuilder#build()} would be infinite recursion in such cases.
      */
     public interface _ConstructorInvoker<ProductT, BuilderT> {
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
index b83b69c..5e9c37b 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
@@ -41,31 +41,37 @@ public class MergingTemplateConfigurationFactory extends TemplateConfigurationFa
     @Override
     public TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
             throws IOException, TemplateConfigurationFactoryException {
-        TemplateConfiguration mergedTC = null;
-        TemplateConfiguration resultTC = null;
+        TemplateConfiguration.Builder mergedTCBuilder = null;
+        TemplateConfiguration firstResultTC = null;
         for (TemplateConfigurationFactory tcf : templateConfigurationFactories) {
             TemplateConfiguration tc = tcf.get(sourceName, templateLoadingSource);
             if (tc != null) {
-                if (resultTC == null) {
-                    resultTC = tc;
+                if (firstResultTC == null) {
+                    firstResultTC = tc;
                 } else {
-                    if (mergedTC == null) {
-                        Configuration cfg = getConfiguration();
-                        if (cfg == null) {
-                            throw new IllegalStateException(
-                                    "The TemplateConfigurationFactory wasn't associated to a Configuration yet.");
-                        }
-                        
-                        mergedTC = new TemplateConfiguration();
-                        mergedTC.setParentConfiguration(cfg);
-                        mergedTC.merge(resultTC);
-                        resultTC = mergedTC;
+                    if (mergedTCBuilder == null) {
+                        mergedTCBuilder = new TemplateConfiguration.Builder();
+                        mergedTCBuilder.merge(firstResultTC);
                     }
-                    mergedTC.merge(tc);
+                    mergedTCBuilder.merge(tc);
                 }
             }
         }
-        return resultTC;
+
+        if (mergedTCBuilder == null) {
+            return firstResultTC; // Maybe null
+        } else {
+            TemplateConfiguration mergedTC = mergedTCBuilder.build();
+
+            Configuration cfg = getConfiguration();
+            if (cfg == null) {
+                throw new IllegalStateException(
+                        "The TemplateConfigurationFactory wasn't associated to a Configuration yet.");
+            }
+            mergedTC.setParentConfiguration(cfg);
+
+            return mergedTC;
+        }
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
index e1a9490..857b15d 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
@@ -95,8 +95,9 @@ public final class TemplateLoadingResult {
      * @param templateConfiguration
      *            Usually {@code null}, as usually the backing storage mechanism doesn't store such information; see
      *            {@link #getTemplateConfiguration()}. The most probable application is supplying the charset (encoding)
-     *            used by the {@link InputStream} (via {@link TemplateConfiguration#setSourceEncoding(Charset)}), but
-     *            only do that if the storage mechanism really knows what the charset is.
+     *            used by the {@link InputStream} (via
+     *            {@link TemplateConfiguration.Builder#setSourceEncoding(Charset)}), but only do that if the storage
+     *            mechanism really knows what the charset is.
      */
     public TemplateLoadingResult(TemplateLoadingSource source, Serializable version, InputStream inputStream,
             TemplateConfiguration templateConfiguration) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
index 537b4eb..e06eba1 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
@@ -534,16 +534,14 @@ public class DefaultTemplateResolver extends TemplateResolver {
             }
             TemplateConfiguration resultTC = templateLoaderResult.getTemplateConfiguration();
             if (resultTC != null) {
-                TemplateConfiguration mergedTC = new TemplateConfiguration();
+                TemplateConfiguration.Builder mergedTCBuilder = new TemplateConfiguration.Builder();
                 if (cfgTC != null) {
-                    mergedTC.merge(cfgTC);
+                    mergedTCBuilder.merge(cfgTC);
                 }
-                if (resultTC != null) {
-                    mergedTC.merge(resultTC);
-                }
-                mergedTC.setParentConfiguration(config);
-                
-                tc = mergedTC;
+                mergedTCBuilder.merge(resultTC);
+
+                tc = mergedTCBuilder.build();
+                tc.setParentConfiguration(config);
             } else {
                 tc = cfgTC;
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/util/BuilderBase.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/BuilderBase.java b/src/main/java/org/apache/freemarker/core/util/BuilderBase.java
deleted file mode 100644
index c8eb12c..0000000
--- a/src/main/java/org/apache/freemarker/core/util/BuilderBase.java
+++ /dev/null
@@ -1,34 +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.util;
-
-/**
- * Common superclass of builders (used for implementing the builder pattern).
- */
-public abstract class BuilderBase<ProductT, SelfT extends BuilderBase<ProductT, SelfT>> {
-
-    public abstract ProductT build();
-
-    @SuppressWarnings("unchecked")
-    protected SelfT self() {
-        return (SelfT) this;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java b/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
new file mode 100644
index 0000000..8939121
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.util;
+
+/**
+ * Interface of builders (used for implementing the builder pattern).
+ */
+public interface CommonBuilder<ProductT> {
+
+    ProductT build();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java b/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java
new file mode 100644
index 0000000..a5e1e5a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util;
+
+/**
+ * Common superclass for implementing {@link CommonBuilder}; adds helper method to implement a fluent API.
+ */
+public abstract class FluentBuilder<ProductT, SelfT extends FluentBuilder<ProductT, SelfT>>
+        implements CommonBuilder<ProductT> {
+
+    @Override
+    public abstract ProductT build();
+
+    @SuppressWarnings("unchecked")
+    protected SelfT self() {
+        return (SelfT) this;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java b/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
index 7c100bb..7bd98f4 100644
--- a/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
+++ b/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
@@ -23,7 +23,7 @@ package org.apache.freemarker.core.util;
  * A builder that encloses an already built product. {@link #build()} will always return the same product object.
  */
 public class ProductWrappingBuilder<ProductT, SelfT extends ProductWrappingBuilder<ProductT, SelfT>>
-        extends BuilderBase<ProductT, SelfT> {
+        extends FluentBuilder<ProductT, SelfT> {
 
     private final ProductT product;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/manual/en_US/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/src/manual/en_US/FM3-CHANGE-LOG.txt b/src/manual/en_US/FM3-CHANGE-LOG.txt
index 2e10077..ccf69b1 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -187,6 +187,7 @@ the FreeMarer 3 changelog here:
   - Renamed Configuration.defaultEncoding to sourceEncoding, also added sourceEncoding to ParserConfiguration, and renamed
     TemplateConfiguration.encoding and Template.encoding to sourceEncoding. (Before this, defaultEncoding was exclusive
     to Configuration, but now it's like any other ParserConfiguration setting that can be overidden on the 3 levels.)
+  - Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder.
 - Settings that have contained a charset name (sourceEncoding, outputEncoding, URLEscapingCharset) are now of type Charset,
   not String. For string based configuration sources (such as .properties files) this means that:
   - Unrecognized charset names are now errors

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 55a40d0..ccfeca0 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -261,11 +261,11 @@ public class ConfigurationTest extends TestCase {
         cfg.setLocale(Locale.GERMAN);
         cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
 
-        TemplateConfiguration huTC = new TemplateConfiguration();
-        huTC.setSourceEncoding(ISO_8859_2);
+        TemplateConfiguration.Builder huTCB = new TemplateConfiguration.Builder();
+        huTCB.setSourceEncoding(ISO_8859_2);
         cfg.setTemplateConfigurations(
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*_hu.*"),
-                huTC));
+                huTCB.build()));
 
         ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
         tl.putTemplate(tFtl, "${1}".getBytes(StandardCharsets.UTF_8));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/DateFormatTest.java b/src/test/java/org/apache/freemarker/core/DateFormatTest.java
index f58e7fc..4fdb1c7 100644
--- a/src/test/java/org/apache/freemarker/core/DateFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/DateFormatTest.java
@@ -333,11 +333,12 @@ public class DateFormatTest extends TemplateTest {
                 "m", new AliasTemplateDateFormatFactory("yyyy-MMM"),
                 "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE));
         
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setCustomDateFormats(ImmutableMap.of(
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomDateFormats(ImmutableMap.of(
                 "m", new AliasTemplateDateFormatFactory("yyyy-MMMM"),
                 "i", new AliasTemplateDateFormatFactory("@epoch")));
-        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tc));
+        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
+                new FileNameGlobMatcher("*2*"), tcb.build()));
         
         addToDataModel("d", TM);
         String commonFtl = "${d?string.@d} ${d?string.@m} "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
index a267645..baee63b 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -34,10 +34,10 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         Configuration cfg = getConfiguration();
         cfg.addAutoImport("t1", "t1.ftl");
 
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.addAutoImport("t2", "t2.ftl");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.addAutoImport("t2", "t2.ftl");
         cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -88,10 +88,10 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         cfg.addAutoImport("t2", "t2.ftl");
         cfg.addAutoImport("t3", "t3.ftl");
 
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.addAutoImport("t2", "t2b.ftl");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.addAutoImport("t2", "t2b.ftl");
         cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -128,10 +128,10 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         Configuration cfg = getConfiguration();
         cfg.addAutoInclude("t1.ftl");
 
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.addAutoInclude("t2.ftl");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.addAutoInclude("t2.ftl");
         cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -182,10 +182,10 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         cfg.addAutoInclude("t2.ftl");
         cfg.addAutoInclude("t3.ftl");
 
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.addAutoInclude("t2.ftl");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.addAutoInclude("t2.ftl");
         cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -233,11 +233,11 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         cfg.addAutoInclude("t1.ftl");
         cfg.addAutoInclude("t1.ftl");
 
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.addAutoInclude("t2.ftl");
-        tc.addAutoInclude("t2.ftl");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.addAutoInclude("t2.ftl");
+        tcb.addAutoInclude("t2.ftl");
         cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -301,8 +301,9 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         assertEquals(expectedOutput, sw.toString());
     }
 
-    private void setLazynessOfConfigurable(MutableProcessingConfiguration cfg, Boolean lazyImports, Boolean lazyAutoImports,
-            boolean setLazyAutoImports) {
+    private void setLazynessOfConfigurable(
+            MutableProcessingConfiguration<?> cfg,
+            Boolean lazyImports, Boolean lazyAutoImports, boolean setLazyAutoImports) {
         if (lazyImports != null) {
             cfg.setLazyImports(lazyImports);
         }


[4/4] incubator-freemarker git commit: Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder. (This is not the final version of TemplateConfiguration; it will be become cleaner as Template also becomes immutable.)

Posted by dd...@apache.org.
Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder. (This is not the final version of TemplateConfiguration; it will be become cleaner as Template also becomes immutable.)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/88baea20
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/88baea20
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/88baea20

Branch: refs/heads/3
Commit: 88baea20cade54dad8a932124d5ecc691ea088d9
Parents: 23f66eb
Author: ddekany <dd...@apache.org>
Authored: Wed Apr 5 21:55:34 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Wed Apr 5 21:55:34 2017 +0200

----------------------------------------------------------------------
 .../core/BuiltInsForMultipleTypes.java          |    4 +-
 .../freemarker/core/BuiltInsForStringsMisc.java |    8 +-
 .../apache/freemarker/core/Configuration.java   |  243 ++--
 .../org/apache/freemarker/core/Environment.java |  274 +++++
 .../MutableProcessingAndParseConfiguration.java |   48 +-
 .../core/MutableProcessingConfiguration.java    |  346 +++---
 .../core/ParserAndProcessingConfiguration.java  |   29 +
 .../freemarker/core/ParserConfiguration.java    |    5 +-
 .../core/ProcessingConfiguration.java           |   14 +-
 .../org/apache/freemarker/core/Template.java    |  148 +++
 .../freemarker/core/TemplateBooleanFormat.java  |   91 ++
 .../freemarker/core/TemplateConfiguration.java  | 1163 ++++++++++--------
 ..._ParserConfigurationWithInheritedFormat.java |   13 +-
 .../core/model/impl/ClassIntrospector.java      |    4 +-
 .../core/model/impl/DefaultObjectWrapper.java   |    4 +-
 .../DefaultObjectWrapperTCCLSingletonUtil.java  |    4 +-
 .../MergingTemplateConfigurationFactory.java    |   40 +-
 .../templateresolver/TemplateLoadingResult.java |    5 +-
 .../impl/DefaultTemplateResolver.java           |   14 +-
 .../freemarker/core/util/BuilderBase.java       |   34 -
 .../freemarker/core/util/CommonBuilder.java     |   29 +
 .../freemarker/core/util/FluentBuilder.java     |   36 +
 .../core/util/ProductWrappingBuilder.java       |    2 +-
 src/manual/en_US/FM3-CHANGE-LOG.txt             |    1 +
 .../freemarker/core/ConfigurationTest.java      |    6 +-
 .../apache/freemarker/core/DateFormatTest.java  |    7 +-
 .../IncludeAndImportConfigurableLayersTest.java |   37 +-
 .../freemarker/core/OutputFormatTest.java       |   24 +-
 .../core/TemplateConfigurationTest.java         |  330 ++---
 ...igurationWithDefaltTemplateResolverTest.java |  264 ----
 ...gurationWithDefaultTemplateResolverTest.java |  264 ++++
 .../core/TemplateGetEncodingTest.java           |    7 +-
 .../TemplateConfigurationFactoryTest.java       |   27 +-
 .../core/valueformat/NumberFormatTest.java      |    7 +-
 .../ConfigureOutputFormatExamples.java          |   16 +-
 .../TemplateConfigurationExamples.java          |   50 +-
 .../servlet/FreemarkerServletTest.java          |   12 +-
 37 files changed, 2217 insertions(+), 1393 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index e75ea9e..ab3df64 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -229,11 +229,11 @@ class BuiltInsForMultipleTypes {
     static class apiBI extends ASTExpBuiltIn {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
-            if (!env.isAPIBuiltinEnabled()) {
+            if (!env.getAPIBuiltinEnabled()) {
                 throw new _MiscTemplateException(this,
                         "Can't use ?api, because the \"", MutableProcessingConfiguration.API_BUILTIN_ENABLED_KEY,
                         "\" configuration setting is false. Think twice before you set it to true though. Especially, "
-                        + "it shouldn't abussed for modifying Map-s and Collection-s.");
+                        + "it shouldn't abused for modifying Map-s and Collection-s.");
             }
             final TemplateModel tm = target.eval(env);
             if (!(tm instanceof TemplateModelWithAPISupport)) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index d0deade..f83cbbb 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -46,13 +46,13 @@ class BuiltInsForStringsMisc {
         @Override
         TemplateModel calculateResult(String s, Environment env)  throws TemplateException {
             final boolean b;
-            if (s.equals("true")) {
+            if (s.equals(MiscUtil.C_TRUE)) {
                 b = true;
-            } else if (s.equals("false")) {
+            } else if (s.equals(MiscUtil.C_FALSE)) {
                 b = false;
-            } else if (s.equals(env.getTrueStringValue())) {
+            } else if (s.equals(env.getTemplateBooleanFormat().getTrueStringValue())) {
                 b = true;
-            } else if (s.equals(env.getFalseStringValue())) {
+            } else if (s.equals(env.getTemplateBooleanFormat().getFalseStringValue())) {
                 b = false;
             } else {
                 throw new _MiscTemplateException(this, env,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java b/src/main/java/org/apache/freemarker/core/Configuration.java
index 52bfd19..c8e700a 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -43,6 +43,7 @@ import java.util.TimeZone;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
@@ -93,6 +94,8 @@ import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.util._SortedArraySet;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
 /**
  * <b>The main entry point into the FreeMarker API</b>; encapsulates the configuration settings of FreeMarker,
@@ -142,7 +145,7 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
  * The methods that aren't for modifying settings, like {@link #getTemplate(String)}, are thread-safe.
  */
 public final class Configuration extends MutableProcessingConfiguration<Configuration>
-        implements Cloneable, ParserConfiguration, ProcessingConfiguration, CustomStateScope {
+        implements Cloneable, ParserAndProcessingConfiguration, CustomStateScope {
     
     private static final String VERSION_PROPERTIES_PATH = "org/apache/freemarker/core/version.properties";
     
@@ -522,6 +525,16 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         return super.getTemplateExceptionHandler();
     }
 
+    @Override
+    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected ArithmeticEngine getInheritedArithmeticEngine() {
+        throw new BugException("Missing property value");
+    }
+
     private void recreateTemplateResolverWith(
             TemplateLoader loader, CacheStorage storage,
             TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,
@@ -767,8 +780,8 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
     /**
      * Sets a {@link TemplateConfigurationFactory} that will configure individual templates where their settings differ
      * from those coming from the common {@link Configuration} object. A typical use case for that is specifying the
-     * {@link TemplateConfiguration#setOutputFormat(OutputFormat) outputFormat} for templates based on their file
-     * extension or parent directory.
+     * {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat) outputFormat} for templates based on their
+     * file extension or parent directory.
      * 
      * <p>
      * Note that the settings suggested by standard file extensions are stronger than that you set here. See
@@ -1003,7 +1016,42 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
             }
         }
     }
-    
+
+    @Override
+    protected ObjectWrapper getInheritedObjectWrapper() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected Charset getInheritedOutputEncoding() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected Charset getInheritedURLEscapingCharset() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected boolean getInheritedAutoFlush() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected boolean getInheritedShowErrorTips() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected boolean getInheritedAPIBuiltinEnabled() {
+        throw new BugException("Missing property value");
+    }
+
     /**
      * Resets the setting to its default, as if it was never set. This means that when you change the
      * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
@@ -1033,6 +1081,11 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         localeExplicitlySet = true;
     }
 
+    @Override
+    protected Locale getInheritedLocale() {
+        throw new BugException("Missing property value");
+    }
+
     /**
      * Resets the setting to its default, as if it was never set.
      *
@@ -1065,6 +1118,66 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         timeZoneExplicitlySet = true;
     }
 
+    @Override
+    protected TimeZone getInheritedTimeZone() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected String getInheritedNumberFormat() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
+        return null;
+    }
+
+    @Override
+    protected boolean getInheritedHasCustomFormats() {
+        return false;
+    }
+
+    @Override
+    protected String getInheritedBooleanFormat() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected String getInheritedTimeFormat() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected String getInheritedDateFormat() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected String getInheritedDateTimeFormat() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
+        return null;
+    }
+
     /**
      * Resets the setting to its default, as if it was never set.
      *
@@ -1130,6 +1243,31 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         logTemplateExceptionsExplicitlySet = true;
     }
 
+    @Override
+    protected boolean getInheritedLogTemplateExceptions() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected boolean getInheritedLazyImports() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected Boolean getInheritedLazyAutoImports() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected Map<String, String> getInheritedAutoImports() {
+        throw new BugException("Missing property value");
+    }
+
+    @Override
+    protected List<String> getInheritedAutoIncludes() {
+        throw new BugException("Missing property value");
+    }
+
     /**
      * Resets the setting to its default, as if it was never set. This means that when you change the
      * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
@@ -1260,8 +1398,8 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
      * Auto-escaping has significance when a value is printed with <code>${...}</code> (or <code>#{...}</code>). If
      * auto-escaping is on, FreeMarker will assume that the value is plain text (as opposed to markup or some kind of
      * rich text), so it will escape it according the current output format (see {@link #setOutputFormat(OutputFormat)}
-     * and {@link TemplateConfiguration#setOutputFormat(OutputFormat)}). If auto-escaping is off, FreeMarker will assume
-     * that the string value is already in the output format, so it prints it as is to the output.
+     * and {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat)}). If auto-escaping is off, FreeMarker
+     * will assume that the string value is already in the output format, so it prints it as is to the output.
      *
      * <p>Further notes on auto-escaping:
      * <ul>
@@ -1286,9 +1424,9 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
      *          One of the {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
      *          {@link #ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}, and {@link #DISABLE_AUTO_ESCAPING_POLICY} constants.  
      * 
-     * @see TemplateConfiguration#setAutoEscapingPolicy(int)
+     * @see TemplateConfiguration.Builder#setAutoEscapingPolicy(int)
      * @see Configuration#setOutputFormat(OutputFormat)
-     * @see TemplateConfiguration#setOutputFormat(OutputFormat)
+     * @see TemplateConfiguration.Builder#setOutputFormat(OutputFormat)
      * 
      * @since 2.3.24
      */
@@ -1573,15 +1711,15 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
      * 2.3.24, it defaults to {@code true}, so the following standard file extensions take their effect:
      * 
      * <ul>
-     *   <li>{@code ftlh}: Sets {@link TemplateConfiguration#setOutputFormat(OutputFormat) outputFormat} to
+     *   <li>{@code ftlh}: Sets {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat) outputFormat} to
      *       {@code "HTML"} (i.e., {@link HTMLOutputFormat#INSTANCE}, unless the {@code "HTML"} name is overridden by
      *       {@link #setRegisteredCustomOutputFormats(Collection)}) and
-     *       {@link TemplateConfiguration#setAutoEscapingPolicy(int) autoEscapingPolicy} to
+     *       {@link TemplateConfiguration.Builder#setAutoEscapingPolicy(int) autoEscapingPolicy} to
      *       {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
-     *   <li>{@code ftlx}: Sets {@link TemplateConfiguration#setOutputFormat(OutputFormat) outputFormat} to
+     *   <li>{@code ftlx}: Sets {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat) outputFormat} to
      *       {@code "XML"} (i.e., {@link XMLOutputFormat#INSTANCE}, unless the {@code "XML"} name is overridden by
      *       {@link #setRegisteredCustomOutputFormats(Collection)}) and
-     *       {@link TemplateConfiguration#setAutoEscapingPolicy(int) autoEscapingPolicy} to
+     *       {@link TemplateConfiguration.Builder#setAutoEscapingPolicy(int) autoEscapingPolicy} to
      *       {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
      * </ul>
      * 
@@ -1647,6 +1785,11 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         return templateLanguage;
     }
 
+    @Override
+    public boolean isTemplateLanguageSet() {
+        return true;
+    }
+
     /**
      * Sets the template language used; this is often overridden for certain file extension with
      * {@link #setTemplateConfigurations(TemplateConfigurationFactory)}.
@@ -2041,7 +2184,7 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
      * another, so <b>you should always set this setting</b>. If you don't know what charset your should chose,
      * {@code "UTF-8"} is usually a good choice.
      *
-     * @param sourceEncoding The charset, for example {@link StandardCharsets#UTF_8}.
+     * @param sourceEncoding The charset, for example such as {@link StandardCharsets#UTF_8}.
      */
     public void setSourceEncoding(Charset sourceEncoding) {
         this.sourceEncoding = sourceEncoding;
@@ -2052,10 +2195,13 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         return sourceEncoding;
     }
 
+    @Override
+    public boolean isSourceEncodingSet() {
+        return true;
+    }
+
     /**
      * Resets the setting to its default, as if it was never set.
-     *
-     * @since 2.3.26
      */
     public void unsetSourceEncoding() {
         if (sourceEncodingExplicitlySet) {
@@ -2547,73 +2693,12 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         
         return super.getCorrectedNameForUnknownSetting(name);
     }
-    
-    @Override
-    protected void doAutoImportsAndIncludes(Environment env) throws TemplateException, IOException {
-        Template t = env.getMainTemplate();
-        doAutoImports(env, t);
-        doAutoIncludes(env, t);
-    }
 
-    private void doAutoImports(Environment env, Template t) throws IOException, TemplateException {
-        Map<String, String> envAutoImports = env.isAutoImportsSet() ? env.getAutoImports() : null;
-        Map<String, String> tAutoImports = t.isAutoImportsSet() ? t.getAutoImports() : null;
-        
-        boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports() : env.getLazyImports();
-        
-        for (Map.Entry<String, String> autoImport : getAutoImports().entrySet()) {
-            String nsVarName = autoImport.getKey();
-            if ((tAutoImports == null || !tAutoImports.containsKey(nsVarName))
-                    && (envAutoImports == null || !envAutoImports.containsKey(nsVarName))) {
-                env.importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
-            }
-        }
-        if (tAutoImports != null) {
-            for (Map.Entry<String, String> autoImport : tAutoImports.entrySet()) {
-                String nsVarName = autoImport.getKey();
-                if (envAutoImports == null || !envAutoImports.containsKey(nsVarName)) {
-                    env.importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
-                }
-            }
-        }
-        if (envAutoImports != null) {
-            for (Map.Entry<String, String> autoImport : envAutoImports.entrySet()) {
-                String nsVarName = autoImport.getKey();
-                env.importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
-            }
-        }
+    @Override
+    protected Object getInheritedCustomAttribute(Object name) {
+        return null;
     }
-    
-    private void doAutoIncludes(Environment env, Template t) throws TemplateException, IOException {
-        // We can't store autoIncludes in LinkedHashSet-s because setAutoIncludes(List) allows duplicates,
-        // unfortunately. Yet we have to prevent duplicates among Configuration levels, with the lowest levels having
-        // priority. So we build some Set-s to do that, but we avoid the most common cases where they aren't needed.
 
-        List<String> tAutoIncludes = t.isAutoIncludesSet() ? t.getAutoIncludes() : null;
-        List<String> envAutoIncludes = env.isAutoIncludesSet() ? env.getAutoIncludes() : null;
-        
-        for (String templateName : getAutoIncludes()) {
-            if ((tAutoIncludes == null || !tAutoIncludes.contains(templateName))
-                    && (envAutoIncludes == null || !envAutoIncludes.contains(templateName))) {
-                env.include(getTemplate(templateName, env.getLocale()));
-            }
-        }
-        
-        if (tAutoIncludes != null) {
-            for (String templateName : tAutoIncludes) {
-                if (envAutoIncludes == null || !envAutoIncludes.contains(templateName)) {
-                    env.include(getTemplate(templateName, env.getLocale()));
-                }
-            }
-        }
-        
-        if (envAutoIncludes != null) {
-            for (String templateName : envAutoIncludes) {
-                env.include(getTemplate(templateName, env.getLocale()));
-            }
-        }
-    }
-    
     /**
      * Returns the FreeMarker version information, most importantly the major.minor.micro version numbers.
      * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Environment.java b/src/main/java/org/apache/freemarker/core/Environment.java
index 6733045..5382404 100644
--- a/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/src/main/java/org/apache/freemarker/core/Environment.java
@@ -42,6 +42,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
@@ -128,6 +129,8 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     private Map<String, TemplateNumberFormat> cachedTemplateNumberFormats;
     private Map<CustomStateKey, Object> customStateMap;
 
+    private TemplateBooleanFormat cachedTemplateBooleanFormat;
+
     /**
      * 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
@@ -321,6 +324,75 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     /**
+     * Executes the auto-imports and auto-includes for the main template of this environment.
+     * This is not meant to be called or overridden by code outside of FreeMarker.
+     */
+    private void doAutoImportsAndIncludes(Environment env) throws TemplateException, IOException {
+        Template t = getMainTemplate();
+        doAutoImports(t);
+        doAutoIncludes(t);
+    }
+
+    private void doAutoImports(Template t) throws IOException, TemplateException {
+        Map<String, String> envAutoImports = isAutoImportsSet() ? getAutoImports() : null;
+        Map<String, String> tAutoImports = t.isAutoImportsSet() ? t.getAutoImports() : null;
+
+        boolean lazyAutoImports = getLazyAutoImports() != null ? getLazyAutoImports() : getLazyImports();
+
+        for (Map.Entry<String, String> autoImport : configuration.getAutoImports().entrySet()) {
+            String nsVarName = autoImport.getKey();
+            if ((tAutoImports == null || !tAutoImports.containsKey(nsVarName))
+                    && (envAutoImports == null || !envAutoImports.containsKey(nsVarName))) {
+                importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
+            }
+        }
+        if (tAutoImports != null) {
+            for (Map.Entry<String, String> autoImport : tAutoImports.entrySet()) {
+                String nsVarName = autoImport.getKey();
+                if (envAutoImports == null || !envAutoImports.containsKey(nsVarName)) {
+                    importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
+                }
+            }
+        }
+        if (envAutoImports != null) {
+            for (Map.Entry<String, String> autoImport : envAutoImports.entrySet()) {
+                String nsVarName = autoImport.getKey();
+                importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
+            }
+        }
+    }
+
+    private void doAutoIncludes(Template t) throws TemplateException, IOException {
+        // We can't store autoIncludes in LinkedHashSet-s because setAutoIncludes(List) allows duplicates,
+        // unfortunately. Yet we have to prevent duplicates among Configuration levels, with the lowest levels having
+        // priority. So we build some Set-s to do that, but we avoid the most common cases where they aren't needed.
+
+        List<String> tAutoIncludes = t.isAutoIncludesSet() ? t.getAutoIncludes() : null;
+        List<String> envAutoIncludes = isAutoIncludesSet() ? getAutoIncludes() : null;
+
+        for (String templateName : configuration.getAutoIncludes()) {
+            if ((tAutoIncludes == null || !tAutoIncludes.contains(templateName))
+                    && (envAutoIncludes == null || !envAutoIncludes.contains(templateName))) {
+                include(configuration.getTemplate(templateName, getLocale()));
+            }
+        }
+
+        if (tAutoIncludes != null) {
+            for (String templateName : tAutoIncludes) {
+                if (envAutoIncludes == null || !envAutoIncludes.contains(templateName)) {
+                    include(configuration.getTemplate(templateName, getLocale()));
+                }
+            }
+        }
+
+        if (envAutoIncludes != null) {
+            for (String templateName : envAutoIncludes) {
+                include(configuration.getTemplate(templateName, getLocale()));
+            }
+        }
+    }
+
+    /**
      * "Visit" the template element.
      */
     void visit(ASTElement element) throws IOException, TemplateException {
@@ -849,6 +921,21 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
+    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
+        return getParent().getTemplateExceptionHandler();
+    }
+
+    @Override
+    protected ArithmeticEngine getInheritedArithmeticEngine() {
+        return getParent().getArithmeticEngine();
+    }
+
+    @Override
+    protected ObjectWrapper getInheritedObjectWrapper() {
+        return getParent().getObjectWrapper();
+    }
+
+    @Override
     public void setLocale(Locale locale) {
         Locale prevLocale = getLocale();
         super.setLocale(locale);
@@ -874,6 +961,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
+    protected Locale getInheritedLocale() {
+        return getParent().getLocale();
+    }
+
+    @Override
     public void setTimeZone(TimeZone timeZone) {
         TimeZone prevTimeZone = getTimeZone();
         super.setTimeZone(timeZone);
@@ -898,6 +990,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
+    protected TimeZone getInheritedTimeZone() {
+        return getParent().getTimeZone();
+    }
+
+    @Override
     public void setSQLDateAndTimeTimeZone(TimeZone timeZone) {
         TimeZone prevTimeZone = getSQLDateAndTimeTimeZone();
         super.setSQLDateAndTimeTimeZone(timeZone);
@@ -921,6 +1018,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         }
     }
 
+    @Override
+    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
+        return getParent().getSQLDateAndTimeTimeZone();
+    }
+
     // Replace with Objects.equals in Java 7
     private static boolean nullSafeEquals(Object o1, Object o2) {
         if (o1 == o2) return true;
@@ -947,6 +1049,61 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         super.setURLEscapingCharset(urlEscapingCharset);
     }
 
+    @Override
+    protected Charset getInheritedURLEscapingCharset() {
+        return getParent().getURLEscapingCharset();
+    }
+
+    @Override
+    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
+        return getParent().getNewBuiltinClassResolver();
+    }
+
+    @Override
+    protected boolean getInheritedAutoFlush() {
+        return getParent().getAutoFlush();
+    }
+
+    @Override
+    protected boolean getInheritedShowErrorTips() {
+        return getParent().getShowErrorTips();
+    }
+
+    @Override
+    protected boolean getInheritedAPIBuiltinEnabled() {
+        return getParent().getAPIBuiltinEnabled();
+    }
+
+    @Override
+    protected boolean getInheritedLogTemplateExceptions() {
+        return getParent().getLogTemplateExceptions();
+    }
+
+    @Override
+    protected boolean getInheritedLazyImports() {
+        return getParent().getLazyImports();
+    }
+
+    @Override
+    protected Boolean getInheritedLazyAutoImports() {
+        return getParent().getLazyAutoImports();
+    }
+
+    @Override
+    protected Map<String, String> getInheritedAutoImports() {
+        return getParent().getAutoImports();
+    }
+
+    @Override
+    protected List<String> getInheritedAutoIncludes() {
+        return getParent().getAutoIncludes();
+    }
+
+    @Override
+    protected Object getInheritedCustomAttribute(Object name) {
+        return getParent().getCustomAttribute(name);
+    }
+
     /*
      * 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
@@ -958,6 +1115,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         super.setOutputEncoding(outputEncoding);
     }
 
+    @Override
+    protected Charset getInheritedOutputEncoding() {
+        return getParent().getOutputEncoding();
+    }
+
     /**
      * 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.
@@ -1056,6 +1218,93 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         cachedTemplateNumberFormat = null;
     }
 
+    @Override
+    protected String getInheritedNumberFormat() {
+        return getParent().getNumberFormat();
+    }
+
+    @Override
+    protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
+        return getParent().getCustomNumberFormats();
+    }
+
+    @Override
+    protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
+        return getParent().getCustomNumberFormat(name);
+    }
+
+    @Override
+    protected boolean getInheritedHasCustomFormats() {
+        return getParent().hasCustomFormats();
+    }
+
+    @Override
+    protected String getInheritedBooleanFormat() {
+        return getParent().getBooleanFormat();
+    }
+
+    String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
+        TemplateBooleanFormat templateBooleanFormat = getTemplateBooleanFormat();
+        if (value) {
+            String s = templateBooleanFormat.getTrueStringValue();
+            if (s == null) {
+                if (fallbackToTrueFalse) {
+                    return MiscUtil.C_TRUE;
+                } else {
+                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
+                }
+            } else {
+                return s;
+            }
+        } else {
+            String s = templateBooleanFormat.getFalseStringValue();
+            if (s == null) {
+                if (fallbackToTrueFalse) {
+                    return MiscUtil.C_FALSE;
+                } else {
+                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
+                }
+            } else {
+                return s;
+            }
+        }
+    }
+
+    TemplateBooleanFormat getTemplateBooleanFormat() {
+        TemplateBooleanFormat format = cachedTemplateBooleanFormat;
+        if (format == null) {
+            format = TemplateBooleanFormat.getInstance(getBooleanFormat());
+            cachedTemplateBooleanFormat = format;
+        }
+        return format;
+    }
+
+    @Override
+    public void setBooleanFormat(String booleanFormat) {
+        String previousFormat = getBooleanFormat();
+        super.setBooleanFormat(booleanFormat);
+        if (!booleanFormat.equals(previousFormat)) {
+            cachedTemplateBooleanFormat = null;
+        }
+    }
+
+    private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
+        return new _ErrorDescriptionBuilder(
+                "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
+                new _DelayedJQuote(getBooleanFormat()),
+                (getBooleanFormat().equals(TemplateBooleanFormat.C_TRUE_FALSE)
+                        ? ", which is the legacy default computer-language format, and hence isn't accepted."
+                        : ".")
+        ).tips(
+                "If you just want \"true\"/\"false\" result as you are generting computer-language output, "
+                        + "use \"?c\", like ${myBool?c}.",
+                "You can write myBool?string('yes', 'no') and like to specify boolean formatting in place.",
+                new Object[] {
+                        "If you need the same two values on most places, the programmers should set the \"",
+                        BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\"." }
+        );
+    }
+
     /**
      * Format number with the default number format.
      * 
@@ -1285,6 +1534,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
+    protected String getInheritedTimeFormat() {
+        return getParent().getTimeFormat();
+    }
+
+    @Override
     public void setDateFormat(String dateFormat) {
         String prevDateFormat = getDateFormat();
         super.setDateFormat(dateFormat);
@@ -1298,6 +1552,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
+    protected String getInheritedDateFormat() {
+        return getParent().getDateFormat();
+    }
+
+    @Override
     public void setDateTimeFormat(String dateTimeFormat) {
         String prevDateTimeFormat = getDateTimeFormat();
         super.setDateTimeFormat(dateTimeFormat);
@@ -1310,6 +1569,21 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         }
     }
 
+    @Override
+    protected String getInheritedDateTimeFormat() {
+        return getParent().getDateTimeFormat();
+    }
+
+    @Override
+    protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
+        return getParent().getCustomDateFormats();
+    }
+
+    @Override
+    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
+        return getParent().getCustomDateFormat(name);
+    }
+
     public Configuration getConfiguration() {
         return configuration;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
index 03cfb95..aaac98d 100644
--- a/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
@@ -30,7 +30,7 @@ import org.apache.freemarker.core.util._NullArgumentException;
 public abstract class MutableProcessingAndParseConfiguration<
         SelfT extends MutableProcessingAndParseConfiguration<SelfT>>
         extends MutableProcessingConfiguration<SelfT>
-        implements ParserConfiguration {
+        implements ParserAndProcessingConfiguration {
 
     private TemplateLanguage templateLanguage;
     private Integer tagSyntax;
@@ -63,10 +63,10 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public int getTagSyntax() {
-        return tagSyntax != null ? tagSyntax : getDefaultTagSyntax();
+        return tagSyntax != null ? tagSyntax : getInheritedTagSyntax();
     }
 
-    protected abstract int getDefaultTagSyntax();
+    protected abstract int getInheritedTagSyntax();
 
     @Override
     public boolean isTagSyntaxSet() {
@@ -78,10 +78,10 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public TemplateLanguage getTemplateLanguage() {
-        return templateLanguage != null ? templateLanguage : getDefaultTemplateLanguage();
+         return isTemplateLanguageSet() ? templateLanguage : getInheritedTemplateLanguage();
     }
 
-    protected abstract TemplateLanguage getDefaultTemplateLanguage();
+    protected abstract TemplateLanguage getInheritedTemplateLanguage();
 
     /**
      * See {@link Configuration#setTemplateLanguage(TemplateLanguage)}
@@ -108,11 +108,11 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public int getNamingConvention() {
-        return namingConvention != null ? namingConvention
-                : getDefaultNamingConvention();
+         return isNamingConventionSet() ? namingConvention
+                : getInheritedNamingConvention();
     }
 
-    protected abstract int getDefaultNamingConvention();
+    protected abstract int getInheritedNamingConvention();
 
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
@@ -134,11 +134,11 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public boolean getWhitespaceStripping() {
-        return whitespaceStripping != null ? whitespaceStripping.booleanValue()
-                : getDefaultWhitespaceStripping();
+         return isWhitespaceStrippingSet() ? whitespaceStripping.booleanValue()
+                : getInheritedWhitespaceStripping();
     }
 
-    protected abstract boolean getDefaultWhitespaceStripping();
+    protected abstract boolean getInheritedWhitespaceStripping();
 
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
@@ -162,11 +162,11 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy.intValue()
-                : getDefaultAutoEscapingPolicy();
+         return isAutoEscapingPolicySet() ? autoEscapingPolicy.intValue()
+                : getInheritedAutoEscapingPolicy();
     }
 
-    protected abstract int getDefaultAutoEscapingPolicy();
+    protected abstract int getInheritedAutoEscapingPolicy();
 
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
@@ -189,10 +189,10 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public OutputFormat getOutputFormat() {
-        return outputFormat != null ? outputFormat : getDefaultOutputFormat();
+         return isOutputFormatSet() ? outputFormat : getInheritedOutputFormat();
     }
 
-    protected abstract OutputFormat getDefaultOutputFormat();
+    protected abstract OutputFormat getInheritedOutputFormat();
 
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
@@ -214,11 +214,11 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public boolean getRecognizeStandardFileExtensions() {
-        return recognizeStandardFileExtensions != null ? recognizeStandardFileExtensions.booleanValue()
-                : getDefaultRecognizeStandardFileExtensions();
+         return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions.booleanValue()
+                : getInheritedRecognizeStandardFileExtensions();
     }
 
-    protected abstract boolean getDefaultRecognizeStandardFileExtensions();
+    protected abstract boolean getInheritedRecognizeStandardFileExtensions();
 
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
@@ -228,11 +228,12 @@ public abstract class MutableProcessingAndParseConfiguration<
         return recognizeStandardFileExtensions != null;
     }
 
+    @Override
     public Charset getSourceEncoding() {
-        return sourceEncoding != null ? sourceEncoding : getDefaultSourceEncoding();
+         return isSourceEncodingSet() ? sourceEncoding : getInheritedSourceEncoding();
     }
 
-    protected abstract Charset getDefaultSourceEncoding();
+    protected abstract Charset getInheritedSourceEncoding();
 
     /**
      * The charset to be used when reading the template "file" that the {@link TemplateLoader} returns as binary
@@ -263,11 +264,10 @@ public abstract class MutableProcessingAndParseConfiguration<
      */
     @Override
     public int getTabSize() {
-        return tabSize != null ? tabSize.intValue()
-                : getDefaultTabSize();
+         return isTabSizeSet() ? tabSize.intValue() : getInheritedTabSize();
     }
 
-    protected abstract int getDefaultTabSize();
+    protected abstract int getInheritedTabSize();
 
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
index a1e756a..305194d 100644
--- a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -19,7 +19,6 @@
 
 package org.apache.freemarker.core;
 
-import java.io.IOException;
 import java.io.Writer;
 import java.nio.charset.Charset;
 import java.text.NumberFormat;
@@ -35,7 +34,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
 import java.util.TimeZone;
@@ -91,8 +89,6 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  */
 public abstract class MutableProcessingConfiguration<SelfT extends MutableProcessingConfiguration<SelfT>>
         implements ProcessingConfiguration {
-    static final String C_TRUE_FALSE = "true,false";
-    
     public static final String NULL_VALUE = "null";
     public static final String DEFAULT_VALUE = "default";
     public static final String JVM_DEFAULT_VALUE = "JVM default";
@@ -321,20 +317,17 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         URL_ESCAPING_CHARSET_KEY_CAMEL_CASE
     };
 
-    private MutableProcessingConfiguration parent;
-    private Map<Object, Object> customAttributes;
-    
+    private ProcessingConfiguration parent;
+
     private Locale locale;
     private String numberFormat;
     private String timeFormat;
     private String dateFormat;
     private String dateTimeFormat;
     private TimeZone timeZone;
-    private TimeZone sqlDataAndTimeTimeZone;
-    private boolean sqlDataAndTimeTimeZoneSet;
+    private TimeZone sqlDateAndTimeTimeZone;
+    private boolean sqlDateAndTimeTimeZoneSet;
     private String booleanFormat;
-    private String trueStringValue;  // deduced from booleanFormat
-    private String falseStringValue;  // deduced from booleanFormat
     private TemplateExceptionHandler templateExceptionHandler;
     private ArithmeticEngine arithmeticEngine;
     private ObjectWrapper objectWrapper;
@@ -347,24 +340,27 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     private Boolean showErrorTips;
     private Boolean apiBuiltinEnabled;
     private Boolean logTemplateExceptions;
-    private Map<String, ? extends TemplateDateFormatFactory> customDateFormats;
-    private Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats;
+    private Map<String, TemplateDateFormatFactory> customDateFormats;
+    private Map<String, TemplateNumberFormatFactory> customNumberFormats;
     private LinkedHashMap<String, String> autoImports;
     private ArrayList<String> autoIncludes;
     private Boolean lazyImports;
     private Boolean lazyAutoImports;
     private boolean lazyAutoImportsSet;
-    
+    private Map<Object, Object> customAttributes;
+
     /**
      * Called by the {@link Configuration} constructor, initializes the fields to their {@link Configuration}-level
      * default without marking them as set.
      */
+    // TODO Move to Configuration(Builder) constructor
     MutableProcessingConfiguration(Version incompatibleImprovements) {
         _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
         parent = null;
         locale = Configuration.getDefaultLocale();
         timeZone = Configuration.getDefaultTimeZone();
-        sqlDataAndTimeTimeZone = null;
+        sqlDateAndTimeTimeZone = null;
+        sqlDateAndTimeTimeZoneSet = true;
         numberFormat = "number";
         timeFormat = "";
         dateFormat = "";
@@ -379,14 +375,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         logTemplateExceptions = Boolean.FALSE;
         // outputEncoding and urlEscapingCharset defaults to null,
         // which means "not specified"
-        setBooleanFormat(C_TRUE_FALSE);
-
+        setBooleanFormat(TemplateBooleanFormat.C_TRUE_FALSE);
         customDateFormats = Collections.emptyMap();
         customNumberFormats = Collections.emptyMap();
-        
+        outputEncodingSet = true;
+        urlEscapingCharsetSet = true;
+
         lazyImports = false;
         lazyAutoImportsSet = true;
-        
         initAutoImportsMap();
         initAutoIncludesList();
     }
@@ -420,7 +416,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @return The parent {@link MutableProcessingConfiguration} object, or {@code null} if this is the root {@link MutableProcessingConfiguration} object
      *         (i.e, if it's the {@link Configuration} object).
      */
-    public final MutableProcessingConfiguration getParent() {
+    public final ProcessingConfiguration getParent() {
         return parent;
     }
     
@@ -429,7 +425,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * template - the included template becomes the parent configurable during
      * its evaluation.
      */
-    void setParent(MutableProcessingConfiguration parent) {
+    void setParent(ProcessingConfiguration parent) {
         this.parent = parent;
     }
     
@@ -454,9 +450,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
 
     @Override
     public Locale getLocale() {
-        return locale != null ? locale : parent.getLocale();
+         return isLocaleSet() ? locale : getInheritedLocale();
     }
 
+    protected abstract Locale getInheritedLocale();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -496,9 +494,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public TimeZone getTimeZone() {
-        return timeZone != null ? timeZone : parent.getTimeZone();
+         return isTimeZoneSet() ? timeZone : getInheritedTimeZone();
     }
-    
+
+    protected abstract TimeZone getInheritedTimeZone();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -571,8 +571,8 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @since 2.3.21
      */
     public void setSQLDateAndTimeTimeZone(TimeZone tz) {
-        sqlDataAndTimeTimeZone = tz;
-        sqlDataAndTimeTimeZoneSet = true;
+        sqlDateAndTimeTimeZone = tz;
+        sqlDateAndTimeTimeZoneSet = true;
     }
 
     /**
@@ -595,11 +595,13 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public TimeZone getSQLDateAndTimeTimeZone() {
-        return sqlDataAndTimeTimeZoneSet
-                ? sqlDataAndTimeTimeZone
-                : (parent != null ? parent.getSQLDateAndTimeTimeZone() : null);
+        return sqlDateAndTimeTimeZoneSet
+                ? sqlDateAndTimeTimeZone
+                : getInheritedSQLDateAndTimeTimeZone();
     }
-    
+
+    protected abstract TimeZone getInheritedSQLDateAndTimeTimeZone();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -607,7 +609,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public boolean isSQLDateAndTimeTimeZoneSet() {
-        return sqlDataAndTimeTimeZoneSet;
+        return sqlDateAndTimeTimeZoneSet;
     }
 
     /**
@@ -653,9 +655,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public String getNumberFormat() {
-        return numberFormat != null ? numberFormat : parent.getNumberFormat();
+         return isNumberFormatSet() ? numberFormat : getInheritedNumberFormat();
     }
 
+    protected abstract String getInheritedNumberFormat();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -682,10 +686,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @since 2.3.24
      */
     @Override
-    public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats() {
-        return customNumberFormats == null ? parent.getCustomNumberFormats() : customNumberFormats;
+    public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
+         return isCustomNumberFormatsSet() ? customNumberFormats : getInheritedCustomNumberFormats();
     }
 
+    protected abstract Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats();
+
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setNumberFormat(String)
      * number_format} setting with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
@@ -700,7 +706,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * 
      * @since 2.3.24
      */
-    public void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats) {
+    public void setCustomNumberFormats(Map<String, TemplateNumberFormatFactory> customNumberFormats) {
         _NullArgumentException.check("customNumberFormats", customNumberFormats);
         validateFormatNames(customNumberFormats.keySet());
         this.customNumberFormats = customNumberFormats;
@@ -709,7 +715,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     /**
      * Fluent API equivalent of {@link #setCustomNumberFormats(Map)}
      */
-    public SelfT customNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> value) {
+    public SelfT customNumberFormats(Map<String, TemplateNumberFormatFactory> value) {
         setCustomNumberFormats(value);
         return self();
     }
@@ -752,6 +758,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * 
      * @since 2.3.24
      */
+    @Override
     public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
         TemplateNumberFormatFactory r;
         if (customNumberFormats != null) {
@@ -760,20 +767,24 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
                 return r;
             }
         }
-        return parent != null ? parent.getCustomNumberFormat(name) : null;
+        return getInheritedCustomNumberFormat(name);
     }
-    
+
+    protected abstract TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name);
+
     /**
      * Tells if this configurable object or its parent defines any custom formats.
      * 
      * @since 2.3.24
      */
     public boolean hasCustomFormats() {
-        return customNumberFormats != null && !customNumberFormats.isEmpty()
-                || customDateFormats != null && !customDateFormats.isEmpty()
-                || getParent() != null && getParent().hasCustomFormats(); 
+        return isCustomNumberFormatsSet() && !customNumberFormats.isEmpty()
+                || isCustomDateFormatsSet() && !customDateFormats.isEmpty()
+                || getInheritedHasCustomFormats();
     }
-    
+
+    protected abstract boolean getInheritedHasCustomFormats();
+
     /**
      * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
      * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
@@ -797,16 +808,6 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         }
         
         this.booleanFormat = booleanFormat; 
-        
-        if (booleanFormat.equals(C_TRUE_FALSE)) {
-            // C_TRUE_FALSE is the default for BC, but it's not a good default for human audience formatting, so we
-            // pretend that it wasn't set.
-            trueStringValue = null; 
-            falseStringValue = null;
-        } else {
-            trueStringValue = booleanFormat.substring(0, commaIdx); 
-            falseStringValue = booleanFormat.substring(commaIdx + 1);
-        }
     }
 
     /**
@@ -822,9 +823,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public String getBooleanFormat() {
-        return booleanFormat != null ? booleanFormat : parent.getBooleanFormat(); 
+         return isBooleanFormatSet() ? booleanFormat : getInheritedBooleanFormat();
     }
-    
+
+    protected abstract String getInheritedBooleanFormat();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -834,75 +837,6 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     public boolean isBooleanFormatSet() {
         return booleanFormat != null;
     }
-        
-    String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
-        if (value) {
-            String s = getTrueStringValue();
-            if (s == null) {
-                if (fallbackToTrueFalse) {
-                    return MiscUtil.C_TRUE;
-                } else {
-                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
-                }
-            } else {
-                return s;
-            }
-        } else {
-            String s = getFalseStringValue();
-            if (s == null) {
-                if (fallbackToTrueFalse) {
-                    return MiscUtil.C_FALSE;
-                } else {
-                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
-                }
-            } else {
-                return s;
-            }
-        }
-    }
-
-    private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
-        return new _ErrorDescriptionBuilder(
-                "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
-                new _DelayedJQuote(getBooleanFormat()), 
-                (getBooleanFormat().equals(C_TRUE_FALSE)
-                    ? ", which is the legacy default computer-language format, and hence isn't accepted."
-                    : ".")
-                ).tips(
-                     "If you just want \"true\"/\"false\" result as you are generting computer-language output, "
-                     + "use \"?c\", like ${myBool?c}.",
-                     "You can write myBool?string('yes', 'no') and like to specify boolean formatting in place.",
-                     new Object[] {
-                         "If you need the same two values on most places, the programmers should set the \"",
-                         BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\"." }
-                 );
-    }
-
-    /**
-     * Returns the string to which {@code true} is converted to for human audience, or {@code null} if automatic
-     * coercion to string is not allowed. The default value is {@code null}.
-     * 
-     * <p>This value is deduced from the {@code "boolean_format"} setting.
-     * Confusingly, for backward compatibility (at least until 2.4) that defaults to {@code "true,false"}, yet this
-     * defaults to {@code null}. That's so because {@code "true,false"} is treated exceptionally, as that default is a
-     * historical mistake in FreeMarker, since it targets computer language output, not human writing. Thus it's
-     * ignored.
-     * 
-     * @since 2.3.20
-     */
-    String getTrueStringValue() {
-        // The first step deliberately tests booleanFormat instead of trueStringValue! 
-        return booleanFormat != null ? trueStringValue : (parent != null ? parent.getTrueStringValue() : null); 
-    }
-
-    /**
-     * Same as {@link #getTrueStringValue()} but with {@code false}. 
-     * @since 2.3.20
-     */
-    String getFalseStringValue() {
-        // The first step deliberately tests booleanFormat instead of falseStringValue! 
-        return booleanFormat != null ? falseStringValue : (parent != null ? parent.getFalseStringValue() : null); 
-    }
 
     /**
      * Sets the format used to convert {@link java.util.Date}-s to string-s that are time (no date part) values,
@@ -930,9 +864,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public String getTimeFormat() {
-        return timeFormat != null ? timeFormat : parent.getTimeFormat();
+         return isTimeFormatSet() ? timeFormat : getInheritedTimeFormat();
     }
 
+    protected abstract String getInheritedTimeFormat();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -969,9 +905,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public String getDateFormat() {
-        return dateFormat != null ? dateFormat : parent.getDateFormat();
+         return isDateFormatSet() ? dateFormat : getInheritedDateFormat();
     }
 
+    protected abstract String getInheritedDateFormat();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1086,9 +1024,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public String getDateTimeFormat() {
-        return dateTimeFormat != null ? dateTimeFormat : parent.getDateTimeFormat();
+         return isDateTimeFormatSet() ? dateTimeFormat : getInheritedDateTimeFormat();
     }
-    
+
+    protected abstract String getInheritedDateTimeFormat();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1115,10 +1055,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @since 2.3.24
      */
     @Override
-    public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats() {
-        return customDateFormats == null ? parent.getCustomDateFormats() : customDateFormats;
+    public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
+         return isCustomDateFormatsSet() ? customDateFormats : getInheritedCustomDateFormats();
     }
 
+    protected abstract Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats();
+
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
      * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
@@ -1134,7 +1076,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * 
      * @since 2.3.24
      */
-    public void setCustomDateFormats(Map<String, ? extends TemplateDateFormatFactory> customDateFormats) {
+    public void setCustomDateFormats(Map<String, TemplateDateFormatFactory> customDateFormats) {
         _NullArgumentException.check("customDateFormats", customDateFormats);
         validateFormatNames(customDateFormats.keySet());
         this.customDateFormats = customDateFormats;
@@ -1143,7 +1085,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     /**
      * Fluent API equivalent of {@link #setCustomDateFormats(Map)}
      */
-    public SelfT customDateFormats(Map<String, ? extends TemplateDateFormatFactory> value) {
+    public SelfT customDateFormats(Map<String, TemplateDateFormatFactory> value) {
         setCustomDateFormats(value);
         return self();
     }
@@ -1163,6 +1105,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * 
      * @since 2.3.24
      */
+    @Override
     public TemplateDateFormatFactory getCustomDateFormat(String name) {
         TemplateDateFormatFactory r;
         if (customDateFormats != null) {
@@ -1171,9 +1114,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
                 return r;
             }
         }
-        return parent != null ? parent.getCustomDateFormat(name) : null;
+        return getInheritedCustomDateFormat(name);
     }
-    
+
+    protected abstract TemplateDateFormatFactory getInheritedCustomDateFormat(String name);
+
     /**
      * Sets the exception handler used to handle exceptions occurring inside templates.
      * The default is {@link TemplateExceptionHandler#DEBUG_HANDLER}. The recommended values are:
@@ -1212,10 +1157,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public TemplateExceptionHandler getTemplateExceptionHandler() {
-        return templateExceptionHandler != null
-                ? templateExceptionHandler : parent.getTemplateExceptionHandler();
+         return isTemplateExceptionHandlerSet()
+                ? templateExceptionHandler : getInheritedTemplateExceptionHandler();
     }
 
+    protected abstract TemplateExceptionHandler getInheritedTemplateExceptionHandler();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1248,10 +1195,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public ArithmeticEngine getArithmeticEngine() {
-        return arithmeticEngine != null
-                ? arithmeticEngine : parent.getArithmeticEngine();
+         return isArithmeticEngineSet()
+                ? arithmeticEngine : getInheritedArithmeticEngine();
     }
 
+    protected abstract ArithmeticEngine getInheritedArithmeticEngine();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1284,10 +1233,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public ObjectWrapper getObjectWrapper() {
-        return objectWrapper != null
-                ? objectWrapper : parent.getObjectWrapper();
+         return isObjectWrapperSet()
+                ? objectWrapper : getInheritedObjectWrapper();
     }
 
+    protected abstract ObjectWrapper getInheritedObjectWrapper();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1321,11 +1272,13 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
 
     @Override
     public Charset getOutputEncoding() {
-        return outputEncodingSet
+        return isOutputEncodingSet()
                 ? outputEncoding
-                : (parent != null ? parent.getOutputEncoding() : null);
+                : getInheritedOutputEncoding();
     }
 
+    protected abstract Charset getInheritedOutputEncoding();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1357,11 +1310,13 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
 
     @Override
     public Charset getURLEscapingCharset() {
-        return urlEscapingCharsetSet
+        return isURLEscapingCharsetSet()
                 ? urlEscapingCharset
-                : (parent != null ? parent.getURLEscapingCharset() : null);
+                : getInheritedURLEscapingCharset();
     }
 
+    protected abstract Charset getInheritedURLEscapingCharset();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1407,10 +1362,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public TemplateClassResolver getNewBuiltinClassResolver() {
-        return newBuiltinClassResolver != null
-                ? newBuiltinClassResolver : parent.getNewBuiltinClassResolver();
+         return isNewBuiltinClassResolverSet()
+                ? newBuiltinClassResolver : getInheritedNewBuiltinClassResolver();
     }
 
+    protected abstract TemplateClassResolver getInheritedNewBuiltinClassResolver();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1456,11 +1413,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public boolean getAutoFlush() {
-        return autoFlush != null 
-            ? autoFlush.booleanValue()
-            : (parent != null ? parent.getAutoFlush() : true);
+         return isAutoFlushSet() ? autoFlush.booleanValue() : getInheritedAutoFlush();
     }
 
+    protected abstract boolean getInheritedAutoFlush();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1496,11 +1453,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public boolean getShowErrorTips() {
-        return showErrorTips != null 
-            ? showErrorTips.booleanValue()
-            : (parent != null ? parent.getShowErrorTips() : true);
+         return isShowErrorTipsSet() ? showErrorTips : getInheritedShowErrorTips();
     }
 
+    protected abstract boolean getInheritedShowErrorTips();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1534,17 +1491,19 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * 
      * @since 2.3.22
      */
-    public boolean isAPIBuiltinEnabled() {
-        return apiBuiltinEnabled != null 
-                ? apiBuiltinEnabled.booleanValue()
-                : (parent != null ? parent.isAPIBuiltinEnabled() : false);
+    @Override
+    public boolean getAPIBuiltinEnabled() {
+         return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getInheritedAPIBuiltinEnabled();
     }
 
+    protected abstract boolean getInheritedAPIBuiltinEnabled();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
      * @since 2.3.24
      */
+    @Override
     public boolean isAPIBuiltinEnabledSet() {
         return apiBuiltinEnabled != null;
     }
@@ -1561,7 +1520,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @since 2.3.22
      */
     public void setLogTemplateExceptions(boolean value) {
-        logTemplateExceptions = Boolean.valueOf(value);
+        logTemplateExceptions = value;
     }
 
     /**
@@ -1571,11 +1530,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public boolean getLogTemplateExceptions() {
-        return logTemplateExceptions != null 
-                ? logTemplateExceptions.booleanValue()
-                : (parent != null ? parent.getLogTemplateExceptions() : true);
+         return isLogTemplateExceptionsSet() ? logTemplateExceptions : getInheritedLogTemplateExceptions();
     }
 
+    protected abstract boolean getInheritedLogTemplateExceptions();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
@@ -1593,9 +1552,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public boolean getLazyImports() {
-        return lazyImports != null ? lazyImports.booleanValue() : parent.getLazyImports();
+         return isLazyImportsSet() ? lazyImports : getInheritedLazyImports();
     }
-    
+
+    protected abstract boolean getInheritedLazyImports();
+
     /**
      * Specifies if {@code <#import ...>} (and {@link Environment#importLib(String, String)}) should delay the loading
      * and processing of the imported templates until the content of the imported namespace is actually accessed. This
@@ -1619,7 +1580,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @since 2.3.25
      */
     public void setLazyImports(boolean lazyImports) {
-        this.lazyImports = Boolean.valueOf(lazyImports);
+        this.lazyImports = lazyImports;
     }
 
     /**
@@ -1639,9 +1600,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public Boolean getLazyAutoImports() {
-        return lazyAutoImportsSet ? lazyAutoImports : parent.getLazyAutoImports();
+        return isLazyAutoImportsSet() ? lazyAutoImports : getInheritedLazyAutoImports();
     }
 
+    protected abstract Boolean getInheritedLazyAutoImports();
+
     /**
      * Specifies if {@linkplain #setAutoImports(Map) auto-imports} will be
      * {@link #setLazyImports(boolean) lazy imports}. This is useful to make the overhead of <em>unused</em>
@@ -1776,9 +1739,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public Map<String, String> getAutoImports() {
-        return autoImports != null ? autoImports : parent.getAutoImports();
+         return isAutoImportsSet() ? autoImports : getInheritedAutoImports();
     }
-    
+
+    protected abstract Map<String,String> getInheritedAutoImports();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      * 
@@ -1870,9 +1835,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     @Override
     public List<String> getAutoIncludes() {
-        return autoIncludes != null ? autoIncludes : parent.getAutoIncludes();
+         return isAutoIncludesSet() ? autoIncludes : getInheritedAutoIncludes();
     }
-    
+
+    protected abstract List<String> getInheritedAutoIncludes();
+
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      * 
@@ -2491,14 +2458,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      */
     protected final UnknownConfigurationSettingException unknownSettingException(String name) {
         Version removalVersion = getRemovalVersionForUnknownSetting(name);
-        return removalVersion != null
+         return removalVersion != null
                 ? new UnknownConfigurationSettingException(name, removalVersion)
                 : new UnknownConfigurationSettingException(name, getCorrectedNameForUnknownSetting(name));
     }
 
     /**
-     * If a setting name is unknown because it was removed over time, then returns the version where it was removed,
-     * otherwise returns {@code null}.
+     * If a setting name is unknown because it was removed over time (not just renamed), then returns the version where
+     * it was removed, otherwise returns {@code null}.
      */
     protected Version getRemovalVersionForUnknownSetting(String name) {
         if (name.equals("classic_compatible") || name.equals("classicCompatible")) {
@@ -2565,31 +2532,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     boolean isCustomAttributeSet(Object key) {
-        return customAttributes != null && customAttributes.containsKey(key);
-    }
-    
-    /**
-     * For internal usage only, copies the custom attributes set directly on this objects into another
-     * {@link MutableProcessingConfiguration}. The target {@link MutableProcessingConfiguration} is assumed to be not seen be other thread than the current
-     * one yet. (That is, the operation is not synchronized on the target {@link MutableProcessingConfiguration}, only on the source
-     * {@link MutableProcessingConfiguration})
-     * 
-     * @since 2.3.24
-     */
-    void copyDirectCustomAttributes(MutableProcessingConfiguration target, boolean overwriteExisting) {
-        if (customAttributes == null) {
-            return;
-        }
-        for (Entry<?, ?> custAttrEnt : customAttributes.entrySet()) {
-            Object custAttrKey = custAttrEnt.getKey();
-            if (overwriteExisting || !target.isCustomAttributeSet(custAttrKey)) {
-                if (custAttrKey instanceof String) {
-                    target.setCustomAttribute((String) custAttrKey, custAttrEnt.getValue());
-                } else {
-                    target.setCustomAttribute(custAttrKey, custAttrEnt.getValue());
-                }
-            }
-        }
+         return isCustomAttributesSet() && customAttributes.containsKey(key);
     }
     
     /**
@@ -2673,19 +2616,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
             r = null;
         }
         if (r == null && parent != null) {
-            return parent.getCustomAttribute(name);
+            return getInheritedCustomAttribute(name);
         }
         return r;
     }
-    
-    /**
-     * Executes the auto-imports and auto-includes for the main template of this environment.
-     * This is not meant to be called or overridden by code outside of FreeMarker. 
-     */
-    protected void doAutoImportsAndIncludes(Environment env)
-    throws TemplateException, IOException {
-        if (parent != null) parent.doAutoImportsAndIncludes(env);
-    }
+
+    protected abstract Object getInheritedCustomAttribute(Object name);
 
     protected final List<String> parseAsList(String text) throws GenericParseException {
         return new SettingStringParser(text).parseAsList();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/ParserAndProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParserAndProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/ParserAndProcessingConfiguration.java
new file mode 100644
index 0000000..9398242
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ParserAndProcessingConfiguration.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * <b>Don't implement this interface yourself</b>; use the existing implementation(s). This interface is the union of
+ * {@link ProcessingConfiguration} and {@link ParserConfiguration}, which is useful for declaring types for values
+ * that must implement both interfaces.
+ */
+public interface ParserAndProcessingConfiguration extends ParserConfiguration, ProcessingConfiguration {
+    // No additional method
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
index e515826..599adbb 100644
--- a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
@@ -31,12 +31,13 @@ import org.apache.freemarker.core.outputformat.OutputFormat;
  * implementation.
  *
  * @see ProcessingConfiguration
- * @since 2.3.24
  */
 public interface ParserConfiguration {
 
     TemplateLanguage getTemplateLanguage();
 
+    boolean isTemplateLanguageSet();
+
     /**
      * See {@link Configuration#getTagSyntax()}.
      */
@@ -147,4 +148,6 @@ public interface ParserConfiguration {
      */
     Charset getSourceEncoding();
 
+    boolean isSourceEncodingSet();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
index 33b028e..b224a01 100644
--- a/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -97,7 +97,9 @@ public interface ProcessingConfiguration {
     /**
      * Getter pair of {@link MutableProcessingConfiguration#setCustomNumberFormats(Map)}.
      */
-    Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats();
+    Map<String, TemplateNumberFormatFactory> getCustomNumberFormats();
+
+    TemplateNumberFormatFactory getCustomNumberFormat(String name);
 
     /**
      * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
@@ -106,6 +108,8 @@ public interface ProcessingConfiguration {
      */
     boolean isCustomNumberFormatsSet();
 
+    boolean hasCustomFormats();
+
     /**
      * Getter pair of {@link MutableProcessingConfiguration#setBooleanFormat(String)}.
      */
@@ -157,7 +161,9 @@ public interface ProcessingConfiguration {
     /**
      * Getter pair of {@link MutableProcessingConfiguration#setCustomDateFormats(Map)}.
      */
-    Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats();
+    Map<String, TemplateDateFormatFactory> getCustomDateFormats();
+
+    TemplateDateFormatFactory getCustomDateFormat(String name);
 
     /**
      * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
@@ -238,6 +244,10 @@ public interface ProcessingConfiguration {
      */
     boolean isNewBuiltinClassResolverSet();
 
+    boolean getAPIBuiltinEnabled();
+
+    boolean isAPIBuiltinEnabledSet();
+
     /**
      * Getter pair of {@link MutableProcessingConfiguration#setAutoFlush(boolean)}.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java
index a82c131..dcde5d9 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -38,9 +38,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TimeZone;
 import java.util.Vector;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.debug._DebuggerService;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateHashModel;
@@ -53,6 +55,8 @@ import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._NullArgumentException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
 /**
  * <p>
@@ -111,6 +115,10 @@ public class Template extends MutableProcessingConfiguration<Template> implement
         this.name = name;
         this.sourceName = sourceName;
         templateLanguageVersion = normalizeTemplateLanguageVersion(cfg.getIncompatibleImprovements());
+        if (customParserConfiguration instanceof TemplateConfiguration.Builder) {
+            throw new IllegalArgumentException("Using TemplateConfiguration.Builder as Template constructor "
+                    + "argument is not allowed; the TemplateConfiguration that it has built is needed instead.");
+        }
         parserConfiguration = customParserConfiguration != null ? customParserConfiguration : getConfiguration();
     }
 
@@ -732,6 +740,146 @@ public class Template extends MutableProcessingConfiguration<Template> implement
         return buf.toString();
     }
 
+    @Override
+    protected Locale getInheritedLocale() {
+        return getParent().getLocale();
+    }
+
+    @Override
+    protected TimeZone getInheritedTimeZone() {
+        return getParent().getTimeZone();
+    }
+
+    @Override
+    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
+        return getParent().getSQLDateAndTimeTimeZone();
+    }
+
+    @Override
+    protected String getInheritedNumberFormat() {
+        return getParent().getNumberFormat();
+    }
+
+    @Override
+    protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
+        return getParent().getCustomNumberFormats();
+    }
+
+    @Override
+    protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
+        return getParent().getCustomNumberFormat(name);
+    }
+
+    @Override
+    protected boolean getInheritedHasCustomFormats() {
+        return getParent().hasCustomFormats();
+    }
+
+    @Override
+    protected String getInheritedBooleanFormat() {
+        return getParent().getBooleanFormat();
+    }
+
+    @Override
+    protected String getInheritedTimeFormat() {
+        return getParent().getTimeFormat();
+    }
+
+    @Override
+    protected String getInheritedDateFormat() {
+        return getParent().getDateFormat();
+    }
+
+    @Override
+    protected String getInheritedDateTimeFormat() {
+        return getParent().getDateTimeFormat();
+    }
+
+    @Override
+    protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
+        return getParent().getCustomDateFormats();
+    }
+
+    @Override
+    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
+        return getParent().getCustomDateFormat(name);
+    }
+
+    @Override
+    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
+        return getParent().getTemplateExceptionHandler();
+    }
+
+    @Override
+    protected ArithmeticEngine getInheritedArithmeticEngine() {
+        return getParent().getArithmeticEngine();
+    }
+
+    @Override
+    protected ObjectWrapper getInheritedObjectWrapper() {
+        return getParent().getObjectWrapper();
+    }
+
+    @Override
+    protected Charset getInheritedOutputEncoding() {
+        return getParent().getOutputEncoding();
+    }
+
+    @Override
+    protected Charset getInheritedURLEscapingCharset() {
+        return getParent().getURLEscapingCharset();
+    }
+
+    @Override
+    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
+        return getParent().getNewBuiltinClassResolver();
+    }
+
+    @Override
+    protected boolean getInheritedAutoFlush() {
+        return getParent().getAutoFlush();
+    }
+
+    @Override
+    protected boolean getInheritedShowErrorTips() {
+        return getParent().getShowErrorTips();
+    }
+
+    @Override
+    protected boolean getInheritedAPIBuiltinEnabled() {
+        return getParent().getAPIBuiltinEnabled();
+    }
+
+    @Override
+    protected boolean getInheritedLogTemplateExceptions() {
+        return getParent().getLogTemplateExceptions();
+    }
+
+    @Override
+    protected boolean getInheritedLazyImports() {
+        return getParent().getLazyImports();
+    }
+
+    @Override
+    protected Boolean getInheritedLazyAutoImports() {
+        return getParent().getLazyAutoImports();
+    }
+
+    @Override
+    protected Map<String, String> getInheritedAutoImports() {
+        return getParent().getAutoImports();
+    }
+
+    @Override
+    protected List<String> getInheritedAutoIncludes() {
+        return getParent().getAutoIncludes();
+    }
+
+    @Override
+    protected Object getInheritedCustomAttribute(Object name) {
+        return getParent().getCustomAttribute(name);
+    }
+
     /**
      * Reader that builds up the line table info for us, and also helps in working around JavaCC's exception
      * suppression.



[2/4] incubator-freemarker git commit: Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder. (This is not the final version of TemplateConfiguration; it will be become cleaner as Template also becomes immutable.)

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/OutputFormatTest.java b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
index 04816f2..224e953 100644
--- a/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
@@ -115,15 +115,15 @@ public class OutputFormatTest extends TemplateTest {
             case 3:
                 cfgOutputFormat = UndefinedOutputFormat.INSTANCE;
                 cfg.unsetOutputFormat();
-                TemplateConfiguration tcXml = new TemplateConfiguration();
-                tcXml.setOutputFormat(XMLOutputFormat.INSTANCE);
+                TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
+                tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
                 cfg.setTemplateConfigurations(
                         new ConditionalTemplateConfigurationFactory(
                                 new OrMatcher(
                                         new FileNameGlobMatcher("*.ftlh"),
                                         new FileNameGlobMatcher("*.FTLH"),
                                         new FileNameGlobMatcher("*.fTlH")),
-                                tcXml));
+                                tcbXML.build()));
                 ftlhOutputFormat = HTMLOutputFormat.INSTANCE; // can't be overidden
                 ftlxOutputFormat = XMLOutputFormat.INSTANCE;
                 break;
@@ -174,15 +174,15 @@ public class OutputFormatTest extends TemplateTest {
         addTemplate("t.ftl",
                 "${'{}'} ${'{}'?esc} ${'{}'?noEsc}");
         
-        TemplateConfiguration tcHTML = new TemplateConfiguration();
-        tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbHTML = new TemplateConfiguration.Builder();
+        tcbHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
         ConditionalTemplateConfigurationFactory tcfHTML = new ConditionalTemplateConfigurationFactory(
-                new FileNameGlobMatcher("t.*"), tcHTML);
+                new FileNameGlobMatcher("t.*"), tcbHTML.build());
 
-        TemplateConfiguration tcNoAutoEsc = new TemplateConfiguration();
-        tcNoAutoEsc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+        TemplateConfiguration.Builder tcbNoAutoEsc = new TemplateConfiguration.Builder();
+        tcbNoAutoEsc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
         ConditionalTemplateConfigurationFactory tcfNoAutoEsc = new ConditionalTemplateConfigurationFactory(
-                new FileNameGlobMatcher("t.*"), tcNoAutoEsc);
+                new FileNameGlobMatcher("t.*"), tcbNoAutoEsc.build());
 
         Configuration cfg = getConfiguration();
         cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
@@ -1007,10 +1007,10 @@ public class OutputFormatTest extends TemplateTest {
     protected Configuration createConfiguration() throws TemplateModelException {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         
-        TemplateConfiguration xmlTC = new TemplateConfiguration();
-        xmlTC.setOutputFormat(XMLOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
+        tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
         cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.xml"), xmlTC));
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.xml"), tcbXML.build()));
 
         cfg.setSharedVariable("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b"));
         cfg.setSharedVariable("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c"));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
index 7cbb0f6..6f6b7f3 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -198,11 +198,12 @@ public class TemplateConfigurationTest {
                 + "Set";
     }
 
-    public static List<PropertyDescriptor> getTemplateConfigurationSettingPropDescs(boolean includeCompilerSettings)
+    public static List<PropertyDescriptor> getTemplateConfigurationSettingPropDescs(
+            Class<? extends ProcessingConfiguration> confClass, boolean includeCompilerSettings)
             throws IntrospectionException {
         List<PropertyDescriptor> settingPropDescs = new ArrayList<>();
 
-        BeanInfo beanInfo = Introspector.getBeanInfo(TemplateConfiguration.class);
+        BeanInfo beanInfo = Introspector.getBeanInfo(confClass);
         for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
             String name = pd.getName();
             if (pd.getWriteMethod() != null && !IGNORED_PROP_NAMES.contains(name)
@@ -281,24 +282,26 @@ public class TemplateConfigurationTest {
 
     @Test
     public void testMergeBasicFunctionality() throws Exception {
-        for (PropertyDescriptor propDesc1 : getTemplateConfigurationSettingPropDescs(true)) {
-            for (PropertyDescriptor propDesc2 : getTemplateConfigurationSettingPropDescs(true)) {
-                TemplateConfiguration tc1 = new TemplateConfiguration();
-                TemplateConfiguration tc2 = new TemplateConfiguration();
+        for (PropertyDescriptor propDesc1 : getTemplateConfigurationSettingPropDescs(
+                TemplateConfiguration.Builder.class, true)) {
+            for (PropertyDescriptor propDesc2 : getTemplateConfigurationSettingPropDescs(
+                    TemplateConfiguration.Builder.class, true)) {
+                TemplateConfiguration.Builder tcb1 = new TemplateConfiguration.Builder();
+                TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
 
                 Object value1 = SETTING_ASSIGNMENTS.get(propDesc1.getName());
-                propDesc1.getWriteMethod().invoke(tc1, value1);
+                propDesc1.getWriteMethod().invoke(tcb1, value1);
                 Object value2 = SETTING_ASSIGNMENTS.get(propDesc2.getName());
-                propDesc2.getWriteMethod().invoke(tc2, value2);
+                propDesc2.getWriteMethod().invoke(tcb2, value2);
 
-                tc1.merge(tc2);
+                tcb1.merge(tcb2);
                 if (propDesc1.getName().equals(propDesc2.getName()) && value1 instanceof List
                         && !propDesc1.getName().equals("autoIncludes")) {
                     assertEquals("For " + propDesc1.getName(),
-                            ListUtils.union((List) value1, (List) value1), propDesc1.getReadMethod().invoke(tc1));
+                            ListUtils.union((List) value1, (List) value1), propDesc1.getReadMethod().invoke(tcb1));
                 } else { // Values of the same setting merged
-                    assertEquals("For " + propDesc1.getName(), value1, propDesc1.getReadMethod().invoke(tc1));
-                    assertEquals("For " + propDesc2.getName(), value2, propDesc2.getReadMethod().invoke(tc1));
+                    assertEquals("For " + propDesc1.getName(), value1, propDesc1.getReadMethod().invoke(tcb1));
+                    assertEquals("For " + propDesc2.getName(), value2, propDesc2.getReadMethod().invoke(tcb1));
                 }
             }
         }
@@ -306,7 +309,7 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testMergeMapSettings() throws Exception {
-        TemplateConfiguration tc1 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
         tc1.setCustomDateFormats(ImmutableMap.of(
                 "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
                 "x", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE));
@@ -315,7 +318,7 @@ public class TemplateConfigurationTest {
                 "x", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE));
         tc1.setAutoImports(ImmutableMap.of("a", "a1.ftl", "b", "b1.ftl"));
         
-        TemplateConfiguration tc2 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
         tc2.setCustomDateFormats(ImmutableMap.of(
                 "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
                 "x", EpochMillisDivTemplateDateFormatFactory.INSTANCE));
@@ -342,12 +345,12 @@ public class TemplateConfigurationTest {
         assertEquals("c2.ftl", mergedAutoImports.get("c"));
         
         // Empty map merging optimization:
-        tc1.merge(new TemplateConfiguration());
+        tc1.merge(new TemplateConfiguration.Builder());
         assertSame(mergedCustomDateFormats, tc1.getCustomDateFormats());
         assertSame(mergedCustomNumberFormats, tc1.getCustomNumberFormats());
         
         // Empty map merging optimization:
-        TemplateConfiguration tc3 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
         tc3.merge(tc1);
         assertSame(mergedCustomDateFormats, tc3.getCustomDateFormats());
         assertSame(mergedCustomNumberFormats, tc3.getCustomNumberFormats());
@@ -355,10 +358,10 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testMergeListSettings() throws Exception {
-        TemplateConfiguration tc1 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
         tc1.setAutoIncludes(ImmutableList.of("a.ftl", "x.ftl", "b.ftl"));
         
-        TemplateConfiguration tc2 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
         tc2.setAutoIncludes(ImmutableList.of("c.ftl", "x.ftl", "d.ftl"));
         
         tc1.merge(tc2);
@@ -368,16 +371,16 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testMergePriority() throws Exception {
-        TemplateConfiguration tc1 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
         tc1.setDateFormat("1");
         tc1.setTimeFormat("1");
         tc1.setDateTimeFormat("1");
 
-        TemplateConfiguration tc2 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
         tc2.setDateFormat("2");
         tc2.setTimeFormat("2");
 
-        TemplateConfiguration tc3 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
         tc3.setDateFormat("3");
 
         tc1.merge(tc2);
@@ -390,7 +393,7 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testMergeCustomAttributes() throws Exception {
-        TemplateConfiguration tc1 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
         tc1.setCustomAttribute("k1", "v1");
         tc1.setCustomAttribute("k2", "v1");
         tc1.setCustomAttribute("k3", "v1");
@@ -398,13 +401,13 @@ public class TemplateConfigurationTest {
         tc1.setCustomAttribute(CA2, "V1");
         tc1.setCustomAttribute(CA3, "V1");
 
-        TemplateConfiguration tc2 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
         tc2.setCustomAttribute("k1", "v2");
         tc2.setCustomAttribute("k2", "v2");
         tc2.setCustomAttribute(CA1, "V2");
         tc2.setCustomAttribute(CA2, "V2");
 
-        TemplateConfiguration tc3 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
         tc3.setCustomAttribute("k1", "v3");
         tc3.setCustomAttribute(CA1, "V3");
 
@@ -421,7 +424,7 @@ public class TemplateConfigurationTest {
 
     @Test
     public void testMergeNullCustomAttributes() throws Exception {
-        TemplateConfiguration tc1 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
         tc1.setCustomAttribute("k1", "v1");
         tc1.setCustomAttribute("k2", "v1");
         tc1.setCustomAttribute(CA1, "V1");
@@ -434,13 +437,13 @@ public class TemplateConfigurationTest {
         assertEquals("V1", tc1.getCustomAttribute(CA2));
         assertNull(tc1.getCustomAttribute(CA3));
 
-        TemplateConfiguration tc2 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
         tc2.setCustomAttribute("k1", "v2");
         tc2.setCustomAttribute("k2", null);
         tc2.setCustomAttribute(CA1, "V2");
         tc2.setCustomAttribute(CA2, null);
 
-        TemplateConfiguration tc3 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
         tc3.setCustomAttribute("k1", null);
         tc2.setCustomAttribute(CA1, null);
 
@@ -454,7 +457,7 @@ public class TemplateConfigurationTest {
         assertNull(tc1.getCustomAttribute(CA2));
         assertNull(tc1.getCustomAttribute(CA3));
 
-        TemplateConfiguration tc4 = new TemplateConfiguration();
+        TemplateConfiguration.Builder tc4 = new TemplateConfiguration.Builder();
         tc4.setCustomAttribute("k1", "v4");
         tc4.setCustomAttribute(CA1, "V4");
 
@@ -474,15 +477,16 @@ public class TemplateConfigurationTest {
         Template t = new Template(null, "", cfg);
         
         {
-            TemplateConfiguration  tc = new TemplateConfiguration();
-            tc.setParentConfiguration(cfg);
-            tc.setBooleanFormat("Y,N");
-            tc.setAutoImports(ImmutableMap.of("a", "a.ftl", "b", "b.ftl", "c", "c.ftl"));
-            tc.setAutoIncludes(ImmutableList.of("i1.ftl", "i2.ftl", "i3.ftl"));
-            tc.setCustomNumberFormats(ImmutableMap.of(
+            TemplateConfiguration.Builder  tcb = new TemplateConfiguration.Builder();
+            tcb.setBooleanFormat("Y,N");
+            tcb.setAutoImports(ImmutableMap.of("a", "a.ftl", "b", "b.ftl", "c", "c.ftl"));
+            tcb.setAutoIncludes(ImmutableList.of("i1.ftl", "i2.ftl", "i3.ftl"));
+            tcb.setCustomNumberFormats(ImmutableMap.of(
                     "a", HexTemplateNumberFormatFactory.INSTANCE,
                     "b", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE));
-            
+
+            TemplateConfiguration tc = tcb.build();
+            tc.setParentConfiguration(cfg);
             tc.apply(t);
         }
         assertEquals("Y,N", t.getBooleanFormat());
@@ -491,15 +495,15 @@ public class TemplateConfigurationTest {
         assertEquals(ImmutableList.of("i1.ftl", "i2.ftl", "i3.ftl"), t.getAutoIncludes());
         
         {
-            TemplateConfiguration  tc = new TemplateConfiguration();
-            tc.setParentConfiguration(cfg);
-            tc.setBooleanFormat("J,N");
-            tc.setAutoImports(ImmutableMap.of("b", "b2.ftl", "d", "d.ftl"));
-            tc.setAutoIncludes(ImmutableList.of("i2.ftl", "i4.ftl"));
-            tc.setCustomNumberFormats(ImmutableMap.of(
+            TemplateConfiguration.Builder  tcb = new TemplateConfiguration.Builder();
+            tcb.setBooleanFormat("J,N");
+            tcb.setAutoImports(ImmutableMap.of("b", "b2.ftl", "d", "d.ftl"));
+            tcb.setAutoIncludes(ImmutableList.of("i2.ftl", "i4.ftl"));
+            tcb.setCustomNumberFormats(ImmutableMap.of(
                     "b", BaseNTemplateNumberFormatFactory.INSTANCE,
                     "c", BaseNTemplateNumberFormatFactory.INSTANCE));
-            
+            TemplateConfiguration tc = tcb.build();
+            tc.setParentConfiguration(cfg);
             tc.apply(t);
         }
         assertEquals("Y,N", t.getBooleanFormat());
@@ -515,17 +519,19 @@ public class TemplateConfigurationTest {
 
     @Test
     public void testConfigureNonParserConfig() throws Exception {
-        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(false)) {
-            TemplateConfiguration tc = new TemplateConfiguration();
-            tc.setParentConfiguration(DEFAULT_CFG);
-    
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(
+                TemplateConfiguration.Builder.class, false)) {
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+
             Object newValue = SETTING_ASSIGNMENTS.get(pd.getName());
-            pd.getWriteMethod().invoke(tc, newValue);
+            pd.getWriteMethod().invoke(tcb, newValue);
             
             Template t = new Template(null, "", DEFAULT_CFG);
             Method tReaderMethod = t.getClass().getMethod(pd.getReadMethod().getName());
             
             assertNotEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+            TemplateConfiguration tc = tcb.build();
+            tc.setParentConfiguration(DEFAULT_CFG);
             tc.apply(t);
             assertEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
         }
@@ -538,15 +544,15 @@ public class TemplateConfigurationTest {
         cfg.setCustomAttribute("k2", "c");
         cfg.setCustomAttribute("k3", "c");
 
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setCustomAttribute("k2", "tc");
-        tc.setCustomAttribute("k3", null);
-        tc.setCustomAttribute("k4", "tc");
-        tc.setCustomAttribute("k5", "tc");
-        tc.setCustomAttribute("k6", "tc");
-        tc.setCustomAttribute(CA1, "tc");
-        tc.setCustomAttribute(CA2,"tc");
-        tc.setCustomAttribute(CA3,"tc");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomAttribute("k2", "tc");
+        tcb.setCustomAttribute("k3", null);
+        tcb.setCustomAttribute("k4", "tc");
+        tcb.setCustomAttribute("k5", "tc");
+        tcb.setCustomAttribute("k6", "tc");
+        tcb.setCustomAttribute(CA1, "tc");
+        tcb.setCustomAttribute(CA2,"tc");
+        tcb.setCustomAttribute(CA3,"tc");
 
         Template t = new Template(null, "", cfg);
         t.setCustomAttribute("k5", "t");
@@ -555,7 +561,8 @@ public class TemplateConfigurationTest {
         t.setCustomAttribute(CA2, "t");
         t.setCustomAttribute(CA3, null);
         t.setCustomAttribute(CA4, "t");
-        
+
+        TemplateConfiguration tc = tcb.build();
         tc.setParentConfiguration(cfg);
         tc.apply(t);
         
@@ -577,41 +584,46 @@ public class TemplateConfigurationTest {
         Set<String> testedProps = new HashSet<>();
         
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
             assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y");
             testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE);
         }
         
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
             assertOutputWithoutAndWithTC(tc, "<#if true>y<#elseif false>n</#if>", "y", null);
             testedProps.add(Configuration.NAMING_CONVENTION_KEY_CAMEL_CASE);
         }
         
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setWhitespaceStripping(false);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setWhitespaceStripping(false);
             assertOutputWithoutAndWithTC(tc, "<#if true>\nx\n</#if>\n", "x\n", "\nx\n\n");
             testedProps.add(Configuration.WHITESPACE_STRIPPING_KEY_CAMEL_CASE);
         }
 
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setArithmeticEngine(new DummyArithmeticEngine());
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setArithmeticEngine(new DummyArithmeticEngine());
             assertOutputWithoutAndWithTC(tc, "${1} ${1+1}", "1 2", "11 22");
             testedProps.add(Configuration.ARITHMETIC_ENGINE_KEY_CAMEL_CASE);
         }
 
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setOutputFormat(XMLOutputFormat.INSTANCE);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setOutputFormat(XMLOutputFormat.INSTANCE);
             assertOutputWithoutAndWithTC(tc, "${.outputFormat} ${\"a'b\"}",
                     UndefinedOutputFormat.INSTANCE.getName() + " a'b",
                     XMLOutputFormat.INSTANCE.getName() + " a&apos;b");
@@ -619,17 +631,19 @@ public class TemplateConfigurationTest {
         }
 
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setOutputFormat(XMLOutputFormat.INSTANCE);
+            tcb.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setOutputFormat(XMLOutputFormat.INSTANCE);
-            tc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
             assertOutputWithoutAndWithTC(tc, "${'a&b'}", "a&b", "a&b");
             testedProps.add(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE);
         }
         
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             /* Can't test this now, as the only valid value is 3.0.0. [FM3.0.1]
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(new Configuration(new Version(2, 3, 0)));
             assertOutputWithoutAndWithTC(tc, "<#foo>", null, "<#foo>");
             */
@@ -637,19 +651,21 @@ public class TemplateConfigurationTest {
         }
 
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setRecognizeStandardFileExtensions(false);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setRecognizeStandardFileExtensions(false);
             assertOutputWithoutAndWithTC(tc, "adhoc.ftlh", "${.outputFormat}",
                     HTMLOutputFormat.INSTANCE.getName(), UndefinedOutputFormat.INSTANCE.getName());
             testedProps.add(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE);
         }
 
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
-            tc.setLogTemplateExceptions(false);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setLogTemplateExceptions(false);
+            tcb.setTabSize(3);
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setTabSize(3);
             assertOutputWithoutAndWithTC(tc,
                     "<#attempt><@'\\t$\\{1+}'?interpret/><#recover>"
                     + "${.error?replace('(?s).*?column ([0-9]+).*', '$1', 'r')}"
@@ -662,12 +678,12 @@ public class TemplateConfigurationTest {
             // As the TemplateLanguage-based parser selection happens in the TemplateResolver, we can't use
             // assertOutput here, as that hard-coded to create an FTL Template.
 
-            TemplateConfiguration tc = new TemplateConfiguration();
-            tc.setTemplateLanguage(TemplateLanguage.STATIC_TEXT);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setTemplateLanguage(TemplateLanguage.STATIC_TEXT);
 
             Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
             cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileExtensionMatcher
-                    ("txt"), tc));
+                    ("txt"), tcb.build()));
 
             StringTemplateLoader templateLoader = new StringTemplateLoader();
             templateLoader.putTemplate("adhoc.ftl", "${1+1}");
@@ -692,13 +708,13 @@ public class TemplateConfigurationTest {
             // As the TemplateLanguage-based parser selection happens in the TemplateResolver, we can't use
             // assertOutput here, as that hard-coded to create an FTL Template.
 
-            TemplateConfiguration tc = new TemplateConfiguration();
-            tc.setSourceEncoding(StandardCharsets.ISO_8859_1);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setSourceEncoding(StandardCharsets.ISO_8859_1);
 
             Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
             cfg.setSourceEncoding(StandardCharsets.UTF_8);
             cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher
-                    ("latin1.ftl"), tc));
+                    ("latin1.ftl"), tcb.build()));
 
             MonitoredTemplateLoader templateLoader = new MonitoredTemplateLoader();
             templateLoader.putBinaryTemplate("utf8.ftl", "pr�ba", StandardCharsets.UTF_8, 1);
@@ -728,9 +744,10 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testArithmeticEngine() throws TemplateException, IOException {
-        TemplateConfiguration tc = new TemplateConfiguration();
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setArithmeticEngine(new DummyArithmeticEngine());
+        TemplateConfiguration tc = tcb.build();
         tc.setParentConfiguration(DEFAULT_CFG);
-        tc.setArithmeticEngine(new DummyArithmeticEngine());
         assertOutputWithoutAndWithTC(tc,
                 "<#setting locale='en_US'>${1} ${1+1} ${1*3} <#assign x = 1>${x + x} ${x * 3}",
                 "1 2 3 2 3", "11 22 33 22 33");
@@ -742,25 +759,28 @@ public class TemplateConfigurationTest {
 
     @Test
     public void testAutoImport() throws TemplateException, IOException {
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setAutoImports(ImmutableMap.of("t1", "t1.ftl", "t2", "t2.ftl"));
-        tc.setParent(DEFAULT_CFG);
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setAutoImports(ImmutableMap.of("t1", "t1.ftl", "t2", "t2.ftl"));
+        TemplateConfiguration tc = tcb.build();
+        tc.setParentConfiguration(DEFAULT_CFG);
         assertOutputWithoutAndWithTC(tc, "<#import 't3.ftl' as t3>${loaded}", "t3;", "t1;t2;t3;");
     }
 
     @Test
     public void testAutoIncludes() throws TemplateException, IOException {
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setAutoIncludes(ImmutableList.of("t1.ftl", "t2.ftl"));
-        tc.setParent(DEFAULT_CFG);
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setAutoIncludes(ImmutableList.of("t1.ftl", "t2.ftl"));
+        TemplateConfiguration tc = tcb.build();
+        tc.setParentConfiguration(DEFAULT_CFG);
         assertOutputWithoutAndWithTC(tc, "<#include 't3.ftl'>", "In t3;", "In t1;In t2;In t3;");
     }
     
     @Test
     public void testStringInterpolate() throws TemplateException, IOException {
-        TemplateConfiguration tc = new TemplateConfiguration();
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setArithmeticEngine(new DummyArithmeticEngine());
+        TemplateConfiguration tc = tcb.build();
         tc.setParentConfiguration(DEFAULT_CFG);
-        tc.setArithmeticEngine(new DummyArithmeticEngine());
         assertOutputWithoutAndWithTC(tc,
                 "<#setting locale='en_US'>${'${1} ${1+1} ${1*3}'} <#assign x = 1>${'${x + x} ${x * 3}'}",
                 "1 2 3 2 3", "11 22 33 22 33");
@@ -772,25 +792,32 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testInterpret() throws TemplateException, IOException {
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setParentConfiguration(DEFAULT_CFG);
-        tc.setArithmeticEngine(new DummyArithmeticEngine());
-        assertOutputWithoutAndWithTC(tc,
-                "<#setting locale='en_US'><#assign src = r'${1} <#assign x = 1>${x + x}'><@src?interpret />",
-                "1 2", "11 22");
-        
-        tc.setWhitespaceStripping(false);
-        assertOutputWithoutAndWithTC(tc,
-                "<#if true>\nX</#if><#assign src = r'<#if true>\nY</#if>'><@src?interpret />",
-                "XY", "\nX\nY");
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setArithmeticEngine(new DummyArithmeticEngine());
+        {
+            TemplateConfiguration tc = tcb.build();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            assertOutputWithoutAndWithTC(tc,
+                    "<#setting locale='en_US'><#assign src = r'${1} <#assign x = 1>${x + x}'><@src?interpret />",
+                    "1 2", "11 22");
+        }
+        tcb.setWhitespaceStripping(false);
+        {
+            TemplateConfiguration tc = tcb.build();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            assertOutputWithoutAndWithTC(tc,
+                    "<#if true>\nX</#if><#assign src = r'<#if true>\nY</#if>'><@src?interpret />",
+                    "XY", "\nX\nY");
+        }
     }
 
     @Test
     public void testEval() throws TemplateException, IOException {
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setArithmeticEngine(new DummyArithmeticEngine());
+            TemplateConfiguration tc = tcb.build();
             tc.setParentConfiguration(DEFAULT_CFG);
-            tc.setArithmeticEngine(new DummyArithmeticEngine());
             assertOutputWithoutAndWithTC(tc,
                     "<#assign x = 1>${r'1 + x'?eval?c}",
                     "2", "22");
@@ -800,34 +827,51 @@ public class TemplateConfigurationTest {
         }
         
         {
-            TemplateConfiguration tc = new TemplateConfiguration();
-            tc.setParentConfiguration(DEFAULT_CFG);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             Charset outputEncoding = ISO_8859_2;
-            tc.setOutputEncoding(outputEncoding);
+            tcb.setOutputEncoding(outputEncoding);
 
             String legacyNCFtl = "${r'.output_encoding!\"null\"'?eval}";
             String camelCaseNCFtl = "${r'.outputEncoding!\"null\"'?eval}";
 
-            // Default is re-auto-detecting in ?eval:
-            assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name());
-            assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name());
-            
-            // Force camelCase:
-            tc.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
-            assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", null);
-            assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name());
-            
-            // Force legacy:
-            tc.setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
-            assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name());
-            assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", null);
+            {
+                TemplateConfiguration tc = tcb.build();
+                tc.setParentConfiguration(DEFAULT_CFG);
+
+                // Default is re-auto-detecting in ?eval:
+                assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name());
+                assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name());
+            }
+
+            {
+                // Force camelCase:
+                tcb.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+                TemplateConfiguration tc = tcb.build();
+                tc.setParentConfiguration(DEFAULT_CFG);
+
+                assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", null);
+                assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name());
+            }
+
+            {
+                // Force legacy:
+                tcb.setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+
+                TemplateConfiguration tc = tcb.build();
+                tc.setParentConfiguration(DEFAULT_CFG);
+
+                assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name());
+                assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", null);
+            }
         }
     }
     
     @Test
     public void testSetParentConfiguration() throws IOException {
-        TemplateConfiguration tc = new TemplateConfiguration();
-        
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        TemplateConfiguration tc = tcb.build();
+
         Template t = new Template(null, "", DEFAULT_CFG);
         try {
             tc.apply(t);
@@ -836,7 +880,7 @@ public class TemplateConfigurationTest {
             assertThat(e.getMessage(), containsString("Configuration"));
         }
         
-        tc.setParent(DEFAULT_CFG);
+        tc.setParentConfiguration(DEFAULT_CFG);
         
         try {
             tc.setParentConfiguration(new Configuration());
@@ -846,21 +890,13 @@ public class TemplateConfigurationTest {
         }
 
         try {
-            // Same as setParentConfiguration
-            tc.setParent(new Configuration());
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), containsString("Configuration"));
-        }
-        
-        try {
             tc.setParentConfiguration(null);
             fail();
         } catch (_NullArgumentException e) {
-            // exected
+            // expected
         }
         
-        tc.setParent(DEFAULT_CFG);
+        tc.setParentConfiguration(DEFAULT_CFG);
         
         tc.apply(t);
     }
@@ -910,22 +946,23 @@ public class TemplateConfigurationTest {
 
     @Test
     public void testIsSet() throws Exception {
-        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true)) {
-            TemplateConfiguration tc = new TemplateConfiguration();
-            checkAllIsSetFalseExcept(tc, null);
-            pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(pd.getName()));
-            checkAllIsSetFalseExcept(tc, pd.getName());
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(
+                TemplateConfiguration.Builder.class, true)) {
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            checkAllIsSetFalseExcept(tcb.build(), null);
+            pd.getWriteMethod().invoke(tcb, SETTING_ASSIGNMENTS.get(pd.getName()));
+            checkAllIsSetFalseExcept(tcb.build(), pd.getName());
         }
     }
 
     private void checkAllIsSetFalseExcept(TemplateConfiguration tc, String setSetting)
             throws SecurityException, IntrospectionException,
             IllegalArgumentException, IllegalAccessException, InvocationTargetException {
-        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true)) {
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(TemplateConfiguration.class, true)) {
             String isSetMethodName = getIsSetMethodName(pd.getReadMethod().getName());
             Method isSetMethod;
             try {
-                isSetMethod = TemplateConfiguration.class.getMethod(isSetMethodName);
+                isSetMethod = tc.getClass().getMethod(isSetMethodName);
             } catch (NoSuchMethodException e) {
                 fail("Missing " + isSetMethodName + " method for \"" + pd.getName() + "\".");
                 return;
@@ -943,26 +980,23 @@ public class TemplateConfigurationTest {
      */
     @Test
     public void checkTestAssignments() throws Exception {
-        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true)) {
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(
+                TemplateConfiguration.Builder.class, true)) {
             String propName = pd.getName();
             if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
                 fail("Test case doesn't cover all settings in SETTING_ASSIGNMENTS. Missing: " + propName);
             }
             Method readMethod = pd.getReadMethod();
             String cfgMethodName = readMethod.getName();
-            if (cfgMethodName.equals("getSourceEncoding")) {
-                // Because Configuration has local-to-encoding map too, this has a different name there.
-                cfgMethodName = "getSourceEncoding";
-            }
             Method cfgMethod = DEFAULT_CFG.getClass().getMethod(cfgMethodName, readMethod.getParameterTypes());
             Object defaultSettingValue = cfgMethod.invoke(DEFAULT_CFG);
             Object assignedValue = SETTING_ASSIGNMENTS.get(propName);
             assertNotEquals("SETTING_ASSIGNMENTS must contain a non-default value for " + propName,
                     assignedValue, defaultSettingValue);
 
-            TemplateConfiguration tc = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             try {
-                pd.getWriteMethod().invoke(tc, assignedValue);
+                pd.getWriteMethod().invoke(tcb, assignedValue);
             } catch (Exception e) {
                 throw new IllegalStateException("For setting \"" + propName + "\" and assigned value of type "
                         + (assignedValue != null ? assignedValue.getClass().getName() : "Null"),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
deleted file mode 100644
index f26ce63..0000000
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
+++ /dev/null
@@ -1,264 +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;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Locale;
-
-import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
-import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
-import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
-import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
-import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
-import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
-import org.junit.Test;
-
-public class TemplateConfigurationWithDefaltTemplateResolverTest {
-
-    private static final String TEXT_WITH_ACCENTS = "pr\u00F3ba";
-
-    private static final Object CUST_ATT_1 = new Object();
-    private static final Object CUST_ATT_2 = new Object();
-
-    private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2");
-
-    @Test
-    public void testEncoding() throws Exception {
-        Configuration cfg = createCommonEncodingTesterConfig();
-        
-        {
-            Template t = cfg.getTemplate("utf8.ftl");
-            assertEquals(StandardCharsets.UTF_8, t.getSourceEncoding());
-            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("utf16.ftl");
-            assertEquals(StandardCharsets.UTF_16LE, t.getSourceEncoding());
-            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("default.ftl");
-            assertEquals(StandardCharsets.ISO_8859_1, t.getSourceEncoding());
-            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("utf8-latin2.ftl");
-            assertEquals(ISO_8859_2, t.getSourceEncoding());
-            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("default-latin2.ftl");
-            assertEquals(ISO_8859_2, t.getSourceEncoding());
-            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
-        }
-    }
-    
-    @Test
-    public void testIncludeAndEncoding() throws Exception {
-        Configuration cfg = createCommonEncodingTesterConfig();
-        ByteArrayTemplateLoader tl = (ByteArrayTemplateLoader) cfg.getTemplateLoader();
-        tl.putTemplate("main.ftl", (
-                        "<#include 'utf8.ftl'>"
-                        + "<#include 'utf16.ftl'>"
-                        + "<#include 'default.ftl'>"
-                        + "<#include 'utf8-latin2.ftl'>"
-                ).getBytes(StandardCharsets.ISO_8859_1));
-        assertEquals(
-                TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS,
-                getTemplateOutput(cfg.getTemplate("main.ftl")));
-    }
-
-    @Test
-    public void testLocale() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("(de).ftl", "${.locale}");
-        tl.putTemplate("default.ftl", "${.locale}");
-        tl.putTemplate("(de)-fr.ftl",
-                ("<#ftl locale='fr_FR'>${.locale}"));
-        tl.putTemplate("default-fr.ftl",
-                ("<#ftl locale='fr_FR'>${.locale}"));
-        cfg.setTemplateLoader(tl);
-
-        TemplateConfiguration tcDe = new TemplateConfiguration();
-        tcDe.setLocale(Locale.GERMANY);
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(de)*"), tcDe));
-        
-        {
-            Template t = cfg.getTemplate("(de).ftl");
-            assertEquals(Locale.GERMANY, t.getLocale());
-            assertEquals("de_DE", getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("(de).ftl", Locale.ITALY);
-            assertEquals(Locale.GERMANY, t.getLocale());
-            assertEquals("de_DE", getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("default.ftl");
-            assertEquals(Locale.US, t.getLocale());
-            assertEquals("en_US", getTemplateOutput(t));
-        }
-        {
-            Template t = cfg.getTemplate("default.ftl", Locale.ITALY);
-            assertEquals(Locale.ITALY, t.getLocale());
-            assertEquals("it_IT", getTemplateOutput(t));
-        }
-    }
-
-    @Test
-    public void testConfigurableSettings() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        
-        TemplateConfiguration tcFR = new TemplateConfiguration();
-        tcFR.setLocale(Locale.FRANCE);
-        TemplateConfiguration tcYN = new TemplateConfiguration();
-        tcYN.setBooleanFormat("Y,N");
-        TemplateConfiguration tc00 = new TemplateConfiguration();
-        tc00.setNumberFormat("0.00");
-        cfg.setTemplateConfigurations(
-                new MergingTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(fr)*"), tcFR),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(yn)*"), tcYN),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(00)*"), tc00)
-                )
-        );
-        
-        String commonFTL = "${.locale} ${true?string} ${1.2}";
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("default", commonFTL);
-        tl.putTemplate("(fr)", commonFTL);
-        tl.putTemplate("(yn)(00)", commonFTL);
-        tl.putTemplate("(00)(fr)", commonFTL);
-        cfg.setTemplateLoader(tl);
-        
-        assertEquals("en_US true 1.2", getTemplateOutput(cfg.getTemplate("default")));
-        assertEquals("fr_FR true 1,2", getTemplateOutput(cfg.getTemplate("(fr)")));
-        assertEquals("en_US Y 1.20", getTemplateOutput(cfg.getTemplate("(yn)(00)")));
-        assertEquals("fr_FR true 1,20", getTemplateOutput(cfg.getTemplate("(00)(fr)")));
-    }
-    
-    @Test
-    public void testCustomAttributes() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
-        TemplateConfiguration tc1 = new TemplateConfiguration();
-        tc1.setCustomAttribute("a1", "a1tc1");
-        tc1.setCustomAttribute("a2", "a2tc1");
-        tc1.setCustomAttribute("a3", "a3tc1");
-        tc1.setCustomAttribute(CUST_ATT_1, "ca1tc1");
-        tc1.setCustomAttribute(CUST_ATT_2, "ca2tc1");
-        
-        TemplateConfiguration tc2 = new TemplateConfiguration();
-        tc2.setCustomAttribute("a1", "a1tc2");
-        tc2.setCustomAttribute(CUST_ATT_1, "ca1tc2");
-        
-        cfg.setTemplateConfigurations(
-                new MergingTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc1)*"), tc1),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc2)*"), tc2)
-                )
-        );
-        
-        String commonFTL = "<#ftl attributes={ 'a3': 'a3temp' }>";
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("(tc1)", commonFTL);
-        tl.putTemplate("(tc1)noHeader", "");
-        tl.putTemplate("(tc2)", commonFTL);
-        tl.putTemplate("(tc1)(tc2)", commonFTL);
-        cfg.setTemplateLoader(tl);
-
-        {
-            Template t = cfg.getTemplate("(tc1)");
-            assertEquals("a1tc1", t.getCustomAttribute("a1"));
-            assertEquals("a2tc1", t.getCustomAttribute("a2"));
-            assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
-        }
-        {
-            Template t = cfg.getTemplate("(tc1)noHeader");
-            assertEquals("a1tc1", t.getCustomAttribute("a1"));
-            assertEquals("a2tc1", t.getCustomAttribute("a2"));
-            assertEquals("a3tc1", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
-        }
-        {
-            Template t = cfg.getTemplate("(tc2)");
-            assertEquals("a1tc2", t.getCustomAttribute("a1"));
-            assertNull(t.getCustomAttribute("a2"));
-            assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
-            assertNull(t.getCustomAttribute(CUST_ATT_2));
-        }
-        {
-            Template t = cfg.getTemplate("(tc1)(tc2)");
-            assertEquals("a1tc2", t.getCustomAttribute("a1"));
-            assertEquals("a2tc1", t.getCustomAttribute("a2"));
-            assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
-        }
-    }
-    
-    private String getTemplateOutput(Template t) throws TemplateException, IOException {
-        StringWriter sw = new StringWriter();
-        t.process(null, sw);
-        return sw.toString();
-    }
-
-    private Configuration createCommonEncodingTesterConfig() throws UnsupportedEncodingException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
-        cfg.setLocale(Locale.US);
-        
-        ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
-        tl.putTemplate("utf8.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_8));
-        tl.putTemplate("utf16.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_16LE));
-        tl.putTemplate("default.ftl", TEXT_WITH_ACCENTS.getBytes(ISO_8859_2));
-        tl.putTemplate("utf8-latin2.ftl",
-                ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2));
-        tl.putTemplate("default-latin2.ftl",
-                ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2));
-        cfg.setTemplateLoader(tl);
-        
-        TemplateConfiguration tcUtf8 = new TemplateConfiguration();
-        tcUtf8.setSourceEncoding(StandardCharsets.UTF_8);
-        TemplateConfiguration tcUtf16 = new TemplateConfiguration();
-        tcUtf16.setSourceEncoding(StandardCharsets.UTF_16LE);
-        cfg.setTemplateConfigurations(
-                new FirstMatchTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf8*"), tcUtf8),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf16*"), tcUtf16)
-                ).allowNoMatch(true));
-        return cfg;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
new file mode 100644
index 0000000..b154ebb
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.junit.Test;
+
+public class TemplateConfigurationWithDefaultTemplateResolverTest {
+
+    private static final String TEXT_WITH_ACCENTS = "pr\u00F3ba";
+
+    private static final Object CUST_ATT_1 = new Object();
+    private static final Object CUST_ATT_2 = new Object();
+
+    private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2");
+
+    @Test
+    public void testEncoding() throws Exception {
+        Configuration cfg = createCommonEncodingTesterConfig();
+        
+        {
+            Template t = cfg.getTemplate("utf8.ftl");
+            assertEquals(StandardCharsets.UTF_8, t.getSourceEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("utf16.ftl");
+            assertEquals(StandardCharsets.UTF_16LE, t.getSourceEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl");
+            assertEquals(StandardCharsets.ISO_8859_1, t.getSourceEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("utf8-latin2.ftl");
+            assertEquals(ISO_8859_2, t.getSourceEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default-latin2.ftl");
+            assertEquals(ISO_8859_2, t.getSourceEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+    }
+    
+    @Test
+    public void testIncludeAndEncoding() throws Exception {
+        Configuration cfg = createCommonEncodingTesterConfig();
+        ByteArrayTemplateLoader tl = (ByteArrayTemplateLoader) cfg.getTemplateLoader();
+        tl.putTemplate("main.ftl", (
+                        "<#include 'utf8.ftl'>"
+                        + "<#include 'utf16.ftl'>"
+                        + "<#include 'default.ftl'>"
+                        + "<#include 'utf8-latin2.ftl'>"
+                ).getBytes(StandardCharsets.ISO_8859_1));
+        assertEquals(
+                TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS,
+                getTemplateOutput(cfg.getTemplate("main.ftl")));
+    }
+
+    @Test
+    public void testLocale() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        cfg.setLocale(Locale.US);
+        
+        StringTemplateLoader tl = new StringTemplateLoader();
+        tl.putTemplate("(de).ftl", "${.locale}");
+        tl.putTemplate("default.ftl", "${.locale}");
+        tl.putTemplate("(de)-fr.ftl",
+                ("<#ftl locale='fr_FR'>${.locale}"));
+        tl.putTemplate("default-fr.ftl",
+                ("<#ftl locale='fr_FR'>${.locale}"));
+        cfg.setTemplateLoader(tl);
+
+        TemplateConfiguration.Builder tcDe = new TemplateConfiguration.Builder();
+        tcDe.setLocale(Locale.GERMANY);
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(de)*"), tcDe.build()));
+        
+        {
+            Template t = cfg.getTemplate("(de).ftl");
+            assertEquals(Locale.GERMANY, t.getLocale());
+            assertEquals("de_DE", getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("(de).ftl", Locale.ITALY);
+            assertEquals(Locale.GERMANY, t.getLocale());
+            assertEquals("de_DE", getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl");
+            assertEquals(Locale.US, t.getLocale());
+            assertEquals("en_US", getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl", Locale.ITALY);
+            assertEquals(Locale.ITALY, t.getLocale());
+            assertEquals("it_IT", getTemplateOutput(t));
+        }
+    }
+
+    @Test
+    public void testConfigurableSettings() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        cfg.setLocale(Locale.US);
+        
+        TemplateConfiguration.Builder tcFR = new TemplateConfiguration.Builder();
+        tcFR.setLocale(Locale.FRANCE);
+        TemplateConfiguration.Builder tcYN = new TemplateConfiguration.Builder();
+        tcYN.setBooleanFormat("Y,N");
+        TemplateConfiguration.Builder tc00 = new TemplateConfiguration.Builder();
+        tc00.setNumberFormat("0.00");
+        cfg.setTemplateConfigurations(
+                new MergingTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(fr)*"), tcFR.build()),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(yn)*"), tcYN.build()),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(00)*"), tc00.build())
+                )
+        );
+        
+        String commonFTL = "${.locale} ${true?string} ${1.2}";
+        StringTemplateLoader tl = new StringTemplateLoader();
+        tl.putTemplate("default", commonFTL);
+        tl.putTemplate("(fr)", commonFTL);
+        tl.putTemplate("(yn)(00)", commonFTL);
+        tl.putTemplate("(00)(fr)", commonFTL);
+        cfg.setTemplateLoader(tl);
+        
+        assertEquals("en_US true 1.2", getTemplateOutput(cfg.getTemplate("default")));
+        assertEquals("fr_FR true 1,2", getTemplateOutput(cfg.getTemplate("(fr)")));
+        assertEquals("en_US Y 1.20", getTemplateOutput(cfg.getTemplate("(yn)(00)")));
+        assertEquals("fr_FR true 1,20", getTemplateOutput(cfg.getTemplate("(00)(fr)")));
+    }
+    
+    @Test
+    public void testCustomAttributes() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        
+        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
+        tc1.setCustomAttribute("a1", "a1tc1");
+        tc1.setCustomAttribute("a2", "a2tc1");
+        tc1.setCustomAttribute("a3", "a3tc1");
+        tc1.setCustomAttribute(CUST_ATT_1, "ca1tc1");
+        tc1.setCustomAttribute(CUST_ATT_2, "ca2tc1");
+        
+        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
+        tc2.setCustomAttribute("a1", "a1tc2");
+        tc2.setCustomAttribute(CUST_ATT_1, "ca1tc2");
+        
+        cfg.setTemplateConfigurations(
+                new MergingTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc1)*"), tc1.build()),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc2)*"), tc2.build())
+                )
+        );
+        
+        String commonFTL = "<#ftl attributes={ 'a3': 'a3temp' }>";
+        StringTemplateLoader tl = new StringTemplateLoader();
+        tl.putTemplate("(tc1)", commonFTL);
+        tl.putTemplate("(tc1)noHeader", "");
+        tl.putTemplate("(tc2)", commonFTL);
+        tl.putTemplate("(tc1)(tc2)", commonFTL);
+        cfg.setTemplateLoader(tl);
+
+        {
+            Template t = cfg.getTemplate("(tc1)");
+            assertEquals("a1tc1", t.getCustomAttribute("a1"));
+            assertEquals("a2tc1", t.getCustomAttribute("a2"));
+            assertEquals("a3temp", t.getCustomAttribute("a3"));
+            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
+            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
+        }
+        {
+            Template t = cfg.getTemplate("(tc1)noHeader");
+            assertEquals("a1tc1", t.getCustomAttribute("a1"));
+            assertEquals("a2tc1", t.getCustomAttribute("a2"));
+            assertEquals("a3tc1", t.getCustomAttribute("a3"));
+            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
+            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
+        }
+        {
+            Template t = cfg.getTemplate("(tc2)");
+            assertEquals("a1tc2", t.getCustomAttribute("a1"));
+            assertNull(t.getCustomAttribute("a2"));
+            assertEquals("a3temp", t.getCustomAttribute("a3"));
+            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
+            assertNull(t.getCustomAttribute(CUST_ATT_2));
+        }
+        {
+            Template t = cfg.getTemplate("(tc1)(tc2)");
+            assertEquals("a1tc2", t.getCustomAttribute("a1"));
+            assertEquals("a2tc1", t.getCustomAttribute("a2"));
+            assertEquals("a3temp", t.getCustomAttribute("a3"));
+            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
+            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
+        }
+    }
+    
+    private String getTemplateOutput(Template t) throws TemplateException, IOException {
+        StringWriter sw = new StringWriter();
+        t.process(null, sw);
+        return sw.toString();
+    }
+
+    private Configuration createCommonEncodingTesterConfig() throws UnsupportedEncodingException {
+        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
+        cfg.setLocale(Locale.US);
+        
+        ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
+        tl.putTemplate("utf8.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_8));
+        tl.putTemplate("utf16.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_16LE));
+        tl.putTemplate("default.ftl", TEXT_WITH_ACCENTS.getBytes(ISO_8859_2));
+        tl.putTemplate("utf8-latin2.ftl",
+                ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2));
+        tl.putTemplate("default-latin2.ftl",
+                ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2));
+        cfg.setTemplateLoader(tl);
+        
+        TemplateConfiguration.Builder tcUtf8 = new TemplateConfiguration.Builder();
+        tcUtf8.setSourceEncoding(StandardCharsets.UTF_8);
+        TemplateConfiguration.Builder tcUtf16 = new TemplateConfiguration.Builder();
+        tcUtf16.setSourceEncoding(StandardCharsets.UTF_16LE);
+        cfg.setTemplateConfigurations(
+                new FirstMatchTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf8*"), tcUtf8.build()),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf16*"), tcUtf16.build())
+                ).allowNoMatch(true));
+        return cfg;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java b/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
index c049cde..6f8517a 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
@@ -44,10 +44,11 @@ public class TemplateGetEncodingTest {
             tl.putBinaryTemplate("bin-static", "<#test>");
             tl.putTextTemplate("text", "test");
             tl.putTextTemplate("text-static", "<#test>");
-            TemplateConfiguration staticTextTC = new TemplateConfiguration();
-            staticTextTC.setTemplateLanguage(TemplateLanguage.STATIC_TEXT);
+            TemplateConfiguration.Builder staticTextTCB = new TemplateConfiguration.Builder();
+            staticTextTCB.setTemplateLanguage(TemplateLanguage.STATIC_TEXT);
             cfg.setTemplateConfigurations(
-                    new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*-static*"), staticTextTC));
+                    new ConditionalTemplateConfigurationFactory(
+                            new FileNameGlobMatcher("*-static*"), staticTextTCB.build()));
             cfg.setTemplateLoader(tl);
             cfg.setCacheStorage(new StrongCacheStorage());
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
index fe1eb7a..3a416d9 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
@@ -22,7 +22,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.freemarker.core.Configuration;
@@ -159,8 +159,9 @@ public class TemplateConfigurationFactoryTest {
 
     @Test
     public void testSetConfiguration() {
-        TemplateConfiguration tc = new TemplateConfiguration();
-        ConditionalTemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*"), tc);
+        TemplateConfiguration tc = new TemplateConfiguration.Builder().build();
+        ConditionalTemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(
+                new FileNameGlobMatcher("*"), tc);
         assertNull(tcf.getConfiguration());
         assertNull(tc.getParentConfiguration());
         
@@ -181,10 +182,10 @@ public class TemplateConfigurationFactoryTest {
 
     @SuppressWarnings("boxing")
     private TemplateConfiguration newTemplateConfiguration(int id) {
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setCustomAttribute("id", id);
-        tc.setCustomAttribute("contains" + id, true);
-        return tc;
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomAttribute("id", id);
+        tcb.setCustomAttribute("contains" + id, true);
+        return tcb.build();
     }
 
     private void assertNotApplicable(TemplateConfigurationFactory tcf, String sourceName)
@@ -196,7 +197,7 @@ public class TemplateConfigurationFactoryTest {
             throws IOException, TemplateConfigurationFactoryException {
         TemplateConfiguration mergedTC = tcf.get(sourceName, DummyTemplateLoadingSource.INSTANCE);
         assertNotNull("TC should have its parents Configuration set", mergedTC.getParentConfiguration());
-        List<String> mergedTCAttNames = Arrays.asList(mergedTC.getCustomAttributeNames());
+        List<Object> mergedTCAttNames = new ArrayList<Object>(mergedTC.getCustomAttributes().keySet());
 
         for (TemplateConfiguration expectedTC : expectedTCs) {
             Integer tcId = (Integer) expectedTC.getCustomAttribute("id");
@@ -208,18 +209,18 @@ public class TemplateConfigurationFactoryTest {
             }
         }
         
-        for (String attName: mergedTCAttNames) {
-            if (!containsCustomAttr(attName, expectedTCs)) {
-                fail("The asserted TemplateConfiguration contains an unexpected custom attribute: " + attName);
+        for (Object attKey: mergedTCAttNames) {
+            if (!containsCustomAttr(attKey, expectedTCs)) {
+                fail("The asserted TemplateConfiguration contains an unexpected custom attribute: " + attKey);
             }
         }
         
         assertEquals(expectedTCs[expectedTCs.length - 1].getCustomAttribute("id"), mergedTC.getCustomAttribute("id"));
     }
 
-    private boolean containsCustomAttr(String attName, TemplateConfiguration... expectedTCs) {
+    private boolean containsCustomAttr(Object attKey, TemplateConfiguration... expectedTCs) {
         for (TemplateConfiguration expectedTC : expectedTCs) {
-            if (expectedTC.getCustomAttribute(attName) != null) {
+            if (expectedTC.getCustomAttribute(attKey) != null) {
                 return true;
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
index 40dd4d6..327bd94 100644
--- a/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
@@ -245,11 +245,12 @@ public class NumberFormatTest extends TemplateTest {
                 "d", new AliasTemplateNumberFormatFactory("0.0#"),
                 "hex", HexTemplateNumberFormatFactory.INSTANCE));
         
-        TemplateConfiguration tc = new TemplateConfiguration();
-        tc.setCustomNumberFormats(ImmutableMap.of(
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomNumberFormats(ImmutableMap.of(
                 "d", new AliasTemplateNumberFormatFactory("0.#'d'"),
                 "i", new AliasTemplateNumberFormatFactory("@hex")));
-        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tc));
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tcb.build()));
         
         String commonFtl = "${1?string.@f} ${1?string.@d} "
                 + "<#setting locale='fr_FR'>${1.5?string.@d} "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java b/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
index ed4fb21..85ffa81 100644
--- a/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
+++ b/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
@@ -46,13 +46,13 @@ public class ConfigureOutputFormatExamples extends ExamplesTest {
         
         // Example 2/a:
         {
-            TemplateConfiguration tcHTML = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcHTML = new TemplateConfiguration.Builder();
             tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
             
             cfg.setTemplateConfigurations(
                     new ConditionalTemplateConfigurationFactory(
                             new PathGlobMatcher("mail/**"),
-                            tcHTML));
+                            tcHTML.build()));
             
             assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("mail/t.ftl").getOutputFormat());
         }
@@ -68,28 +68,28 @@ public class ConfigureOutputFormatExamples extends ExamplesTest {
         
         // Example 3/a:
         {
-            TemplateConfiguration tcHTML = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcHTML = new TemplateConfiguration.Builder();
             tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
             
-            TemplateConfiguration tcXML = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcXML = new TemplateConfiguration.Builder();
             tcXML.setOutputFormat(XMLOutputFormat.INSTANCE);
 
-            TemplateConfiguration tcRTF = new TemplateConfiguration();
+            TemplateConfiguration.Builder tcRTF = new TemplateConfiguration.Builder();
             tcRTF.setOutputFormat(RTFOutputFormat.INSTANCE);
             
             cfg.setTemplateConfigurations(
                     new FirstMatchTemplateConfigurationFactory(
                             new ConditionalTemplateConfigurationFactory(
                                     new FileExtensionMatcher("xml"),
-                                    tcXML),
+                                    tcXML.build()),
                             new ConditionalTemplateConfigurationFactory(
                                     new OrMatcher(
                                             new FileExtensionMatcher("html"),
                                             new FileExtensionMatcher("htm")),
-                                    tcHTML),
+                                    tcHTML.build()),
                             new ConditionalTemplateConfigurationFactory(
                                     new FileExtensionMatcher("rtf"),
-                                    tcRTF)
+                                    tcRTF.build())
                     ).allowNoMatch(true)
             );
             

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java b/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
index 3896a2e..eded767 100644
--- a/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
+++ b/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
@@ -48,13 +48,13 @@ public class TemplateConfigurationExamples extends ExamplesTest {
 
         addTemplate("t.xml", "");
         
-        TemplateConfiguration tcUTF8XML = new TemplateConfiguration();
-        tcUTF8XML.setSourceEncoding(StandardCharsets.UTF_8);
-        tcUTF8XML.setOutputFormat(XMLOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbUTF8XML = new TemplateConfiguration.Builder();
+        tcbUTF8XML.setSourceEncoding(StandardCharsets.UTF_8);
+        tcbUTF8XML.setOutputFormat(XMLOutputFormat.INSTANCE);
 
         {
             cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
-                    new FileExtensionMatcher("xml"), tcUTF8XML));
+                    new FileExtensionMatcher("xml"), tcbUTF8XML.build()));
             
             Template t = cfg.getTemplate("t.xml");
             assertEquals(StandardCharsets.UTF_8, t.getSourceEncoding());
@@ -79,11 +79,11 @@ public class TemplateConfigurationExamples extends ExamplesTest {
         addTemplate("mail/t.subject.ftl", "");
         addTemplate("mail/t.body.ftl", "");
 
-        TemplateConfiguration tcSubject = new TemplateConfiguration();
-        tcSubject.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbSubject = new TemplateConfiguration.Builder();
+        tcbSubject.setOutputFormat(PlainTextOutputFormat.INSTANCE);
         
-        TemplateConfiguration tcBody = new TemplateConfiguration();
-        tcBody.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbBody = new TemplateConfiguration.Builder();
+        tcbBody.setOutputFormat(HTMLOutputFormat.INSTANCE);
         
         cfg.setTemplateConfigurations(
                 new ConditionalTemplateConfigurationFactory(
@@ -91,10 +91,10 @@ public class TemplateConfigurationExamples extends ExamplesTest {
                         new FirstMatchTemplateConfigurationFactory(
                                 new ConditionalTemplateConfigurationFactory(
                                         new FileNameGlobMatcher("*.subject.*"),
-                                        tcSubject),
+                                        tcbSubject.build()),
                                 new ConditionalTemplateConfigurationFactory(
                                         new FileNameGlobMatcher("*.body.*"),
-                                        tcBody)
+                                        tcbBody.build())
                                 )
                                 .noMatchErrorDetails("Mail template names must contain \".subject.\" or \".body.\"!")
                         ));
@@ -125,38 +125,38 @@ public class TemplateConfigurationExamples extends ExamplesTest {
         addTemplate("t.xml", "");
         addTemplate("mail/t.html", "");
 
-        TemplateConfiguration tcStats = new TemplateConfiguration();
-        tcStats.setDateTimeFormat("iso");
-        tcStats.setDateFormat("iso");
-        tcStats.setTimeFormat("iso");
-        tcStats.setTimeZone(_DateUtil.UTC);
+        TemplateConfiguration.Builder tcbStats = new TemplateConfiguration.Builder();
+        tcbStats.setDateTimeFormat("iso");
+        tcbStats.setDateFormat("iso");
+        tcbStats.setTimeFormat("iso");
+        tcbStats.setTimeZone(_DateUtil.UTC);
 
-        TemplateConfiguration tcMail = new TemplateConfiguration();
-        tcMail.setSourceEncoding(StandardCharsets.UTF_8);
+        TemplateConfiguration.Builder tcbMail = new TemplateConfiguration.Builder();
+        tcbMail.setSourceEncoding(StandardCharsets.UTF_8);
         
-        TemplateConfiguration tcHTML = new TemplateConfiguration();
-        tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbHTML = new TemplateConfiguration.Builder();
+        tcbHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
         
-        TemplateConfiguration tcXML = new TemplateConfiguration();
-        tcXML.setOutputFormat(XMLOutputFormat.INSTANCE);
+        TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
+        tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
         
         cfg.setTemplateConfigurations(
                 new MergingTemplateConfigurationFactory(
                         new ConditionalTemplateConfigurationFactory(
                                 new FileNameGlobMatcher("*.stats.*"),
-                                tcStats),
+                                tcbStats.build()),
                         new ConditionalTemplateConfigurationFactory(
                                 new PathGlobMatcher("mail/**"),
-                                tcMail),
+                                tcbMail.build()),
                         new FirstMatchTemplateConfigurationFactory(
                                 new ConditionalTemplateConfigurationFactory(
                                         new FileExtensionMatcher("xml"),
-                                        tcXML),
+                                        tcbXML.build()),
                                 new ConditionalTemplateConfigurationFactory(
                                         new OrMatcher(
                                                 new FileExtensionMatcher("html"),
                                                 new FileExtensionMatcher("htm")),
-                                        tcHTML)
+                                        tcbHTML.build())
                         ).allowNoMatch(true)
                 )
         );

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/88baea20/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java b/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
index d638025..85b4c13 100644
--- a/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
@@ -547,18 +547,18 @@ public class FreemarkerServletTest {
             cfg.setSourceEncoding(CFG_DEFAULT_ENCODING);
 
             {
-                TemplateConfiguration outUtf8TC = new TemplateConfiguration();
-                outUtf8TC.setOutputEncoding(StandardCharsets.UTF_8);
+                TemplateConfiguration.Builder outUtf8TCB = new TemplateConfiguration.Builder();
+                outUtf8TCB.setOutputEncoding(StandardCharsets.UTF_8);
                 
-                TemplateConfiguration srcUtf8TC = new TemplateConfiguration();
-                srcUtf8TC.setSourceEncoding(StandardCharsets.UTF_8);
+                TemplateConfiguration.Builder srcUtf8TCB = new TemplateConfiguration.Builder();
+                srcUtf8TCB.setSourceEncoding(StandardCharsets.UTF_8);
                 
                 cfg.setTemplateConfigurations(
                         new FirstMatchTemplateConfigurationFactory(
                                 new ConditionalTemplateConfigurationFactory(
-                                        new FileNameGlobMatcher(FOO_SRC_UTF8_FTL), srcUtf8TC),
+                                        new FileNameGlobMatcher(FOO_SRC_UTF8_FTL), srcUtf8TCB.build()),
                                 new ConditionalTemplateConfigurationFactory(
-                                        new FileNameGlobMatcher(FOO_OUT_UTF8_FTL), outUtf8TC)
+                                        new FileNameGlobMatcher(FOO_OUT_UTF8_FTL), outUtf8TCB.build())
                         )
                         .allowNoMatch(true)
                 );