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:50 UTC

[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.)

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.