freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [4/4] incubator-freemarker git commit: Made Template immutable (via public API-s). Template-specific settings now can only come from the TemplateConfiguration associated to the template, or from the #ftl header for some settings (most notably for custom
Date Tue, 11 Apr 2017 20:05:18 GMT
Made Template immutable (via public API-s). Template-specific settings now can only come from the TemplateConfiguration associated to the template, or from the #ftl header for some settings (most notably for custom attributes). Many smaller cleanups in the still evolving new configuration setting API-s. An important change is that TemplateConfiguration doesn't store the Configuration anymore, thus we don't have a loop in the bean dependency graph anymore. Also, getParent() is not part of the these API-s anymore, as for many configuration API implementations it doesn't make sense anymore.


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

Branch: refs/heads/3
Commit: 0356b30b05ddb2699b92c7ead768541b7563c408
Parents: 413c0e1
Author: ddekany <ddekany@apache.org>
Authored: Tue Apr 11 22:04:51 2017 +0200
Committer: ddekany <ddekany@apache.org>
Committed: Tue Apr 11 22:04:51 2017 +0200

----------------------------------------------------------------------
 .../Editor-Inspections-FreeMarker.xml           |  18 +
 .../Java-code-style-FreeMarker.xml              |  18 +
 .../freemarker/core/ASTExpStringLiteral.java    |   8 +-
 .../freemarker/core/BuiltInsForStringsMisc.java |  23 +-
 .../apache/freemarker/core/Configuration.java   |   3 -
 .../org/apache/freemarker/core/Environment.java |  74 +--
 .../MutableProcessingAndParseConfiguration.java |  88 +++-
 .../core/MutableProcessingConfiguration.java    | 114 ++--
 .../freemarker/core/ParserConfiguration.java    |   3 +-
 .../core/SettingValueNotSetException.java       |   4 +-
 .../org/apache/freemarker/core/Template.java    | 524 ++++++++++++++-----
 .../freemarker/core/TemplateConfiguration.java  | 420 ++++++---------
 .../freemarker/core/TemplateLanguage.java       |  19 +-
 ...TemplateParserConfigurationWithFallback.java | 146 ++++++
 ..._ParserConfigurationWithInheritedFormat.java | 147 ------
 ...ConditionalTemplateConfigurationFactory.java |  11 -
 .../FirstMatchTemplateConfigurationFactory.java |   8 -
 .../MergingTemplateConfigurationFactory.java    |  23 +-
 .../TemplateConfigurationFactory.java           |  36 --
 .../templateresolver/TemplateLoadingResult.java |   5 +-
 .../impl/DefaultTemplateResolver.java           |  19 +-
 src/main/javacc/FTL.jj                          |  48 +-
 src/manual/en_US/FM3-CHANGE-LOG.txt             |   4 +
 .../freemarker/core/ConfigurableTest.java       |   2 +-
 .../freemarker/core/ConfigurationTest.java      | 182 ++++---
 .../freemarker/core/CustomAttributeTest.java    | 161 +++---
 .../IncludeAndImportConfigurableLayersTest.java |  19 +-
 .../freemarker/core/OutputFormatTest.java       |   8 +-
 .../core/TemplateConfigurationTest.java         | 164 +-----
 .../freemarker/core/TemplateLevelSettings.java  | 103 ----
 .../TemplateConfigurationFactoryTest.java       |  37 +-
 31 files changed, 1200 insertions(+), 1239 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
----------------------------------------------------------------------
diff --git a/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
index c2e6c17..4b40e57 100644
--- a/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
+++ b/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
@@ -1,3 +1,21 @@
+<!--
+  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.
+-->
 <component name="InspectionProjectProfileManager">
   <profile version="1.0">
     <option name="myName" value="FreeMarker" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
----------------------------------------------------------------------
diff --git a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
index d77ca2d..983f742 100644
--- a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
+++ b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
@@ -1,3 +1,21 @@
+<!--
+  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.
+-->
 <code_scheme name="FreeMarker">
   <option name="LINE_SEPARATOR" value="&#xA;" />
   <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java b/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
index 6af5d27..972752a 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpStringLiteral.java
@@ -54,19 +54,21 @@ final class ASTExpStringLiteral extends ASTExpression implements TemplateScalarM
         if (value.length() > 3 && (value.indexOf("${") >= 0 || value.indexOf("#{") >= 0)) {
             
             Template parentTemplate = getTemplate();
-            ParserConfiguration pcfg = parentTemplate.getParserConfiguration();
+            ParserConfiguration pCfg = parentTemplate.getParserConfiguration();
 
             try {
                 SimpleCharStream simpleCharacterStream = new SimpleCharStream(
                         new StringReader(value),
                         beginLine, beginColumn + 1,
                         value.length());
-                simpleCharacterStream.setTabSize(pcfg.getTabSize());
+                simpleCharacterStream.setTabSize(pCfg.getTabSize());
                 
                 FMParserTokenManager tkMan = new FMParserTokenManager(
                         simpleCharacterStream);
                 
-                FMParser parser = new FMParser(parentTemplate, false, tkMan, pcfg, null);
+                FMParser parser = new FMParser(parentTemplate, false,
+                        tkMan, pCfg, null, null,
+                        null);
                 // We continue from the parent parser's current state:
                 parser.setupStringLiteralMode(parentTkMan, outputFormat);
                 try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 f83cbbb..ae06392 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -86,14 +86,11 @@ class BuiltInsForStringsMisc {
                             simpleCharStream);
                     tkMan.SwitchTo(FMParserConstants.FM_EXPRESSION);
 
-                    // pCfg.outputFormat is exceptional: it's inherited from the lexical context
-                    if (pCfg.getOutputFormat() != outputFormat) {
-                        pCfg = new _ParserConfigurationWithInheritedFormat(
-                                pCfg, outputFormat, Integer.valueOf(autoEscapingPolicy));
-                    }
-                    
+                    // pCfg.outputFormat+autoEscapingPolicy is exceptional: it's inherited from the lexical context
                     FMParser parser = new FMParser(
-                            parentTemplate, false, tkMan, pCfg, null);
+                            parentTemplate, false, tkMan,
+                            pCfg, outputFormat, autoEscapingPolicy,
+                            null);
                     
                     exp = parser.ASTExpression();
                 } catch (TokenMgrError e) {
@@ -172,17 +169,14 @@ class BuiltInsForStringsMisc {
             final Template interpretedTemplate;
             try {
                 ParserConfiguration pCfg = parentTemplate.getParserConfiguration();
-                // pCfg.outputFormat is exceptional: it's inherited from the lexical context
-                if (pCfg.getOutputFormat() != outputFormat) {
-                    pCfg = new _ParserConfigurationWithInheritedFormat(
-                            pCfg, outputFormat, Integer.valueOf(autoEscapingPolicy));
-                }
+                // pCfg.outputFormat+autoEscapingPolicy is exceptional: it's inherited from the lexical context
                 interpretedTemplate = new Template(
                         (parentTemplate.getName() != null ? parentTemplate.getName() : "nameless_template") + "->" + id,
                         null,
                         new StringReader(templateSource),
-                        parentTemplate.getConfiguration(), pCfg,
-                        null);
+                        parentTemplate.getConfiguration(), parentTemplate.getTemplateConfiguration(),
+                        outputFormat, autoEscapingPolicy,
+                        null, null);
             } catch (IOException e) {
                 throw new _MiscTemplateException(this, e, env,
                         "Template parsing with \"?", key, "\" has failed with this error:\n\n",
@@ -192,7 +186,6 @@ class BuiltInsForStringsMisc {
                         "\n\nThe failed expression:");
             }
             
-            interpretedTemplate.setLocale(env.getLocale());
             return new TemplateProcessorModel(interpretedTemplate);
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 c8e700a..5d2cd34 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -793,9 +793,6 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
      */
     public void setTemplateConfigurations(TemplateConfigurationFactory templateConfigurations) {
         if (templateResolver.getTemplateConfigurations() != templateConfigurations) {
-            if (templateConfigurations != null) {
-                templateConfigurations.setConfiguration(this);
-            }
             recreateTemplateResolverWith(templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(),
                     templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(),
                     templateConfigurations);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 5382404..1f75e2f 100644
--- a/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/src/main/java/org/apache/freemarker/core/Environment.java
@@ -173,6 +173,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     private Writer out;
     private ASTDirMacro.Context currentMacroContext;
     private LocalContextStack localContextStack;
+    private final Template mainTemplate;
     private final Namespace mainNamespace;
     private Namespace currentNamespace, globalNamespace;
     private HashMap<String, Namespace> loadedLibs;
@@ -217,10 +218,10 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) {
-        super(template);
+        mainTemplate = template;
         configuration = template.getConfiguration();
         globalNamespace = new Namespace(null);
-        currentNamespace = mainNamespace = new Namespace(template);
+        currentNamespace = mainNamespace = new Namespace(mainTemplate);
         this.out = out;
         this.rootDataModel = rootDataModel;
         importMacros(template);
@@ -235,7 +236,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
      * @since 2.3.22
      */
     public Template getMainTemplate() {
-        return mainNamespace.getTemplate();
+        return mainTemplate;
     }
 
     /**
@@ -922,17 +923,17 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
-        return getParent().getTemplateExceptionHandler();
+        return getMainTemplate().getTemplateExceptionHandler();
     }
 
     @Override
     protected ArithmeticEngine getInheritedArithmeticEngine() {
-        return getParent().getArithmeticEngine();
+        return getMainTemplate().getArithmeticEngine();
     }
 
     @Override
     protected ObjectWrapper getInheritedObjectWrapper() {
-        return getParent().getObjectWrapper();
+        return getMainTemplate().getObjectWrapper();
     }
 
     @Override
@@ -962,7 +963,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected Locale getInheritedLocale() {
-        return getParent().getLocale();
+        return getMainTemplate().getLocale();
     }
 
     @Override
@@ -991,7 +992,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected TimeZone getInheritedTimeZone() {
-        return getParent().getTimeZone();
+        return getMainTemplate().getTimeZone();
     }
 
     @Override
@@ -1020,7 +1021,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
-        return getParent().getSQLDateAndTimeTimeZone();
+        return getMainTemplate().getSQLDateAndTimeTimeZone();
     }
 
     // Replace with Objects.equals in Java 7
@@ -1051,57 +1052,57 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected Charset getInheritedURLEscapingCharset() {
-        return getParent().getURLEscapingCharset();
+        return getMainTemplate().getURLEscapingCharset();
     }
 
     @Override
     protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
-        return getParent().getNewBuiltinClassResolver();
+        return getMainTemplate().getNewBuiltinClassResolver();
     }
 
     @Override
     protected boolean getInheritedAutoFlush() {
-        return getParent().getAutoFlush();
+        return getMainTemplate().getAutoFlush();
     }
 
     @Override
     protected boolean getInheritedShowErrorTips() {
-        return getParent().getShowErrorTips();
+        return getMainTemplate().getShowErrorTips();
     }
 
     @Override
     protected boolean getInheritedAPIBuiltinEnabled() {
-        return getParent().getAPIBuiltinEnabled();
+        return getMainTemplate().getAPIBuiltinEnabled();
     }
 
     @Override
     protected boolean getInheritedLogTemplateExceptions() {
-        return getParent().getLogTemplateExceptions();
+        return getMainTemplate().getLogTemplateExceptions();
     }
 
     @Override
     protected boolean getInheritedLazyImports() {
-        return getParent().getLazyImports();
+        return getMainTemplate().getLazyImports();
     }
 
     @Override
     protected Boolean getInheritedLazyAutoImports() {
-        return getParent().getLazyAutoImports();
+        return getMainTemplate().getLazyAutoImports();
     }
 
     @Override
     protected Map<String, String> getInheritedAutoImports() {
-        return getParent().getAutoImports();
+        return getMainTemplate().getAutoImports();
     }
 
     @Override
     protected List<String> getInheritedAutoIncludes() {
-        return getParent().getAutoIncludes();
+        return getMainTemplate().getAutoIncludes();
     }
 
     @Override
     protected Object getInheritedCustomAttribute(Object name) {
-        return getParent().getCustomAttribute(name);
+        return getMainTemplate().getCustomAttribute(name);
     }
 
     /*
@@ -1117,7 +1118,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected Charset getInheritedOutputEncoding() {
-        return getParent().getOutputEncoding();
+        return getMainTemplate().getOutputEncoding();
     }
 
     /**
@@ -1220,27 +1221,27 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected String getInheritedNumberFormat() {
-        return getParent().getNumberFormat();
+        return getMainTemplate().getNumberFormat();
     }
 
     @Override
     protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
-        return getParent().getCustomNumberFormats();
+        return getMainTemplate().getCustomNumberFormats();
     }
 
     @Override
     protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
-        return getParent().getCustomNumberFormat(name);
+        return getMainTemplate().getCustomNumberFormat(name);
     }
 
     @Override
     protected boolean getInheritedHasCustomFormats() {
-        return getParent().hasCustomFormats();
+        return getMainTemplate().hasCustomFormats();
     }
 
     @Override
     protected String getInheritedBooleanFormat() {
-        return getParent().getBooleanFormat();
+        return getMainTemplate().getBooleanFormat();
     }
 
     String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
@@ -1535,7 +1536,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected String getInheritedTimeFormat() {
-        return getParent().getTimeFormat();
+        return getMainTemplate().getTimeFormat();
     }
 
     @Override
@@ -1553,7 +1554,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected String getInheritedDateFormat() {
-        return getParent().getDateFormat();
+        return getMainTemplate().getDateFormat();
     }
 
     @Override
@@ -1571,17 +1572,17 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     @Override
     protected String getInheritedDateTimeFormat() {
-        return getParent().getDateTimeFormat();
+        return getMainTemplate().getDateTimeFormat();
     }
 
     @Override
     protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
-        return getParent().getCustomDateFormats();
+        return getMainTemplate().getCustomDateFormats();
     }
 
     @Override
     protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
-        return getParent().getCustomDateFormat(name);
+        return getMainTemplate().getCustomDateFormat(name);
     }
 
     public Configuration getConfiguration() {
@@ -2986,6 +2987,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
         private Template template;
 
+        // TODO [FM3] #macro etc. uses this, so the NS is associated to the main temp., even if #macro is elsewhere.
         Namespace() {
             this(Environment.this.getMainTemplate());
         }
@@ -3001,9 +3003,15 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         public Template getTemplate() {
             return template == null ? Environment.this.getMainTemplate() : template;
         }
-        
+
+        /**
+         * Used when initializing a lazily initialized namespace.
+         */
         void setTemplate(Template template) {
-            this.template = template; 
+            if (this.template != null) {
+                throw new IllegalStateException("Can't change the template of a namespace once it was established.");
+            }
+            this.template = template;
         }
         
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 aaac98d..6b47360 100644
--- a/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
@@ -46,8 +46,8 @@ public abstract class MutableProcessingAndParseConfiguration<
         super(incompatibleImprovements);
     }
 
-    protected MutableProcessingAndParseConfiguration(MutableProcessingConfiguration parent) {
-        super(parent);
+    protected MutableProcessingAndParseConfiguration() {
+        super();
     }
 
     /**
@@ -59,6 +59,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #tagSyntax(int)}
+     */
+    public SelfT tagSyntax(int tagSyntax) {
+        setTagSyntax(tagSyntax);
+        return self();
+    }
+
+    /**
      * The getter pair of {@link #setTagSyntax(int)}.
      */
     @Override
@@ -91,6 +99,14 @@ public abstract class MutableProcessingAndParseConfiguration<
         this.templateLanguage = templateLanguage;
     }
 
+    /**
+     * Fluent API equivalent of {@link #setTemplateLanguage(TemplateLanguage)}
+     */
+    public SelfT templateLanguage(TemplateLanguage templateLanguage) {
+        setTemplateLanguage(templateLanguage);
+        return self();
+    }
+
     public boolean isTemplateLanguageSet() {
         return templateLanguage != null;
     }
@@ -104,6 +120,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #setNamingConvention(int)}
+     */
+    public SelfT namingConvention(int namingConvention) {
+        setNamingConvention(namingConvention);
+        return self();
+    }
+
+    /**
      * The getter pair of {@link #setNamingConvention(int)}.
      */
     @Override
@@ -115,7 +139,7 @@ public abstract class MutableProcessingAndParseConfiguration<
     protected abstract int getInheritedNamingConvention();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
      */
     @Override
     public boolean isNamingConventionSet() {
@@ -130,6 +154,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #setWhitespaceStripping(boolean)}
+     */
+    public SelfT whitespaceStripping(boolean whitespaceStripping) {
+        setWhitespaceStripping(whitespaceStripping);
+        return self();
+    }
+
+    /**
      * The getter pair of {@link #getWhitespaceStripping()}.
      */
     @Override
@@ -141,7 +173,7 @@ public abstract class MutableProcessingAndParseConfiguration<
     protected abstract boolean getInheritedWhitespaceStripping();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
      */
     @Override
     public boolean isWhitespaceStrippingSet() {
@@ -158,6 +190,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #setAutoEscapingPolicy(int)}
+     */
+    public SelfT autoEscapingPolicy(int autoEscapingPolicy) {
+        setAutoEscapingPolicy(autoEscapingPolicy);
+        return self();
+    }
+
+    /**
      * The getter pair of {@link #setAutoEscapingPolicy(int)}.
      */
     @Override
@@ -169,7 +209,7 @@ public abstract class MutableProcessingAndParseConfiguration<
     protected abstract int getInheritedAutoEscapingPolicy();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
      */
     @Override
     public boolean isAutoEscapingPolicySet() {
@@ -185,6 +225,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #setOutputFormat(OutputFormat)}
+     */
+    public SelfT outputFormat(OutputFormat outputFormat) {
+        setOutputFormat(outputFormat);
+        return self();
+    }
+
+    /**
      * The getter pair of {@link #setOutputFormat(OutputFormat)}.
      */
     @Override
@@ -195,7 +243,7 @@ public abstract class MutableProcessingAndParseConfiguration<
     protected abstract OutputFormat getInheritedOutputFormat();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
      */
     @Override
     public boolean isOutputFormatSet() {
@@ -210,6 +258,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #setRecognizeStandardFileExtensions(boolean)}
+     */
+    public SelfT recognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
+        setRecognizeStandardFileExtensions(recognizeStandardFileExtensions);
+        return self();
+    }
+
+    /**
      * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
      */
     @Override
@@ -221,7 +277,7 @@ public abstract class MutableProcessingAndParseConfiguration<
     protected abstract boolean getInheritedRecognizeStandardFileExtensions();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
      */
     @Override
     public boolean isRecognizeStandardFileExtensionsSet() {
@@ -244,6 +300,14 @@ public abstract class MutableProcessingAndParseConfiguration<
         this.sourceEncoding = sourceEncoding;
     }
 
+    /**
+     * Fluent API equivalent of {@link #setSourceEncoding(Charset)}
+     */
+    public SelfT sourceEncoding(Charset sourceEncoding) {
+        setSourceEncoding(sourceEncoding);
+        return self();
+    }
+
     public boolean isSourceEncodingSet() {
         return sourceEncoding != null;
     }
@@ -258,6 +322,14 @@ public abstract class MutableProcessingAndParseConfiguration<
     }
 
     /**
+     * Fluent API equivalent of {@link #setTabSize(int)}
+     */
+    public SelfT tabSize(int tabSize) {
+        setTabSize(tabSize);
+        return self();
+    }
+
+    /**
      * Getter pair of {@link #setTabSize(int)}.
      *
      * @since 2.3.25
@@ -270,7 +342,7 @@ public abstract class MutableProcessingAndParseConfiguration<
     protected abstract int getInheritedTabSize();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
      *
      * @since 2.3.25
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 305194d..76204df 100644
--- a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -317,8 +317,6 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         URL_ESCAPING_CHARSET_KEY_CAMEL_CASE
     };
 
-    private ProcessingConfiguration parent;
-
     private Locale locale;
     private String numberFormat;
     private String timeFormat;
@@ -354,9 +352,8 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * default without marking them as set.
      */
     // TODO Move to Configuration(Builder) constructor
-    MutableProcessingConfiguration(Version incompatibleImprovements) {
-        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        parent = null;
+    MutableProcessingConfiguration(Version iciForDefaults) {
+        _CoreAPI.checkVersionNotNullAndSupported(iciForDefaults);
         locale = Configuration.getDefaultLocale();
         timeZone = Configuration.getDefaultTimeZone();
         sqlDateAndTimeTimeZone = null;
@@ -367,7 +364,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         dateTimeFormat = "";
         templateExceptionHandler = Configuration.getDefaultTemplateExceptionHandler();
         arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE;
-        objectWrapper = Configuration.getDefaultObjectWrapper(incompatibleImprovements);
+        objectWrapper = Configuration.getDefaultObjectWrapper(iciForDefaults);
         autoFlush = Boolean.TRUE;
         newBuiltinClassResolver = TemplateClassResolver.UNRESTRICTED_RESOLVER;
         showErrorTips = Boolean.TRUE;
@@ -391,44 +388,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * Creates a new instance. Normally you do not need to use this constructor,
      * as you don't use <code>MutableProcessingConfiguration</code> directly, but its subclasses.
      */
-    protected MutableProcessingConfiguration(MutableProcessingConfiguration parent) {
-        this.parent = parent;
-        locale = null;
-        numberFormat = null;
-        templateExceptionHandler = null;
-    }
-    
-    /**
-     * Returns the parent {@link MutableProcessingConfiguration} object of this object. The parent stores the default setting values for
-     * this {@link MutableProcessingConfiguration}. For example, the parent of a {@link org.apache.freemarker.core.Template} object is a
-     * {@link Configuration} object, so values not specified on {@link Template}-level are get from the
-     * {@link Configuration} object.
-     * 
-     * <p>
-     * Note on the parent of {@link Environment}: If you set {@link Configuration#setIncompatibleImprovements(Version)
-     * incompatible_improvements} to at least 2.3.22, it will be always the "main" {@link Template}, that is, the
-     * template for whose processing the {@link Environment} was created. With lower {@code incompatible_improvements},
-     * the current parent can temporary change <em>during template execution</em>, for example when your are inside an
-     * {@code #include}-d template (among others). Thus, don't build on which {@link Template} the parent of
-     * {@link Environment} is during template execution, unless you set {@code incompatible_improvements} to 2.3.22 or
-     * higher.
-     *
-     * @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 ProcessingConfiguration getParent() {
-        return parent;
+    protected MutableProcessingConfiguration() {
+        // Empty
     }
-    
-    /**
-     * Reparenting support. This is used by Environment when it includes a
-     * template - the included template becomes the parent configurable during
-     * its evaluation.
-     */
-    void setParent(ProcessingConfiguration parent) {
-        this.parent = parent;
-    }
-    
+
     /**
      * Sets the default locale used for number and date formatting (among others), also the locale used for searching
      * localized template variations when no locale was explicitly requested.
@@ -456,7 +419,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract Locale getInheritedLocale();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -500,7 +463,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract TimeZone getInheritedTimeZone();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -603,7 +566,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract TimeZone getInheritedSQLDateAndTimeTimeZone();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -661,7 +624,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract String getInheritedNumberFormat();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -744,7 +707,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -829,7 +792,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract String getInheritedBooleanFormat();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -870,7 +833,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract String getInheritedTimeFormat();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -911,7 +874,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract String getInheritedDateFormat();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1030,7 +993,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract String getInheritedDateTimeFormat();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1091,7 +1054,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      * 
      * @since 2.3.24
      */
@@ -1164,7 +1127,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract TemplateExceptionHandler getInheritedTemplateExceptionHandler();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1202,7 +1165,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract ArithmeticEngine getInheritedArithmeticEngine();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1240,7 +1203,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract ObjectWrapper getInheritedObjectWrapper();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1280,7 +1243,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract Charset getInheritedOutputEncoding();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1318,7 +1281,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract Charset getInheritedURLEscapingCharset();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1369,7 +1332,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract TemplateClassResolver getInheritedNewBuiltinClassResolver();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1419,7 +1382,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract boolean getInheritedAutoFlush();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1459,7 +1422,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract boolean getInheritedShowErrorTips();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1499,7 +1462,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract boolean getInheritedAPIBuiltinEnabled();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1536,7 +1499,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract boolean getInheritedLogTemplateExceptions();
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.24
      */
@@ -1584,7 +1547,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.25
      */
@@ -1619,7 +1582,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
     
     /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      *  
      * @since 2.3.25
      */
@@ -1745,7 +1708,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     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}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      * 
      * @since 2.3.25
      */
@@ -1841,7 +1804,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     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}.
+     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
      * 
      * @since 2.3.25
      */
@@ -2597,7 +2560,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * attribute is not present in the configurable, and the configurable has
      * a parent, then the parent is looked up as well.
      *
-     * @param name the name of the custom attribute
+     * @param key the name of the custom attribute
      *
      * @return the value of the custom attribute. Note that if the custom attribute
      * was created with <tt>&lt;#ftl&nbsp;attributes={...}&gt;</tt>, then this value is already
@@ -2605,20 +2568,17 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * <code>Map</code>, ...etc., not a FreeMarker specific class).
      */
     @Override
-    public Object getCustomAttribute(Object name) {
-        Object r;
+    public Object getCustomAttribute(Object key) {
+        Object value;
         if (customAttributes != null) {
-            r = customAttributes.get(name);
-            if (r == null && customAttributes.containsKey(name)) {
+            value = customAttributes.get(key);
+            if (value == null && customAttributes.containsKey(key)) {
                 return null;
             }
         } else {
-            r = null;
-        }
-        if (r == null && parent != null) {
-            return getInheritedCustomAttribute(name);
+            value = null;
         }
-        return r;
+        return value != null ? value : getInheritedCustomAttribute(key);
     }
 
     protected abstract Object getInheritedCustomAttribute(Object name);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 599adbb..87bab74 100644
--- a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
@@ -123,7 +123,8 @@ public interface ParserConfiguration {
     boolean isRecognizeStandardFileExtensionsSet();
 
     /**
-     * See {@link Configuration#getIncompatibleImprovements()}.
+     * See {@link Configuration#getIncompatibleImprovements()}; as this is normally directly delegates to
+     * {@link Configuration#getIncompatibleImprovements()}, it's always set.
      */
     Version getIncompatibleImprovements();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
index 6ff7bab..1ce895d 100644
--- a/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
+++ b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
@@ -26,8 +26,8 @@ public class SettingValueNotSetException extends IllegalStateException {
     private final String settingName;
 
     public SettingValueNotSetException(String settingName) {
-        super("Setting " + _StringUtil.jQuote(settingName)
-                + " is not set in this layer and has no default here either.");
+        super("The " + _StringUtil.jQuote(settingName)
+                + " setting is not set in this layer and has no default here either.");
         this.settingName = settingName;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 5e58508..8671dc1 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -35,6 +35,7 @@ import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -77,49 +78,51 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * shared {@link Configuration}, and you are using {@link Configuration#getTemplate(String)} (or its overloads), then
  * use {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)} to achieve that.
  */
-public class Template extends MutableProcessingConfiguration<Template> implements CustomStateScope {
+// TODO [FM3] Try to make Template serializable for distributed caching. Transient fields will have to be restored.
+public class Template implements ProcessingConfiguration, CustomStateScope {
     public static final String DEFAULT_NAMESPACE_PREFIX = "D";
     public static final String NO_NS_PREFIX = "N";
 
     private static final int READER_BUFFER_SIZE = 8192;
-    
-    private Map macros = new HashMap();
-    private List imports = new ArrayList();
+
     private ASTElement rootElement;
-    private Charset actualSourceEncoding;
-    private String defaultNS;
-    private Serializable customLookupCondition;
-    private int actualTagSyntax;
-    private int actualNamingConvention;
-    private boolean autoEscaping;
-    private OutputFormat outputFormat;
-    private final String name;
+    private Map macros = new HashMap(); // TODO Don't create new object if it remains empty.
+    private List imports = new ArrayList(); // TODO Don't create new object if it remains empty.
+
+    // Source (TemplateLoader) related information:
     private final String sourceName;
     private final ArrayList lines = new ArrayList();
-    private final ParserConfiguration parserConfiguration;
+
+    // TODO [FM3] We want to get rid of these, thenthe same Template object could be reused for different lookups.
+    // Template lookup parameters:
+    private final String name;
+    private Locale lookupLocale;
+    private Serializable customLookupCondition;
+
+    // Inherited settings:
+    private final transient Configuration cfg;
+    private final transient TemplateConfiguration tCfg;
+    private final transient ParserConfiguration parserConfiguration;
+
+    // Values from the template content (#ftl header parameters usually), as opposed to from the TemplateConfiguration:
+    private transient OutputFormat outputFormat; // TODO Deserialization: use the name of the output format
+    private String defaultNS;
     private Map prefixToNamespaceURILookup = new HashMap();
     private Map namespaceURIToPrefixLookup = new HashMap();
+    private Map<String, Serializable> customAttributes;
+    private transient Map<Object, Object> mergedCustomAttributes;
+
+    private Integer autoEscapingPolicy;
+    // Values from template content that are detected automatically:
+    private Charset actualSourceEncoding;
+    private int actualTagSyntax;
 
+    private int actualNamingConvention;
+    // Custom state:
     private final Object lock = new Object();
     private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0);
 
     /**
-     * A prime constructor to which all other constructors should
-     * delegate directly or indirectly.
-     */
-    private Template(String name, String sourceName, Configuration cfg, ParserConfiguration customParserConfiguration) {
-        super(cfg);
-        _NullArgumentException.check("cfg", cfg);
-        this.name = name;
-        this.sourceName = sourceName;
-        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();
-    }
-
-    /**
      * Same as {@link #Template(String, String, Reader, Configuration)} with {@code null} {@code sourceName} parameter.
      */
     public Template(String name, Reader reader, Configuration cfg) throws IOException {
@@ -137,6 +140,16 @@ public class Template extends MutableProcessingConfiguration<Template> implement
     }
 
     /**
+     * Convenience constructor for {@link #Template(String, String, Reader, Configuration, TemplateConfiguration,
+     * Charset) Template(name, null, new StringReader(reader), cfg), tc, null}.
+     *
+     * @since 2.3.20
+     */
+    public Template(String name, String sourceCode, Configuration cfg, TemplateConfiguration tc) throws IOException {
+        this(name, null, new StringReader(sourceCode), cfg, tc, null);
+    }
+
+    /**
      * Convenience constructor for {@link #Template(String, String, Reader, Configuration, Charset) Template(name, null,
      * reader, cfg, sourceEncoding)}.
      */
@@ -180,12 +193,12 @@ public class Template extends MutableProcessingConfiguration<Template> implement
            String name, String sourceName, Reader reader, Configuration cfg) throws IOException {
        this(name, sourceName, reader, cfg, null);
    }
-    
+
     /**
      * Same as {@link #Template(String, String, Reader, Configuration)}, but also specifies the template's source
      * encoding.
      *
-     * @param sourceEncoding
+     * @param actualSourceEncoding
      *            This is the charset that was used to read the template. This can be {@code null} if the template
      *            was loaded from a source that returns it already as text. If this is not {@code null} and there's an
      *            {@code #ftl} header with {@code encoding} parameter, they must match, or else a
@@ -194,43 +207,37 @@ public class Template extends MutableProcessingConfiguration<Template> implement
      * @since 2.3.22
      */
    public Template(
-           String name, String sourceName, Reader reader, Configuration cfg, Charset sourceEncoding) throws
+           String name, String sourceName, Reader reader, Configuration cfg, Charset actualSourceEncoding) throws
            IOException {
-       this(name, sourceName, reader, cfg, null, sourceEncoding);
+       this(name, sourceName, reader, cfg, null, actualSourceEncoding);
    }
-   
+
     /**
      * Same as {@link #Template(String, String, Reader, Configuration, Charset)}, but also specifies a
      * {@link TemplateConfiguration}. This is mostly meant to be used by FreeMarker internally, but advanced users might
      * still find this useful.
      * 
-     * @param customParserConfiguration
-     *            Overrides the parsing related configuration settings of the {@link Configuration} parameter; can be
+     * @param templateConfiguration
+     *            Overrides the configuration settings of the {@link Configuration} parameter; can be
      *            {@code null}. This is useful as the {@link Configuration} is normally a singleton shared by all
-     *            templates, and so it's not good for specifying template-specific settings. (While {@link Template}
-     *            itself has methods to specify settings just for that template, those don't influence the parsing, and
-     *            you only have opportunity to call them after the parsing anyway.) This objects is often a
-     *            {@link TemplateConfiguration} whose parent is the {@link Configuration} parameter, and then it
-     *            practically just overrides some of the parser settings, as the others are inherited from the
-     *            {@link Configuration}. Note that if this is a {@link TemplateConfiguration}, you will also want to
-     *            call {@link TemplateConfiguration#apply(Template)} on the resulting {@link Template} so that
-     *            {@link MutableProcessingConfiguration} settings will be set too, because this constructor only uses it as a
-     *            {@link ParserConfiguration}.
-     * @param sourceEncoding
+     *            templates, and so it's not good for specifying template-specific settings. Settings that influence
+     *            parsing always have an effect, while settings that influence processing only have effect when the
+     *            template is the main template of the {@link Environment}.
+     * @param actualSourceEncoding
      *            Same as in {@link #Template(String, String, Reader, Configuration, Charset)}.
      * 
      * @since 2.3.24
      */
    public Template(
            String name, String sourceName, Reader reader,
-           Configuration cfg, ParserConfiguration customParserConfiguration,
-           Charset sourceEncoding) throws IOException {
-       this(name, sourceName, reader, cfg, customParserConfiguration, sourceEncoding, null);
+           Configuration cfg, TemplateConfiguration templateConfiguration,
+           Charset actualSourceEncoding) throws IOException {
+       this(name, sourceName, reader, cfg, templateConfiguration, actualSourceEncoding, null);
     }
 
     /**
-     * Same as {@link #Template(String, String, Reader, Configuration, ParserConfiguration, Charset)}, but allows
-     * specifying the {@code streamToUnmarkWhenEncEstabd} {@link InputStream}.
+     * Same as {@link #Template(String, String, Reader, Configuration, TemplateConfiguration, Charset)}, but allows
+     * specifying the {@code streamToUnmarkWhenEncEstabd}.
      *
      * @param streamToUnmarkWhenEncEstabd
      *         If not {@code null}, when during the parsing we reach a point where we know that no {@link
@@ -240,29 +247,61 @@ public class Template extends MutableProcessingConfiguration<Template> implement
      *         that you can retry if a {@link WrongTemplateCharsetException} is thrown without extra I/O. As keeping that
      *         mark consumes some resources, so you may want to release it as soon as possible.
      */
-   public Template(
-           String name, String sourceName, Reader reader,
-           Configuration cfg, ParserConfiguration customParserConfiguration,
-           Charset sourceEncoding, InputStream streamToUnmarkWhenEncEstabd) throws IOException, ParseException {
-        this(name, sourceName, cfg, customParserConfiguration);
+    public Template(
+            String name, String sourceName, Reader reader,
+            Configuration cfg, TemplateConfiguration templateConfiguration,
+            Charset actualSourceEncoding, InputStream streamToUnmarkWhenEncEstabd) throws IOException, ParseException {
+        this(name, sourceName, reader,
+                cfg, templateConfiguration,
+                null, null,
+                actualSourceEncoding, streamToUnmarkWhenEncEstabd);
+    }
 
-       setActualSourceEncoding(sourceEncoding);
+    /**
+     * Same as {@link #Template(String, String, Reader, Configuration, TemplateConfiguration, Charset, InputStream)},
+     * but allows specifying the output format and the auto escaping policy, with similar effect as if they were
+     * specified in the template content (like in the #ftl header).
+     * <p>
+     * <p>This method is currently only used internally, as it's not generalized enough and so it carries too much
+     * backward compatibility risk. Also, the same functionality can be achieved by constructing an appropriate
+     * {@link TemplateConfiguration}, only that's somewhat slower.
+     *
+     * @param contextOutputFormat
+     *         The output format of the enclosing lexical context, used when a template snippet is parsed on runtime. If
+     *         not {@code null}, this will override the value coming from the {@link TemplateConfiguration} or the
+     *         {@link Configuration}.
+     * @param contextAutoEscapingPolicy
+     *         Similar to {@code contextOutputFormat}; usually this and the that is set together.
+     */
+   Template(
+            String name, String sourceName, Reader reader,
+            Configuration configuration, TemplateConfiguration templateConfiguration,
+            OutputFormat contextOutputFormat, Integer contextAutoEscapingPolicy,
+            Charset actualSourceEncoding, InputStream streamToUnmarkWhenEncEstabd) throws IOException, ParseException {
+        _NullArgumentException.check("configuration", configuration);
+        this.cfg = configuration;
+        this.tCfg = templateConfiguration;
+        this.parserConfiguration = tCfg != null ? new TemplateParserConfigurationWithFallback(cfg, tCfg) : cfg;
+        this.name = name;
+        this.sourceName = sourceName;
+
+        setActualSourceEncoding(actualSourceEncoding);
         LineTableBuilder ltbReader;
         try {
-            ParserConfiguration actualParserConfiguration = getParserConfiguration();
-            
             // Ensure that the parameter Reader is only read in bigger chunks, as we don't know if the it's buffered.
             // In particular, inside the FreeMarker code, we assume that the stream stages need not be buffered.
             if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) {
                 reader = new BufferedReader(reader, READER_BUFFER_SIZE);
             }
             
-            ltbReader = new LineTableBuilder(reader, actualParserConfiguration);
+            ltbReader = new LineTableBuilder(reader, parserConfiguration);
             reader = ltbReader;
             
             try {
                 FMParser parser = new FMParser(
-                        this, reader, actualParserConfiguration, streamToUnmarkWhenEncEstabd);
+                        this, reader,
+                        parserConfiguration, contextOutputFormat, contextAutoEscapingPolicy,
+                        streamToUnmarkWhenEncEstabd);
                 try {
                     rootElement = parser.Root();
                 } catch (IndexOutOfBoundsException exc) {
@@ -550,20 +589,21 @@ public class Template extends MutableProcessingConfiguration<Template> implement
      * Returns the Configuration object associated with this template.
      */
     public Configuration getConfiguration() {
-        return (Configuration) getParent();
+        return cfg;
     }
-    
+
     /**
-     * Returns the {@link ParserConfiguration} that was used for parsing this template. This is most often the same
-     * object as {@link #getConfiguration()}, but sometimes it's a {@link TemplateConfiguration}, or something else.
-     * It's never {@code null}.
-     * 
-     * @since 2.3.24
+     * The {@link TemplateConfiguration} associated to this template, or {@code null} if there was none.
      */
+    public TemplateConfiguration getTemplateConfiguration() {
+        return tCfg;
+    }
+
     public ParserConfiguration getParserConfiguration() {
         return parserConfiguration;
     }
 
+
     /**
      * @param actualSourceEncoding
      *            The sourceEncoding that was used to read this template, or {@code null} if the source of the template
@@ -633,40 +673,39 @@ public class Template extends MutableProcessingConfiguration<Template> implement
      * Returns the output format (see {@link Configuration#setOutputFormat(OutputFormat)}) used for this template.
      * The output format of a template can come from various places, in order of increasing priority:
      * {@link Configuration#getOutputFormat()}, {@link ParserConfiguration#getOutputFormat()} (which is usually
-     * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's {@code output_format}
-     * option in the template.
+     * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's
+     * {@code output_format} option in the template.
      * 
      * @since 2.3.24
      */
     public OutputFormat getOutputFormat() {
         return outputFormat;
     }
-    
+
     /**
-     * Meant to be called by the parser only. 
+     * Should be called by the parser, for example to apply the output format specified in the #ftl header.
      */
     void setOutputFormat(OutputFormat outputFormat) {
         this.outputFormat = outputFormat;
     }
     
     /**
-     * Returns if the template actually uses auto-escaping (see {@link Configuration#setAutoEscapingPolicy(int)}). This value
-     * is decided by the parser based on the actual {@link OutputFormat}, and the auto-escaping enums, in order of
-     * increasing priority: {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()}
-     * (which is usually provided by {@link Configuration#getTemplateConfigurations()}), and finally on the {@code #ftl}
-     * header's {@code auto_esc} option in the template.
-     * 
-     * @since 2.3.24
+     * Returns if the auto-escaping policy (see {@link Configuration#setAutoEscapingPolicy(int)}) that this template
+     * uses. This is decided from these, in increasing priority:
+     * {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()},
+     * {@code #ftl} header's {@code auto_esc} option in the template.
      */
-    public boolean getAutoEscaping() {
-        return autoEscaping;
+    public int getAutoEscapingPolicy() {
+        return autoEscapingPolicy != null ? autoEscapingPolicy
+                : tCfg != null && tCfg.isAutoEscapingPolicySet() ? tCfg.getAutoEscapingPolicy()
+                : cfg.getAutoEscapingPolicy();
     }
 
     /**
-     * Meant to be called by the parser only. 
+     * Should be called by the parser, for example to apply the auto escaping policy specified in the #ftl header.
      */
-    void setAutoEscaping(boolean autoEscaping) {
-        this.autoEscaping = autoEscaping;
+    void setAutoEscapingPolicy(int autoEscapingPolicy) {
+        this.autoEscapingPolicy = autoEscapingPolicy;
     }
     
     /**
@@ -680,7 +719,7 @@ public class Template extends MutableProcessingConfiguration<Template> implement
      * Dump the raw template in canonical form.
      */
     public void dump(Writer out) throws IOException {
-        out.write(rootElement.getCanonicalForm());
+        out.write(rootElement != null ? rootElement.getCanonicalForm() : "Unfinished template");
     }
 
     void addMacro(ASTDirMacro macro) {
@@ -730,143 +769,346 @@ public class Template extends MutableProcessingConfiguration<Template> implement
     }
 
     @Override
-    protected Locale getInheritedLocale() {
-        return getParent().getLocale();
+    public Locale getLocale() {
+        // TODO [FM3] Temporary hack; See comment above the locale field
+        if (lookupLocale != null) {
+            return lookupLocale;
+        }
+
+        return tCfg != null && tCfg.isLocaleSet() ? tCfg.getLocale() : cfg.getLocale();
+    }
+
+    // TODO [FM3] Temporary hack; See comment above the locale field
+    public void setLookupLocale(Locale lookupLocale) {
+        this.lookupLocale = lookupLocale;
+    }
+
+    @Override
+    public boolean isLocaleSet() {
+        return tCfg != null && tCfg.isLocaleSet();
+    }
+
+    @Override
+    public TimeZone getTimeZone() {
+        return tCfg != null && tCfg.isTimeZoneSet() ? tCfg.getTimeZone() : cfg.getTimeZone();
+    }
+
+    @Override
+    public boolean isTimeZoneSet() {
+        return tCfg != null && tCfg.isTimeZoneSet();
+    }
+
+    @Override
+    public TimeZone getSQLDateAndTimeTimeZone() {
+        return tCfg != null && tCfg.isSQLDateAndTimeTimeZoneSet() ? tCfg.getSQLDateAndTimeTimeZone() : cfg.getSQLDateAndTimeTimeZone();
+    }
+
+    @Override
+    public boolean isSQLDateAndTimeTimeZoneSet() {
+        return tCfg != null && tCfg.isSQLDateAndTimeTimeZoneSet();
+    }
+
+    @Override
+    public String getNumberFormat() {
+        return tCfg != null && tCfg.isNumberFormatSet() ? tCfg.getNumberFormat() : cfg.getNumberFormat();
     }
 
     @Override
-    protected TimeZone getInheritedTimeZone() {
-        return getParent().getTimeZone();
+    public boolean isNumberFormatSet() {
+        return tCfg != null && tCfg.isNumberFormatSet();
     }
 
     @Override
-    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
-        return getParent().getSQLDateAndTimeTimeZone();
+    public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
+        return tCfg != null && tCfg.isCustomNumberFormatsSet() ? tCfg.getCustomNumberFormats()
+                : cfg.getCustomNumberFormats();
     }
 
     @Override
-    protected String getInheritedNumberFormat() {
-        return getParent().getNumberFormat();
+    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+        if (tCfg != null && tCfg.isCustomNumberFormatsSet()) {
+            TemplateNumberFormatFactory value = tCfg.getCustomNumberFormats().get(name);
+            if (value != null) {
+                return value;
+            }
+        }
+        return cfg.getCustomNumberFormat(name);
+    }
+
+    @Override
+    public boolean isCustomNumberFormatsSet() {
+        return tCfg != null && tCfg.isCustomNumberFormatsSet();
+    }
+
+    @Override
+    public boolean hasCustomFormats() {
+        if (tCfg != null && tCfg.hasCustomFormats()) {
+            return true;
+        }
+        return cfg.hasCustomFormats();
+    }
+
+    @Override
+    public String getBooleanFormat() {
+        return tCfg != null && tCfg.isBooleanFormatSet() ? tCfg.getBooleanFormat() : cfg.getBooleanFormat();
+    }
+
+    @Override
+    public boolean isBooleanFormatSet() {
+        return tCfg != null && tCfg.isBooleanFormatSet();
+    }
+
+    @Override
+    public String getTimeFormat() {
+        return tCfg != null && tCfg.isTimeFormatSet() ? tCfg.getTimeFormat() : cfg.getTimeFormat();
+    }
+
+    @Override
+    public boolean isTimeFormatSet() {
+        return tCfg != null && tCfg.isTimeFormatSet();
+    }
+
+    @Override
+    public String getDateFormat() {
+        return tCfg != null && tCfg.isDateFormatSet() ? tCfg.getDateFormat() : cfg.getDateFormat();
+    }
+
+    @Override
+    public boolean isDateFormatSet() {
+        return tCfg != null && tCfg.isDateFormatSet();
+    }
+
+    @Override
+    public String getDateTimeFormat() {
+        return tCfg != null && tCfg.isDateTimeFormatSet() ? tCfg.getDateTimeFormat() : cfg.getDateTimeFormat();
+    }
+
+    @Override
+    public boolean isDateTimeFormatSet() {
+        return tCfg != null && tCfg.isDateTimeFormatSet();
+    }
+
+    @Override
+    public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
+        return tCfg != null && tCfg.isCustomDateFormatsSet() ? tCfg.getCustomDateFormats() : cfg.getCustomDateFormats();
+    }
+
+    @Override
+    public TemplateDateFormatFactory getCustomDateFormat(String name) {
+        if (tCfg != null && tCfg.isCustomDateFormatsSet()) {
+            TemplateDateFormatFactory value = tCfg.getCustomDateFormats().get(name);
+            if (value != null) {
+                return value;
+            }
+        }
+        return cfg.getCustomDateFormat(name);
     }
 
     @Override
-    protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
-        return getParent().getCustomNumberFormats();
+    public boolean isCustomDateFormatsSet() {
+        return tCfg != null && tCfg.isCustomDateFormatsSet();
     }
 
     @Override
-    protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
-        return getParent().getCustomNumberFormat(name);
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        return tCfg != null && tCfg.isTemplateExceptionHandlerSet() ? tCfg.getTemplateExceptionHandler() : cfg.getTemplateExceptionHandler();
     }
 
     @Override
-    protected boolean getInheritedHasCustomFormats() {
-        return getParent().hasCustomFormats();
+    public boolean isTemplateExceptionHandlerSet() {
+        return tCfg != null && tCfg.isTemplateExceptionHandlerSet();
     }
 
     @Override
-    protected String getInheritedBooleanFormat() {
-        return getParent().getBooleanFormat();
+    public ArithmeticEngine getArithmeticEngine() {
+        return tCfg != null && tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine();
     }
 
     @Override
-    protected String getInheritedTimeFormat() {
-        return getParent().getTimeFormat();
+    public boolean isArithmeticEngineSet() {
+        return tCfg != null && tCfg.isArithmeticEngineSet();
     }
 
     @Override
-    protected String getInheritedDateFormat() {
-        return getParent().getDateFormat();
+    public ObjectWrapper getObjectWrapper() {
+        return tCfg != null && tCfg.isObjectWrapperSet() ? tCfg.getObjectWrapper() : cfg.getObjectWrapper();
     }
 
     @Override
-    protected String getInheritedDateTimeFormat() {
-        return getParent().getDateTimeFormat();
+    public boolean isObjectWrapperSet() {
+        return tCfg != null && tCfg.isObjectWrapperSet();
     }
 
     @Override
-    protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
-        return getParent().getCustomDateFormats();
+    public Charset getOutputEncoding() {
+        return tCfg != null && tCfg.isOutputEncodingSet() ? tCfg.getOutputEncoding() : cfg.getOutputEncoding();
     }
 
     @Override
-    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
-        return getParent().getCustomDateFormat(name);
+    public boolean isOutputEncodingSet() {
+        return tCfg != null && tCfg.isOutputEncodingSet();
     }
 
     @Override
-    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
-        return getParent().getTemplateExceptionHandler();
+    public Charset getURLEscapingCharset() {
+        return tCfg != null && tCfg.isURLEscapingCharsetSet() ? tCfg.getURLEscapingCharset() : cfg.getURLEscapingCharset();
     }
 
     @Override
-    protected ArithmeticEngine getInheritedArithmeticEngine() {
-        return getParent().getArithmeticEngine();
+    public boolean isURLEscapingCharsetSet() {
+        return tCfg != null && tCfg.isURLEscapingCharsetSet();
     }
 
     @Override
-    protected ObjectWrapper getInheritedObjectWrapper() {
-        return getParent().getObjectWrapper();
+    public TemplateClassResolver getNewBuiltinClassResolver() {
+        return tCfg != null && tCfg.isNewBuiltinClassResolverSet() ? tCfg.getNewBuiltinClassResolver() : cfg.getNewBuiltinClassResolver();
     }
 
     @Override
-    protected Charset getInheritedOutputEncoding() {
-        return getParent().getOutputEncoding();
+    public boolean isNewBuiltinClassResolverSet() {
+        return tCfg != null && tCfg.isNewBuiltinClassResolverSet();
     }
 
     @Override
-    protected Charset getInheritedURLEscapingCharset() {
-        return getParent().getURLEscapingCharset();
+    public boolean getAPIBuiltinEnabled() {
+        return tCfg != null && tCfg.isAPIBuiltinEnabledSet() ? tCfg.getAPIBuiltinEnabled() : cfg.getAPIBuiltinEnabled();
     }
 
     @Override
-    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
-        return getParent().getNewBuiltinClassResolver();
+    public boolean isAPIBuiltinEnabledSet() {
+        return tCfg != null && tCfg.isAPIBuiltinEnabledSet();
     }
 
     @Override
-    protected boolean getInheritedAutoFlush() {
-        return getParent().getAutoFlush();
+    public boolean getAutoFlush() {
+        return tCfg != null && tCfg.isAutoFlushSet() ? tCfg.getAutoFlush() : cfg.getAutoFlush();
     }
 
     @Override
-    protected boolean getInheritedShowErrorTips() {
-        return getParent().getShowErrorTips();
+    public boolean isAutoFlushSet() {
+        return tCfg != null && tCfg.isAutoFlushSet();
     }
 
     @Override
-    protected boolean getInheritedAPIBuiltinEnabled() {
-        return getParent().getAPIBuiltinEnabled();
+    public boolean getShowErrorTips() {
+        return tCfg != null && tCfg.isShowErrorTipsSet() ? tCfg.getShowErrorTips() : cfg.getShowErrorTips();
     }
 
     @Override
-    protected boolean getInheritedLogTemplateExceptions() {
-        return getParent().getLogTemplateExceptions();
+    public boolean isShowErrorTipsSet() {
+        return tCfg != null && tCfg.isShowErrorTipsSet();
     }
 
     @Override
-    protected boolean getInheritedLazyImports() {
-        return getParent().getLazyImports();
+    public boolean getLogTemplateExceptions() {
+        return tCfg != null && tCfg.isLogTemplateExceptionsSet() ? tCfg.getLogTemplateExceptions() : cfg.getLogTemplateExceptions();
     }
 
     @Override
-    protected Boolean getInheritedLazyAutoImports() {
-        return getParent().getLazyAutoImports();
+    public boolean isLogTemplateExceptionsSet() {
+        return tCfg != null && tCfg.isLogTemplateExceptionsSet();
     }
 
     @Override
-    protected Map<String, String> getInheritedAutoImports() {
-        return getParent().getAutoImports();
+    public boolean getLazyImports() {
+        return tCfg != null && tCfg.isLazyImportsSet() ? tCfg.getLazyImports() : cfg.getLazyImports();
     }
 
     @Override
-    protected List<String> getInheritedAutoIncludes() {
-        return getParent().getAutoIncludes();
+    public boolean isLazyImportsSet() {
+        return tCfg != null && tCfg.isLazyImportsSet();
     }
 
     @Override
-    protected Object getInheritedCustomAttribute(Object name) {
-        return getParent().getCustomAttribute(name);
+    public Boolean getLazyAutoImports() {
+        return tCfg != null && tCfg.isLazyAutoImportsSet() ? tCfg.getLazyAutoImports() : cfg.getLazyAutoImports();
+    }
+
+    @Override
+    public boolean isLazyAutoImportsSet() {
+        return tCfg != null && tCfg.isLazyAutoImportsSet();
+    }
+
+    @Override
+    public Map<String, String> getAutoImports() {
+        return tCfg != null && tCfg.isAutoImportsSet() ? tCfg.getAutoImports() : cfg.getAutoImports();
+    }
+
+    @Override
+    public boolean isAutoImportsSet() {
+        return tCfg != null && tCfg.isAutoImportsSet();
+    }
+
+    @Override
+    public List<String> getAutoIncludes() {
+        return tCfg != null && tCfg.isAutoIncludesSet() ? tCfg.getAutoIncludes() : cfg.getAutoIncludes();
+    }
+
+    @Override
+    public boolean isAutoIncludesSet() {
+        return tCfg != null && tCfg.isAutoIncludesSet();
+    }
+
+    /**
+     * This exists to provide the functionality required by {@link ProcessingConfiguration}, but try not call it
+     * too frequently as it has some overhead compared to an usual getter.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public Map<Object, Object> getCustomAttributes() {
+        if (mergedCustomAttributes != null) {
+            return Collections.unmodifiableMap(mergedCustomAttributes);
+        } else if (customAttributes != null) {
+            return (Map) Collections.unmodifiableMap(customAttributes);
+        } else if (tCfg != null && tCfg.isCustomAttributesSet()) {
+            return tCfg.getCustomAttributes();
+        } else {
+            return cfg.getCustomAttributes();
+        }
+    }
+
+    @Override
+    public boolean isCustomAttributesSet() {
+        return customAttributes != null || tCfg != null && tCfg.isCustomAttributesSet();
+    }
+
+    @Override
+    public Object getCustomAttribute(Object name) {
+        // Extra step for custom attributes specified in the #ftl header:
+        if (mergedCustomAttributes != null) {
+            Object value = mergedCustomAttributes.get(name);
+            if (value != null || mergedCustomAttributes.containsKey(name)) {
+                return value;
+            }
+        } else if (customAttributes != null) {
+            Object value = customAttributes.get(name);
+            if (value != null || customAttributes.containsKey(name)) {
+                return value;
+            }
+        } else if (tCfg != null && tCfg.isCustomAttributesSet()) {
+            Object value = tCfg.getCustomAttributes().get(name);
+            if (value != null || tCfg.getCustomAttributes().containsKey(name)) {
+                return value;
+            }
+        }
+        return cfg.getCustomAttribute(name);
+    }
+
+    /**
+     * Should be called by the parser, for example to add the attributes specified in the #ftl header.
+     */
+    void setCustomAttribute(String attName, Serializable attValue) {
+        if (customAttributes == null) {
+            customAttributes = new LinkedHashMap<>();
+        }
+        customAttributes.put(attName, attValue);
+
+        if (tCfg != null && tCfg.isCustomAttributesSet()) {
+            if (mergedCustomAttributes == null) {
+                mergedCustomAttributes = new LinkedHashMap<>(tCfg.getCustomAttributes());
+            }
+            mergedCustomAttributes.put(attName, attValue);
+        }
     }
 
     /**


Mime
View raw message