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/11 20:05:15 UTC

[1/4] incubator-freemarker git commit: Made TemplateModel classes used by the parser for literals Serializable. (Without this attribute values set in the #ftl header wouldn't be always Serializable, which in turn will sabotage making Template-s Serializa

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 863a9d98c -> 0356b30b0


Made TemplateModel classes used by the parser for literals Serializable. (Without this attribute values set in the #ftl header wouldn't be always Serializable, which in turn will sabotage making Template-s Serializable in the future.)


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

Branch: refs/heads/3
Commit: 413c0e124ba141ff7c17e7790e7469de6e386498
Parents: 863a9d9
Author: ddekany <dd...@apache.org>
Authored: Thu Apr 6 23:27:45 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Apr 6 23:36:46 2017 +0200

----------------------------------------------------------------------
 src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java  | 3 ++-
 src/main/java/org/apache/freemarker/core/NativeHashEx2.java      | 3 ++-
 src/main/java/org/apache/freemarker/core/NativeSequence.java     | 3 ++-
 .../org/apache/freemarker/core/model/TemplateBooleanModel.java   | 4 +++-
 .../java/org/apache/freemarker/core/model/impl/SimpleDate.java   | 4 +++-
 5 files changed, 12 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/413c0e12/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java b/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
index 50f8851..57a5a6e 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -106,7 +107,7 @@ final class ASTExpHashLiteral extends ASTExpression {
     	return new ASTExpHashLiteral(clonedKeys, clonedValues);
     }
 
-    private class SequenceHash implements TemplateHashModelEx2 {
+    private class SequenceHash implements TemplateHashModelEx2, Serializable {
 
         private HashMap<String, TemplateModel> map;
         private TemplateCollectionModel keyCollection, valueCollection; // ordered lists of keys and values

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/413c0e12/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NativeHashEx2.java b/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
index 7290c2f..2850255 100644
--- a/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
+++ b/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core;
 
+import java.io.Serializable;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -36,7 +37,7 @@ import org.apache.freemarker.core.model.impl.SimpleScalar;
  * <p>While this class allows adding items, doing so is not thread-safe, and thus only meant to be done during the
  * initialization of the sequence.
  */
-class NativeHashEx2 implements TemplateHashModelEx2 {
+class NativeHashEx2 implements TemplateHashModelEx2, Serializable {
 
     private final LinkedHashMap<String, TemplateModel> map;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/413c0e12/src/main/java/org/apache/freemarker/core/NativeSequence.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NativeSequence.java b/src/main/java/org/apache/freemarker/core/NativeSequence.java
index c27971a..d1b6886 100644
--- a/src/main/java/org/apache/freemarker/core/NativeSequence.java
+++ b/src/main/java/org/apache/freemarker/core/NativeSequence.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -33,7 +34,7 @@ import org.apache.freemarker.core.model.TemplateSequenceModel;
  * <p>While this class allows adding items, doing so is not thread-safe, and thus only meant to be done during the
  * initialization of the sequence.
  */
-class NativeSequence implements TemplateSequenceModel {
+class NativeSequence implements TemplateSequenceModel, Serializable {
 
     private final ArrayList<TemplateModel> items;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/413c0e12/src/main/java/org/apache/freemarker/core/model/TemplateBooleanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/TemplateBooleanModel.java b/src/main/java/org/apache/freemarker/core/model/TemplateBooleanModel.java
index 381edf5..555e619 100644
--- a/src/main/java/org/apache/freemarker/core/model/TemplateBooleanModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/TemplateBooleanModel.java
@@ -19,6 +19,8 @@
 
 package org.apache.freemarker.core.model;
 
+import java.io.Serializable;
+
 /**
  * "boolean" template language data type; same as in Java; either {@code true} or {@code false}.
  * 
@@ -26,7 +28,7 @@ package org.apache.freemarker.core.model;
  * Objects of this type should be immutable, that is, calling {@link #getAsBoolean()} should always return the same
  * value as for the first time.
  */
-public interface TemplateBooleanModel extends TemplateModel {
+public interface TemplateBooleanModel extends TemplateModel, Serializable {
 
     /**
      * @return whether to interpret this object as true or false in a boolean context

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/413c0e12/src/main/java/org/apache/freemarker/core/model/impl/SimpleDate.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleDate.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleDate.java
index 38190f2..cf6e753 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleDate.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleDate.java
@@ -19,6 +19,8 @@
 
 package org.apache.freemarker.core.model.impl;
 
+import java.io.Serializable;
+
 import org.apache.freemarker.core.model.TemplateDateModel;
 
 /**
@@ -26,7 +28,7 @@ import org.apache.freemarker.core.model.TemplateDateModel;
  * interface. Note that this class is immutable.
  * <p>This class is thread-safe.
  */
-public class SimpleDate implements TemplateDateModel {
+public class SimpleDate implements TemplateDateModel, Serializable {
     private final java.util.Date date;
     private final int type;
     


[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

Posted by dd...@apache.org.
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 <dd...@apache.org>
Authored: Tue Apr 11 22:04:51 2017 +0200
Committer: ddekany <dd...@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);
+        }
     }
 
     /**


[2/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

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/OutputFormatTest.java b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
index 224e953..64fad5f 100644
--- a/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
@@ -276,23 +276,23 @@ public class OutputFormatTest extends TemplateTest {
             {
                 Template t = cfg.getTemplate("t");
                 if (cfgAutoEscaping) {
-                    assertTrue(t.getAutoEscaping());
+                    assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                     assertOutput(t, "a&amp;b");
                 } else {
-                    assertFalse(t.getAutoEscaping());
+                    assertEquals(Configuration.DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                     assertOutput(t, "a&b");
                 }
             }
             
             {
                 Template t = cfg.getTemplate("tWithHeaderFalse");
-                assertFalse(t.getAutoEscaping());
+                assertEquals(Configuration.DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                 assertOutput(t, "a&b");
             }
             
             {
                 Template t = cfg.getTemplate("tWithHeaderTrue");
-                assertTrue(t.getAutoEscaping());
+                assertEquals(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                 assertOutput(t, "a&amp;b");
             }
             

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
index 6f6b7f3..5d57e48 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.freemarker.core;
 
-import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.beans.BeanInfo;
@@ -60,7 +59,6 @@ import org.apache.freemarker.core.userpkg.EpochMillisTemplateDateFormatFactory;
 import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory;
 import org.apache.freemarker.core.userpkg.LocAndTZSensitiveTemplateDateFormatFactory;
 import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFactory;
-import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 import org.apache.freemarker.test.MonitoredTemplateLoader;
@@ -276,9 +274,9 @@ public class TemplateConfigurationTest {
     }
 
     private static final Object CA1 = new Object();
-    private static final Object CA2 = new Object();
-    private static final Object CA3 = new Object();
-    private static final Object CA4 = new Object();
+    private static final String CA2 = "ca2";
+    private static final String CA3 = "ca3";
+    private static final String CA4 = "ca4";
 
     @Test
     public void testMergeBasicFunctionality() throws Exception {
@@ -472,52 +470,6 @@ public class TemplateConfigurationTest {
     }
 
     @Test
-    public void applyOrder() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        
-        {
-            TemplateConfiguration.Builder  tcb = new TemplateConfiguration.Builder();
-            tcb.setBooleanFormat("Y,N");
-            tcb.setAutoImports(ImmutableMap.of("a", "a.ftl", "b", "b.ftl", "c", "c.ftl"));
-            tcb.setAutoIncludes(ImmutableList.of("i1.ftl", "i2.ftl", "i3.ftl"));
-            tcb.setCustomNumberFormats(ImmutableMap.of(
-                    "a", HexTemplateNumberFormatFactory.INSTANCE,
-                    "b", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE));
-
-            TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(cfg);
-            tc.apply(t);
-        }
-        assertEquals("Y,N", t.getBooleanFormat());
-        assertEquals(ImmutableMap.of("a", "a.ftl", "b", "b.ftl", "c", "c.ftl"), t.getAutoImports());
-        assertEquals(ImmutableList.of("a", "b", "c"), new ArrayList<>(t.getAutoImports().keySet()));
-        assertEquals(ImmutableList.of("i1.ftl", "i2.ftl", "i3.ftl"), t.getAutoIncludes());
-        
-        {
-            TemplateConfiguration.Builder  tcb = new TemplateConfiguration.Builder();
-            tcb.setBooleanFormat("J,N");
-            tcb.setAutoImports(ImmutableMap.of("b", "b2.ftl", "d", "d.ftl"));
-            tcb.setAutoIncludes(ImmutableList.of("i2.ftl", "i4.ftl"));
-            tcb.setCustomNumberFormats(ImmutableMap.of(
-                    "b", BaseNTemplateNumberFormatFactory.INSTANCE,
-                    "c", BaseNTemplateNumberFormatFactory.INSTANCE));
-            TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(cfg);
-            tc.apply(t);
-        }
-        assertEquals("Y,N", t.getBooleanFormat());
-        assertEquals(ImmutableMap.of("d", "d.ftl", "a", "a.ftl", "b", "b.ftl", "c", "c.ftl"), t.getAutoImports());
-        assertEquals(ImmutableList.of("d", "a", "b", "c"), new ArrayList<>(t.getAutoImports().keySet()));
-        assertEquals(ImmutableList.of("i4.ftl", "i1.ftl", "i2.ftl", "i3.ftl"), t.getAutoIncludes());
-        assertEquals(ImmutableMap.of( //
-                "b", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, //
-                "c", BaseNTemplateNumberFormatFactory.INSTANCE, //
-                "a", HexTemplateNumberFormatFactory.INSTANCE), //
-                t.getCustomNumberFormats());
-    }
-
-    @Test
     public void testConfigureNonParserConfig() throws Exception {
         for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(
                 TemplateConfiguration.Builder.class, false)) {
@@ -526,14 +478,16 @@ public class TemplateConfigurationTest {
             Object newValue = SETTING_ASSIGNMENTS.get(pd.getName());
             pd.getWriteMethod().invoke(tcb, newValue);
             
-            Template t = new Template(null, "", DEFAULT_CFG);
-            Method tReaderMethod = t.getClass().getMethod(pd.getReadMethod().getName());
-            
-            assertNotEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
-            tc.apply(t);
-            assertEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+
+            Method tReaderMethod = Template.class.getMethod(pd.getReadMethod().getName());
+
+            // Without TC
+            assertNotEquals("For \"" + pd.getName() + "\"", newValue,
+                    tReaderMethod.invoke(new Template(null, "", DEFAULT_CFG)));
+            // With TC
+            assertEquals("For \"" + pd.getName() + "\"", newValue,
+                    tReaderMethod.invoke(new Template(null, "", DEFAULT_CFG, tc)));
         }
     }
     
@@ -554,7 +508,8 @@ public class TemplateConfigurationTest {
         tcb.setCustomAttribute(CA2,"tc");
         tcb.setCustomAttribute(CA3,"tc");
 
-        Template t = new Template(null, "", cfg);
+        TemplateConfiguration tc = tcb.build();
+        Template t = new Template(null, "", cfg, tc);
         t.setCustomAttribute("k5", "t");
         t.setCustomAttribute("k6", null);
         t.setCustomAttribute("k7", "t");
@@ -562,10 +517,6 @@ public class TemplateConfigurationTest {
         t.setCustomAttribute(CA3, null);
         t.setCustomAttribute(CA4, "t");
 
-        TemplateConfiguration tc = tcb.build();
-        tc.setParentConfiguration(cfg);
-        tc.apply(t);
-        
         assertEquals("c", t.getCustomAttribute("k1"));
         assertEquals("tc", t.getCustomAttribute("k2"));
         assertNull(t.getCustomAttribute("k3"));
@@ -587,7 +538,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y");
             testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE);
         }
@@ -596,7 +546,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "<#if true>y<#elseif false>n</#if>", "y", null);
             testedProps.add(Configuration.NAMING_CONVENTION_KEY_CAMEL_CASE);
         }
@@ -605,7 +554,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setWhitespaceStripping(false);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "<#if true>\nx\n</#if>\n", "x\n", "\nx\n\n");
             testedProps.add(Configuration.WHITESPACE_STRIPPING_KEY_CAMEL_CASE);
         }
@@ -614,7 +562,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setArithmeticEngine(new DummyArithmeticEngine());
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "${1} ${1+1}", "1 2", "11 22");
             testedProps.add(Configuration.ARITHMETIC_ENGINE_KEY_CAMEL_CASE);
         }
@@ -623,7 +570,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setOutputFormat(XMLOutputFormat.INSTANCE);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "${.outputFormat} ${\"a'b\"}",
                     UndefinedOutputFormat.INSTANCE.getName() + " a'b",
                     XMLOutputFormat.INSTANCE.getName() + " a&apos;b");
@@ -635,7 +581,6 @@ public class TemplateConfigurationTest {
             tcb.setOutputFormat(XMLOutputFormat.INSTANCE);
             tcb.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "${'a&b'}", "a&b", "a&b");
             testedProps.add(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE);
         }
@@ -654,7 +599,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setRecognizeStandardFileExtensions(false);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc, "adhoc.ftlh", "${.outputFormat}",
                     HTMLOutputFormat.INSTANCE.getName(), UndefinedOutputFormat.INSTANCE.getName());
             testedProps.add(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE);
@@ -665,7 +609,6 @@ public class TemplateConfigurationTest {
             tcb.setLogTemplateExceptions(false);
             tcb.setTabSize(3);
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc,
                     "<#attempt><@'\\t$\\{1+}'?interpret/><#recover>"
                     + "${.error?replace('(?s).*?column ([0-9]+).*', '$1', 'r')}"
@@ -744,17 +687,16 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testArithmeticEngine() throws TemplateException, IOException {
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setArithmeticEngine(new DummyArithmeticEngine());
-        TemplateConfiguration tc = tcb.build();
-        tc.setParentConfiguration(DEFAULT_CFG);
+        TemplateConfiguration tc = new TemplateConfiguration.Builder()
+                .arithmeticEngine(new DummyArithmeticEngine())
+                .build();
         assertOutputWithoutAndWithTC(tc,
                 "<#setting locale='en_US'>${1} ${1+1} ${1*3} <#assign x = 1>${x + x} ${x * 3}",
                 "1 2 3 2 3", "11 22 33 22 33");
         
-        // Doesn't affect template.arithmeticEngine, only affects the parsing:
+        // Does affect template.arithmeticEngine (unlike in FM2)
         Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null);
-        assertEquals(DEFAULT_CFG.getArithmeticEngine(), t.getArithmeticEngine());
+        assertEquals(tc.getArithmeticEngine(), t.getArithmeticEngine());
     }
 
     @Test
@@ -762,7 +704,6 @@ public class TemplateConfigurationTest {
         TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
         tcb.setAutoImports(ImmutableMap.of("t1", "t1.ftl", "t2", "t2.ftl"));
         TemplateConfiguration tc = tcb.build();
-        tc.setParentConfiguration(DEFAULT_CFG);
         assertOutputWithoutAndWithTC(tc, "<#import 't3.ftl' as t3>${loaded}", "t3;", "t1;t2;t3;");
     }
 
@@ -771,23 +712,21 @@ public class TemplateConfigurationTest {
         TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
         tcb.setAutoIncludes(ImmutableList.of("t1.ftl", "t2.ftl"));
         TemplateConfiguration tc = tcb.build();
-        tc.setParentConfiguration(DEFAULT_CFG);
         assertOutputWithoutAndWithTC(tc, "<#include 't3.ftl'>", "In t3;", "In t1;In t2;In t3;");
     }
     
     @Test
     public void testStringInterpolate() throws TemplateException, IOException {
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setArithmeticEngine(new DummyArithmeticEngine());
-        TemplateConfiguration tc = tcb.build();
-        tc.setParentConfiguration(DEFAULT_CFG);
+        TemplateConfiguration tc = new TemplateConfiguration.Builder()
+                .arithmeticEngine(new DummyArithmeticEngine())
+                .build();
         assertOutputWithoutAndWithTC(tc,
                 "<#setting locale='en_US'>${'${1} ${1+1} ${1*3}'} <#assign x = 1>${'${x + x} ${x * 3}'}",
                 "1 2 3 2 3", "11 22 33 22 33");
         
-        // Doesn't affect template.arithmeticEngine, only affects the parsing:
+        // Does affect template.arithmeticEngine (unlike in FM2):
         Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null);
-        assertEquals(DEFAULT_CFG.getArithmeticEngine(), t.getArithmeticEngine());
+        assertEquals(tc.getArithmeticEngine(), t.getArithmeticEngine());
     }
     
     @Test
@@ -796,7 +735,6 @@ public class TemplateConfigurationTest {
         tcb.setArithmeticEngine(new DummyArithmeticEngine());
         {
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc,
                     "<#setting locale='en_US'><#assign src = r'${1} <#assign x = 1>${x + x}'><@src?interpret />",
                     "1 2", "11 22");
@@ -804,7 +742,6 @@ public class TemplateConfigurationTest {
         tcb.setWhitespaceStripping(false);
         {
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc,
                     "<#if true>\nX</#if><#assign src = r'<#if true>\nY</#if>'><@src?interpret />",
                     "XY", "\nX\nY");
@@ -817,7 +754,6 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setArithmeticEngine(new DummyArithmeticEngine());
             TemplateConfiguration tc = tcb.build();
-            tc.setParentConfiguration(DEFAULT_CFG);
             assertOutputWithoutAndWithTC(tc,
                     "<#assign x = 1>${r'1 + x'?eval?c}",
                     "2", "22");
@@ -836,7 +772,6 @@ public class TemplateConfigurationTest {
 
             {
                 TemplateConfiguration tc = tcb.build();
-                tc.setParentConfiguration(DEFAULT_CFG);
 
                 // Default is re-auto-detecting in ?eval:
                 assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name());
@@ -848,7 +783,6 @@ public class TemplateConfigurationTest {
                 tcb.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
 
                 TemplateConfiguration tc = tcb.build();
-                tc.setParentConfiguration(DEFAULT_CFG);
 
                 assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", null);
                 assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name());
@@ -859,7 +793,6 @@ public class TemplateConfigurationTest {
                 tcb.setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
 
                 TemplateConfiguration tc = tcb.build();
-                tc.setParentConfiguration(DEFAULT_CFG);
 
                 assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name());
                 assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", null);
@@ -867,40 +800,6 @@ public class TemplateConfigurationTest {
         }
     }
     
-    @Test
-    public void testSetParentConfiguration() throws IOException {
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        TemplateConfiguration tc = tcb.build();
-
-        Template t = new Template(null, "", DEFAULT_CFG);
-        try {
-            tc.apply(t);
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), containsString("Configuration"));
-        }
-        
-        tc.setParentConfiguration(DEFAULT_CFG);
-        
-        try {
-            tc.setParentConfiguration(new Configuration());
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), containsString("Configuration"));
-        }
-
-        try {
-            tc.setParentConfiguration(null);
-            fail();
-        } catch (_NullArgumentException e) {
-            // expected
-        }
-        
-        tc.setParentConfiguration(DEFAULT_CFG);
-        
-        tc.apply(t);
-    }
-
     private void assertOutputWithoutAndWithTC(
             TemplateConfiguration tc, String ftl, String expectedDefaultOutput,
             String expectedConfiguredOutput) throws TemplateException, IOException {
@@ -917,24 +816,17 @@ public class TemplateConfigurationTest {
         assertOutput(tc, templateName, ftl, expectedConfiguredOutput);
     }
 
-    private void assertOutput(TemplateConfiguration tc, String templateName, String ftl, String expectedConfiguredOutput)
+    private void assertOutput(TemplateConfiguration tc, String templateName, String ftl, String
+            expectedConfiguredOutput)
             throws TemplateException, IOException {
         StringWriter sw = new StringWriter();
         try {
-            Configuration cfg = tc != null ? tc.getParentConfiguration() : DEFAULT_CFG;
-            Template t = new Template(templateName, null, new StringReader(ftl), cfg, tc, null);
-            if (tc != null) {
-                tc.apply(t);
-            }
+            Template t = new Template(templateName, null, new StringReader(ftl), DEFAULT_CFG, tc, null);
             t.process(null, sw);
             if (expectedConfiguredOutput == null) {
                 fail("Template should have fail.");
             }
-        } catch (TemplateException e) {
-            if (expectedConfiguredOutput != null) {
-                throw e;
-            }
-        } catch (ParseException e) {
+        } catch (TemplateException|ParseException e) {
             if (expectedConfiguredOutput != null) {
                 throw e;
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/TemplateLevelSettings.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateLevelSettings.java b/src/test/java/org/apache/freemarker/core/TemplateLevelSettings.java
deleted file mode 100644
index 178e518..0000000
--- a/src/test/java/org/apache/freemarker/core/TemplateLevelSettings.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.io.StringWriter;
-
-import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
-import org.apache.freemarker.core.templateresolver.impl.StrongCacheStorage;
-import org.junit.Test;
-
-public class TemplateLevelSettings {
-    
-    private static final String IMPORTED_FTL = "imported.ftl";
-    private static final String INCLUDED_FTL = "included.ftl";
-    private static final String MAIN_FTL = "main.ftl";
-    private static final StringTemplateLoader TEMPLATES = new StringTemplateLoader();
-    static {
-        TEMPLATES.putTemplate(MAIN_FTL,
-                "${true}<#include '" + INCLUDED_FTL + "'>"
-                + "${true}<#import '" + IMPORTED_FTL + "' as ns>"
-                + "${true}<@ns.impM1>${true}</@>"
-                + "${true}<@incM>${true}</@>"
-                + "${true}");
-        TEMPLATES.putTemplate(INCLUDED_FTL,
-                "[inc:${true}]"
-                + "<#macro incM>[incM:${true}{<#nested>}${true}]</#macro>");
-        TEMPLATES.putTemplate(IMPORTED_FTL,
-                "<#macro impM1>[impM1:${true}{<#n...@impM2>${true}</@>${true}]</#macro>"
-                + "<#macro impM2>[impM2:${true}{<#nested>}${true}]</#macro>"
-                );
-    }
-    
-    @Test
-    public void test() throws IOException, TemplateException {
-        assertOutputs(
-                "M[inc:M]MM[impM1:M{M}M[impM2:M{M}M]M]M[incM:M{M}M]M",
-                "M,m", "INC,inc", "IMP,imp");
-        assertOutputs(
-                "C[inc:C]CC[impM1:C{C}C[impM2:C{C}C]C]C[incM:C{C}C]C",
-                null, "INC,inc", "IMP,imp");
-        assertOutputs(
-                "M[inc:M]MM[impM1:M{M}M[impM2:M{M}M]M]M[incM:M{M}M]M",
-                "M,m", null, "IMP,imp");
-        assertOutputs(
-                "M[inc:M]MM[impM1:M{M}M[impM2:M{M}M]M]M[incM:M{M}M]M",
-                "M,m", "INC,inc", null);
-    }
-
-    private void assertOutputs(
-            String expectedOutput,
-            String mainBoolFmt, String incBoolFmt, String impBoolFtm)
-            throws IOException, TemplateException {
-        assertEquals(
-                expectedOutput,
-                renderWith(Configuration.VERSION_3_0_0, mainBoolFmt, incBoolFmt, impBoolFtm));
-    }
-    
-    private String renderWith(Version version, String mainBoolFmt, String incBoolFmt, String impBoolFtm)
-            throws IOException, TemplateException {
-        Configuration cfg = new Configuration(version);
-        cfg.setTemplateLoader(TEMPLATES);
-        cfg.setCacheStorage(new StrongCacheStorage());
-        cfg.setBooleanFormat("C,c");
-        
-        if (incBoolFmt != null) {
-            cfg.getTemplate(INCLUDED_FTL).setBooleanFormat(incBoolFmt);
-        }
-        
-        if (impBoolFtm != null) {
-            cfg.getTemplate(IMPORTED_FTL).setBooleanFormat(impBoolFtm);
-        }
-        
-        Template t = cfg.getTemplate(MAIN_FTL);
-        if (mainBoolFmt != null) {
-            t.setBooleanFormat(mainBoolFmt);
-        }
-        
-        StringWriter sw = new StringWriter();
-        t.process(null, sw);
-        return sw.toString();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
index 3a416d9..727faa1 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
@@ -38,8 +38,7 @@ public class TemplateConfigurationFactoryTest {
         TemplateConfiguration tc = newTemplateConfiguration(1);
         
         TemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.ftlx"), tc);
-        tcf.setConfiguration(cfg);
-        
+
         assertNotApplicable(tcf, "x.ftl");
         assertApplicable(tcf, "x.ftlx", tc);
     }
@@ -52,8 +51,7 @@ public class TemplateConfigurationFactoryTest {
                 new FileNameGlobMatcher("*.ftlx"),
                 new ConditionalTemplateConfigurationFactory(
                         new FileNameGlobMatcher("x.*"), tc));
-        tcf.setConfiguration(cfg);
-        
+
         assertNotApplicable(tcf, "x.ftl");
         assertNotApplicable(tcf, "y.ftlx");
         assertApplicable(tcf, "x.ftlx", tc);
@@ -69,8 +67,7 @@ public class TemplateConfigurationFactoryTest {
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*a*.*"), tc2),
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*b*.*"), tc3));
-        tcf.setConfiguration(cfg);
-        
+
         assertNotApplicable(tcf, "x.ftl");
         assertApplicable(tcf, "x.ftlx", tc1);
         assertApplicable(tcf, "a.ftl", tc2);
@@ -93,7 +90,6 @@ public class TemplateConfigurationFactoryTest {
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*a*.*"), tc2),
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*b*.*"), tc3));
-        tcf.setConfiguration(cfg);
 
         try {
             assertNotApplicable(tcf, "x.ftl");
@@ -145,8 +141,7 @@ public class TemplateConfigurationFactoryTest {
                         new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.*x"), tcXml))
                         .allowNoMatch(true),
                 new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.nws.*"), tcNWS));
-        tcf.setConfiguration(cfg);
-        
+
         assertNotApplicable(tcf, "x.ftl");
         assertApplicable(tcf, "b/x.ftl", tcBCommon);
         assertApplicable(tcf, "b/x.s.ftl", tcBCommon, tcBSpec);
@@ -157,29 +152,6 @@ public class TemplateConfigurationFactoryTest {
         assertApplicable(tcf, "a.nws.hh", tcHH, tcNWS);
     }
 
-    @Test
-    public void testSetConfiguration() {
-        TemplateConfiguration tc = new TemplateConfiguration.Builder().build();
-        ConditionalTemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(
-                new FileNameGlobMatcher("*"), tc);
-        assertNull(tcf.getConfiguration());
-        assertNull(tc.getParentConfiguration());
-        
-        tcf.setConfiguration(cfg);
-        assertEquals(cfg, tcf.getConfiguration());
-        assertEquals(cfg, tc.getParentConfiguration());
-        
-        // Ignored:
-        tcf.setConfiguration(cfg);
-        
-        try {
-            tcf.setConfiguration(new Configuration(Configuration.VERSION_3_0_0));
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), containsString("TemplateConfigurationFactory"));
-        }
-    }
-
     @SuppressWarnings("boxing")
     private TemplateConfiguration newTemplateConfiguration(int id) {
         TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
@@ -196,7 +168,6 @@ public class TemplateConfigurationFactoryTest {
     private void assertApplicable(TemplateConfigurationFactory tcf, String sourceName, TemplateConfiguration... expectedTCs)
             throws IOException, TemplateConfigurationFactoryException {
         TemplateConfiguration mergedTC = tcf.get(sourceName, DummyTemplateLoadingSource.INSTANCE);
-        assertNotNull("TC should have its parents Configuration set", mergedTC.getParentConfiguration());
         List<Object> mergedTCAttNames = new ArrayList<Object>(mergedTC.getCustomAttributes().keySet());
 
         for (TemplateConfiguration expectedTC : expectedTCs) {


[3/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

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
index e1fa479..f2a5fc6 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -30,57 +30,28 @@ import java.util.TimeZone;
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
-import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
 import org.apache.freemarker.core.util.CommonBuilder;
-import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
 /**
- * Used for customizing the configuration settings for individual {@link Template}-s (or rather groups of templates),
- * relatively to the common setting values coming from the {@link Configuration}. This was designed with the standard
- * template loading mechanism of FreeMarker in mind ({@link Configuration#getTemplate(String)}
- * and {@link DefaultTemplateResolver}), though can also be reused for custom template loading and caching solutions.
- * 
+ * A partial set of configuration settings used for customizing the {@link Configuration}-level settings for individual
+ * {@link Template}-s (or rather, for a group of templates). That it's partial means that you should call the
+ * corresponding {@code isXxxSet()} before getting a settings, or else you may cause
+ * {@link SettingValueNotSetException}. (The fallback to the {@link Configuration} setting isn't automatic to keep
+ * the dependency graph of configuration related beans non-cyclic. As user code seldom reads settings from here anyway,
+ * this compromise was chosen.)
  * <p>
- * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism (
- * {@link Configuration#getTemplate(String)} and its overloads), localized lookup happens before the {@code locale}
- * specified here could have effect. The {@code locale} will be only set in the template that the localized lookup has
- * already found.
- * 
+ * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism ({@link
+ * Configuration#getTemplate(String)} and its overloads), localized lookup happens before the {@code locale} specified
+ * here could have effect. The {@code locale} will be only set in the template that the localized lookup has already
+ * found.
  * <p>
- * Note on the sourceEncoding setting {@code sourceEncoding}: See {@link Builder#setSourceEncoding(Charset)}.
- * 
- * <p>
- * Note that the result value of the reader methods (getter and "is" methods) is usually not useful unless the value of
- * that setting was already set on this object. Otherwise you will get the value from the parent {@link Configuration},
- * or an {@link IllegalStateException} before this object is associated to a {@link Configuration}.
- * 
- * <p>
- * If you are using this class for your own template loading and caching solution, rather than with the standard one,
- * you should be aware of a few more details:
- * 
- * <ul>
- * <li>This class implements both {@link MutableProcessingConfiguration} and {@link ParserConfiguration}. This means that it can influence
- * both the template parsing phase and the runtime settings. For both aspects (i.e., {@link ParserConfiguration} and
- * {@link MutableProcessingConfiguration}) to take effect, you have first pass this object to the {@link Template} constructor
- * (this is where the {@link ParserConfiguration} interface is used), and then you have to call {@link #apply(Template)}
- * on the resulting {@link Template} object (this is where the {@link MutableProcessingConfiguration} aspect is used).
- * 
- * <li>{@link #apply(Template)} only change the settings that weren't yet set on the {@link Template} (but are inherited
- * from the {@link Configuration}). This is primarily because if the template configures itself via the {@code #ftl}
- * header, those values should have precedence. A consequence of this is that if you want to configure the same
- * {@link Template} with multiple {@link TemplateConfiguration}-s, you either should merge them to a single one before
- * that (with {@link Builder#merge(ParserAndProcessingConfiguration)}), or you have to apply them in reverse order of
- * their intended precedence.
- * </ul>
- * 
- * @see Template#Template(String, String, Reader, Configuration, ParserConfiguration, Charset)
- * 
- * @since 2.3.24
+ * This class is immutable. Use {@link TemplateConfiguration.Builder} to create a new instance.
+ *
+ * @see Template#Template(String, String, Reader, Configuration, TemplateConfiguration, Charset)
  */
 public final class TemplateConfiguration implements ParserAndProcessingConfiguration {
-    private Configuration configuration;
 
     private final Locale locale;
     private final String numberFormat;
@@ -164,162 +135,6 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
         tabSize = builder.isTabSizeSet() ? builder.getTabSize() : null;
     }
 
-    /**
-     * Associates this instance with a {@link Configuration}; usually you don't call this, as it's called internally
-     * when this instance is added to a {@link Configuration}. This method can be called only once (except with the same
-     * {@link Configuration} parameter again, as that changes nothing anyway).
-     *
-     * @throws IllegalArgumentException
-     *             if the argument is {@code null} or not a {@link Configuration}
-     * @throws IllegalStateException
-     *             if this object is already associated to a different {@link Configuration} object,
-     *             or if the {@code Configuration} has {@code #getIncompatibleImprovements()} less than 2.3.22 and
-     *             this object tries to change any non-parser settings
-     */
-    public void setParentConfiguration(Configuration configuration) {
-        _NullArgumentException.check(configuration);
-        synchronized (this) {
-            if (this.configuration != null && this.configuration != configuration) {
-                throw new IllegalStateException(
-                        "This TemplateConfiguration was already associated to another Configuration");
-            }
-            this.configuration = configuration;
-        }
-    }
-
-    /**
-     * Returns the parent {@link Configuration}, or {@code null} if none was associated yet.
-     */
-    public Configuration getParentConfiguration() {
-        return configuration;
-    }
-
-    private Configuration getNonNullParentConfiguration() {
-        if (configuration == null) {
-            throw new IllegalStateException("The TemplateConfiguration wasn't associated with a Configuration yet.");
-        }
-        return configuration;
-    }
-
-    /**
-     * Sets those settings of the {@link Template} which aren't yet set in the {@link Template} and are set in this
-     * {@link TemplateConfiguration}, leaves the other settings as is. A setting is said to be set in a
-     * {@link TemplateConfiguration} or {@link Template} if it was explicitly set via a setter method on that object, as
-     * opposed to be inherited from the {@link Configuration}.
-     * 
-     * <p>
-     * Note that this method doesn't deal with settings that influence the parser, as those are already baked in at this
-     * point via the {@link ParserConfiguration}. 
-     * 
-     * <p>
-     * Note that the {@code sourceEncoding} setting of the {@link Template} counts as unset if it's {@code null},
-     * even if {@code null} was set via {@link Template#setActualSourceEncoding(Charset)}.
-     *
-     * @throws IllegalStateException
-     *             If the parent configuration wasn't yet set.
-     */
-    public void apply(Template template) {
-        Configuration cfg = getNonNullParentConfiguration();
-        if (template.getConfiguration() != cfg) {
-            // This is actually not a problem right now, but for future BC we enforce this.
-            throw new IllegalArgumentException(
-                    "The argument Template doesn't belong to the same Configuration as the TemplateConfiguration");
-        }
-
-        if (isAPIBuiltinEnabledSet() && !template.isAPIBuiltinEnabledSet()) {
-            template.setAPIBuiltinEnabled(getAPIBuiltinEnabled());
-        }
-        if (isArithmeticEngineSet() && !template.isArithmeticEngineSet()) {
-            template.setArithmeticEngine(getArithmeticEngine());
-        }
-        if (isAutoFlushSet() && !template.isAutoFlushSet()) {
-            template.setAutoFlush(getAutoFlush());
-        }
-        if (isBooleanFormatSet() && !template.isBooleanFormatSet()) {
-            template.setBooleanFormat(getBooleanFormat());
-        }
-        if (isCustomDateFormatsSet()) {
-            template.setCustomDateFormats(
-                    mergeMaps(
-                            getCustomDateFormats(),
-                            template.isCustomDateFormatsSet() ? template.getCustomDateFormats() : null,
-                            false));
-        }
-        if (isCustomNumberFormatsSet()) {
-            template.setCustomNumberFormats(
-                    mergeMaps(
-                            getCustomNumberFormats(),
-                            template.isCustomNumberFormatsSet() ? template.getCustomNumberFormats() : null,
-                            false));
-        }
-        if (isDateFormatSet() && !template.isDateFormatSet()) {
-            template.setDateFormat(getDateFormat());
-        }
-        if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) {
-            template.setDateTimeFormat(getDateTimeFormat());
-        }
-        if (isLocaleSet() && !template.isLocaleSet()) {
-            template.setLocale(getLocale());
-        }
-        if (isLogTemplateExceptionsSet() && !template.isLogTemplateExceptionsSet()) {
-            template.setLogTemplateExceptions(getLogTemplateExceptions());
-        }
-        if (isNewBuiltinClassResolverSet() && !template.isNewBuiltinClassResolverSet()) {
-            template.setNewBuiltinClassResolver(getNewBuiltinClassResolver());
-        }
-        if (isNumberFormatSet() && !template.isNumberFormatSet()) {
-            template.setNumberFormat(getNumberFormat());
-        }
-        if (isObjectWrapperSet() && !template.isObjectWrapperSet()) {
-            template.setObjectWrapper(getObjectWrapper());
-        }
-        if (isOutputEncodingSet() && !template.isOutputEncodingSet()) {
-            template.setOutputEncoding(getOutputEncoding());
-        }
-        if (isShowErrorTipsSet() && !template.isShowErrorTipsSet()) {
-            template.setShowErrorTips(getShowErrorTips());
-        }
-        if (isSQLDateAndTimeTimeZoneSet() && !template.isSQLDateAndTimeTimeZoneSet()) {
-            template.setSQLDateAndTimeTimeZone(getSQLDateAndTimeTimeZone());
-        }
-        if (isTemplateExceptionHandlerSet() && !template.isTemplateExceptionHandlerSet()) {
-            template.setTemplateExceptionHandler(getTemplateExceptionHandler());
-        }
-        if (isTimeFormatSet() && !template.isTimeFormatSet()) {
-            template.setTimeFormat(getTimeFormat());
-        }
-        if (isTimeZoneSet() && !template.isTimeZoneSet()) {
-            template.setTimeZone(getTimeZone());
-        }
-        if (isURLEscapingCharsetSet() && !template.isURLEscapingCharsetSet()) {
-            template.setURLEscapingCharset(getURLEscapingCharset());
-        }
-        if (isLazyImportsSet() && !template.isLazyImportsSet()) {
-            template.setLazyImports(getLazyImports());
-        }
-        if (isLazyAutoImportsSet() && !template.isLazyAutoImportsSet()) {
-            template.setLazyAutoImports(getLazyAutoImports());
-        }
-        if (isAutoImportsSet()) {
-            // Regarding the order of the maps in the merge:
-            // - Existing template-level imports have precedence over those coming from the TC (just as with the others
-            //   apply()-ed settings), thus for clashing import prefixes they must win.
-            // - Template-level imports count as more specific, and so come after the more generic ones from TC.
-            template.setAutoImports(mergeMaps(
-                    getAutoImports(),
-                    template.isAutoImportsSet() ? template.getAutoImports() : null,
-                    true));
-        }
-        if (isAutoIncludesSet()) {
-            template.setAutoIncludes(mergeLists(
-                    getAutoIncludes(),
-                    template.isAutoIncludesSet() ? template.getAutoIncludes() : null));
-        }
-        
-        copyDirectCustomAttributes(template, false);
-
-    }
-
     private static <K,V> Map<K,V> mergeMaps(Map<K,V> m1, Map<K,V> m2, boolean overwriteUpdatesOrder) {
         if (m1 == null) return m2;
         if (m2 == null) return m1;
@@ -371,7 +186,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public int getTagSyntax() {
-        return tagSyntax != null ? tagSyntax : getNonNullParentConfiguration().getTagSyntax();
+        if (!isTagSyntaxSet()) {
+            throw new SettingValueNotSetException("tagSyntax");
+        }
+        return tagSyntax;
     }
 
     @Override
@@ -381,7 +199,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TemplateLanguage getTemplateLanguage() {
-        return templateLanguage != null ? templateLanguage : getNonNullParentConfiguration().getTemplateLanguage();
+        if (!isTemplateLanguageSet()) {
+            throw new SettingValueNotSetException("templateLanguage");
+        }
+        return templateLanguage;
     }
 
     @Override
@@ -391,8 +212,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public int getNamingConvention() {
-        return namingConvention != null ? namingConvention
-                : getNonNullParentConfiguration().getNamingConvention();
+        if (!isNamingConventionSet()) {
+            throw new SettingValueNotSetException("namingConvention");
+        }
+        return namingConvention;
     }
 
     @Override
@@ -402,8 +225,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getWhitespaceStripping() {
-        return whitespaceStripping != null ? whitespaceStripping
-                : getNonNullParentConfiguration().getWhitespaceStripping();
+        if (!isWhitespaceStrippingSet()) {
+            throw new SettingValueNotSetException("whitespaceStripping");
+        }
+        return whitespaceStripping;
     }
 
     @Override
@@ -413,8 +238,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy
-                : getNonNullParentConfiguration().getAutoEscapingPolicy();
+        if (!isAutoEscapingPolicySet()) {
+            throw new SettingValueNotSetException("autoEscapingPolicy");
+        }
+        return autoEscapingPolicy;
     }
 
     @Override
@@ -424,12 +251,18 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public OutputFormat getOutputFormat() {
-        return outputFormat != null ? outputFormat : getNonNullParentConfiguration().getOutputFormat();
+        if (!isOutputFormatSet()) {
+            throw new SettingValueNotSetException("outputFormat");
+        }
+        return outputFormat;
     }
 
     @Override
     public ArithmeticEngine getArithmeticEngine() {
-        return isArithmeticEngineSet() ? arithmeticEngine : getNonNullParentConfiguration().getArithmeticEngine();
+        if (!isArithmeticEngineSet()) {
+            throw new SettingValueNotSetException("arithmeticEngine");
+        }
+        return arithmeticEngine;
     }
 
     @Override
@@ -444,8 +277,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
     
     @Override
     public boolean getRecognizeStandardFileExtensions() {
-        return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions
-                : getNonNullParentConfiguration().getRecognizeStandardFileExtensions();
+        if (!isRecognizeStandardFileExtensionsSet()) {
+            throw new SettingValueNotSetException("recognizeStandardFileExtensions");
+        }
+        return recognizeStandardFileExtensions;
     }
     
     @Override
@@ -455,7 +290,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Charset getSourceEncoding() {
-        return isSourceEncodingSet() ? sourceEncoding : getNonNullParentConfiguration().getSourceEncoding();
+        if (!isSourceEncodingSet()) {
+            throw new SettingValueNotSetException("sourceEncoding");
+        }
+        return sourceEncoding;
     }
 
     @Override
@@ -465,8 +303,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
     
     @Override
     public int getTabSize() {
-        return isTabSizeSet() ? tabSize
-                : getNonNullParentConfiguration().getTabSize();
+        if (!isTabSizeSet()) {
+            throw new SettingValueNotSetException("tabSize");
+        }
+        return tabSize;
     }
     
     @Override
@@ -475,20 +315,20 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
     }
     
     /**
-     * Returns {@link Configuration#getIncompatibleImprovements()} from the parent {@link Configuration}. This mostly
-     * just exist to satisfy the {@link ParserConfiguration} interface.
-     * 
-     * @throws IllegalStateException
-     *             If the parent configuration wasn't yet set.
+     * Always throws {@link SettingValueNotSetException}, as this can't be set on the {@link TemplateConfiguration}
+     * level.
      */
     @Override
     public Version getIncompatibleImprovements() {
-        return getNonNullParentConfiguration().getIncompatibleImprovements();
+        throw new SettingValueNotSetException("incompatibleImprovements");
     }
 
     @Override
     public Locale getLocale() {
-        return isLocaleSet() ? locale : getNonNullParentConfiguration().getLocale();
+        if (!isLocaleSet()) {
+            throw new SettingValueNotSetException("locale");
+        }
+        return locale;
     }
 
     @Override
@@ -498,7 +338,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TimeZone getTimeZone() {
-        return isTimeZoneSet() ? timeZone : getNonNullParentConfiguration().getTimeZone();
+        if (!isTimeZoneSet()) {
+            throw new SettingValueNotSetException("timeZone");
+        }
+        return timeZone;
     }
 
     @Override
@@ -508,8 +351,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TimeZone getSQLDateAndTimeTimeZone() {
-        return isSQLDateAndTimeTimeZoneSet() ? sqlDateAndTimeTimeZone : getNonNullParentConfiguration()
-                .getSQLDateAndTimeTimeZone();
+        if (!isSQLDateAndTimeTimeZoneSet()) {
+            throw new SettingValueNotSetException("sqlDateAndTimeTimeZone");
+        }
+        return sqlDateAndTimeTimeZone;
     }
 
     @Override
@@ -519,7 +364,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getNumberFormat() {
-        return isNumberFormatSet() ? numberFormat : getNonNullParentConfiguration().getNumberFormat();
+        if (!isNumberFormatSet()) {
+            throw new SettingValueNotSetException("numberFormat");
+        }
+        return numberFormat;
     }
 
     @Override
@@ -529,18 +377,15 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
-        return isCustomNumberFormatsSet() ? customNumberFormats : getNonNullParentConfiguration().getCustomNumberFormats();
+        if (!isCustomNumberFormatsSet()) {
+            throw new SettingValueNotSetException("customNumberFormats");
+        }
+        return customNumberFormats;
     }
 
     @Override
     public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
-        if (isCustomNumberFormatsSet()) {
-            TemplateNumberFormatFactory format = customNumberFormats.get(name);
-            if (format != null) {
-                return  format;
-            }
-        }
-        return getNonNullParentConfiguration().getCustomNumberFormat(name);
+        return getCustomNumberFormats().get(name);
     }
 
     @Override
@@ -550,13 +395,16 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean hasCustomFormats() {
-        return isCustomNumberFormatsSet() ? !customNumberFormats.isEmpty()
-                : getNonNullParentConfiguration().hasCustomFormats();
+        return isCustomNumberFormatsSet() && !customNumberFormats.isEmpty()
+                || isCustomDateFormatsSet() && !customDateFormats.isEmpty();
     }
 
     @Override
     public String getBooleanFormat() {
-        return isBooleanFormatSet() ? booleanFormat : getNonNullParentConfiguration().getBooleanFormat();
+        if (!isBooleanFormatSet()) {
+            throw new SettingValueNotSetException("booleanFormat");
+        }
+        return booleanFormat;
     }
 
     @Override
@@ -566,7 +414,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getTimeFormat() {
-        return isTimeFormatSet() ? timeFormat : getNonNullParentConfiguration().getTimeFormat();
+        if (!isTimeFormatSet()) {
+            throw new SettingValueNotSetException("timeFormat");
+        }
+        return timeFormat;
     }
 
     @Override
@@ -576,7 +427,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getDateFormat() {
-        return isDateFormatSet() ? dateFormat : getNonNullParentConfiguration().getDateFormat();
+        if (!isDateFormatSet()) {
+            throw new SettingValueNotSetException("dateFormat");
+        }
+        return dateFormat;
     }
 
     @Override
@@ -586,7 +440,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getDateTimeFormat() {
-        return isDateTimeFormatSet() ? dateTimeFormat : getNonNullParentConfiguration().getDateTimeFormat();
+        if (!isDateTimeFormatSet()) {
+            throw new SettingValueNotSetException("dateTimeFormat");
+        }
+        return dateTimeFormat;
     }
 
     @Override
@@ -596,7 +453,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
-        return isCustomDateFormatsSet() ? customDateFormats : getNonNullParentConfiguration().getCustomDateFormats();
+        if (!isCustomDateFormatsSet()) {
+            throw new SettingValueNotSetException("customDateFormats");
+        }
+        return customDateFormats;
     }
 
     @Override
@@ -607,7 +467,7 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
                 return  format;
             }
         }
-        return getNonNullParentConfiguration().getCustomDateFormat(name);
+        return null;
     }
 
     @Override
@@ -617,7 +477,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TemplateExceptionHandler getTemplateExceptionHandler() {
-        return isTemplateExceptionHandlerSet() ? templateExceptionHandler : getNonNullParentConfiguration().getTemplateExceptionHandler();
+        if (!isTemplateExceptionHandlerSet()) {
+            throw new SettingValueNotSetException("templateExceptionHandler");
+        }
+        return templateExceptionHandler;
     }
 
     @Override
@@ -627,7 +490,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public ObjectWrapper getObjectWrapper() {
-        return isObjectWrapperSet() ? objectWrapper : getNonNullParentConfiguration().getObjectWrapper();
+        if (!isObjectWrapperSet()) {
+            throw new SettingValueNotSetException("objectWrapper");
+        }
+        return objectWrapper;
     }
 
     @Override
@@ -637,7 +503,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Charset getOutputEncoding() {
-        return isOutputEncodingSet() ? outputEncoding : getNonNullParentConfiguration().getOutputEncoding();
+        if (!isOutputEncodingSet()) {
+            throw new SettingValueNotSetException("");
+        }
+        return outputEncoding;
     }
 
     @Override
@@ -647,7 +516,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Charset getURLEscapingCharset() {
-        return isURLEscapingCharsetSet() ? urlEscapingCharset : getNonNullParentConfiguration().getURLEscapingCharset();
+        if (!isURLEscapingCharsetSet()) {
+            throw new SettingValueNotSetException("urlEscapingCharset");
+        }
+        return urlEscapingCharset;
     }
 
     @Override
@@ -657,7 +529,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TemplateClassResolver getNewBuiltinClassResolver() {
-        return isNewBuiltinClassResolverSet() ? newBuiltinClassResolver : getNonNullParentConfiguration().getNewBuiltinClassResolver();
+        if (!isNewBuiltinClassResolverSet()) {
+            throw new SettingValueNotSetException("newBuiltinClassResolver");
+        }
+        return newBuiltinClassResolver;
     }
 
     @Override
@@ -667,7 +542,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getAPIBuiltinEnabled() {
-        return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getNonNullParentConfiguration().getAPIBuiltinEnabled();
+        if (!isAPIBuiltinEnabledSet()) {
+            throw new SettingValueNotSetException("apiBuiltinEnabled");
+        }
+        return apiBuiltinEnabled;
     }
 
     @Override
@@ -677,7 +555,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getAutoFlush() {
-        return isAutoFlushSet() ? autoFlush : getNonNullParentConfiguration().getAutoFlush();
+        if (!isAutoFlushSet()) {
+            throw new SettingValueNotSetException("autoFlush");
+        }
+        return autoFlush;
     }
 
     @Override
@@ -687,7 +568,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getShowErrorTips() {
-        return isShowErrorTipsSet() ? showErrorTips : getNonNullParentConfiguration().getShowErrorTips();
+        if (!isShowErrorTipsSet()) {
+            throw new SettingValueNotSetException("showErrorTips");
+        }
+        return showErrorTips;
     }
 
     @Override
@@ -697,7 +581,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getLogTemplateExceptions() {
-        return isLogTemplateExceptionsSet() ? logTemplateExceptions : getNonNullParentConfiguration().getLogTemplateExceptions();
+        if (!isLogTemplateExceptionsSet()) {
+            throw new SettingValueNotSetException("logTemplateExceptions");
+        }
+        return logTemplateExceptions;
     }
 
     @Override
@@ -707,7 +594,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getLazyImports() {
-        return isLazyImportsSet() ? lazyImports : getNonNullParentConfiguration().getLazyImports();
+        if (!isLazyImportsSet()) {
+            throw new SettingValueNotSetException("lazyImports");
+        }
+        return lazyImports;
     }
 
     @Override
@@ -717,7 +607,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Boolean getLazyAutoImports() {
-        return isLazyAutoImportsSet() ? lazyAutoImports : getNonNullParentConfiguration().getLazyAutoImports();
+        if (!isLazyAutoImportsSet()) {
+            throw new SettingValueNotSetException("lazyAutoImports");
+        }
+        return lazyAutoImports;
     }
 
     @Override
@@ -727,7 +620,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<String, String> getAutoImports() {
-        return isAutoImportsSet() ? autoImports : getNonNullParentConfiguration().getAutoImports();
+        if (!isAutoImportsSet()) {
+            throw new SettingValueNotSetException("");
+        }
+        return autoImports;
     }
 
     @Override
@@ -737,7 +633,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public List<String> getAutoIncludes() {
-        return isAutoIncludesSet() ? autoIncludes : getNonNullParentConfiguration().getAutoIncludes();
+        if (!isAutoIncludesSet()) {
+            throw new SettingValueNotSetException("autoIncludes");
+        }
+        return autoIncludes;
     }
 
     @Override
@@ -747,7 +646,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<Object, Object> getCustomAttributes() {
-        return isCustomAttributesSet() ? customAttributes : getNonNullParentConfiguration().getCustomAttributes();
+        if (!isCustomAttributesSet()) {
+            throw new SettingValueNotSetException("customAttributes");
+        }
+        return customAttributes;
     }
 
     @Override
@@ -764,14 +666,14 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
                 return attValue;
             }
         }
-        return getNonNullParentConfiguration().getCustomAttribute(name);
+        return null;
     }
 
     public static final class Builder extends MutableProcessingAndParseConfiguration<Builder>
             implements CommonBuilder<TemplateConfiguration> {
 
         public Builder() {
-            super((Configuration) null);
+            super();
         }
 
         @Override
@@ -779,12 +681,6 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
             return new TemplateConfiguration(this);
         }
 
-        // TODO This will be removed
-        @Override
-        void setParent(ProcessingConfiguration cfg) {
-            throw new UnsupportedOperationException();
-        }
-
         @Override
         protected Locale getInheritedLocale() {
             throw new SettingValueNotSetException("locale");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateLanguage.java b/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
index 9de92bb..205fa8c 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
@@ -43,10 +43,11 @@ public abstract class TemplateLanguage {
         }
 
         @Override
-        public Template parse(String name, String sourceName, Reader reader, Configuration cfg, ParserConfiguration
-                customParserConfiguration, Charset encoding, InputStream streamToUnmarkWhenEncEstabd) throws
+        public Template parse(String name, String sourceName, Reader reader, Configuration cfg,
+                TemplateConfiguration templateConfiguration, Charset encoding,
+                InputStream streamToUnmarkWhenEncEstabd) throws
                 IOException, ParseException {
-            return new Template(name, sourceName, reader, cfg, customParserConfiguration,
+            return new Template(name, sourceName, reader, cfg, templateConfiguration,
                     encoding, streamToUnmarkWhenEncEstabd);
         }
     };
@@ -58,8 +59,9 @@ public abstract class TemplateLanguage {
         }
 
         @Override
-        public Template parse(String name, String sourceName, Reader reader, Configuration cfg, ParserConfiguration
-                customParserConfiguration, Charset sourceEncoding, InputStream streamToUnmarkWhenEncEstabd)
+        public Template parse(String name, String sourceName, Reader reader, Configuration cfg,
+                TemplateConfiguration templateConfiguration, Charset sourceEncoding,
+                InputStream streamToUnmarkWhenEncEstabd)
                 throws IOException, ParseException {
             // Read the contents into a StringWriter, then construct a single-text-block template from it.
             final StringBuilder sb = new StringBuilder();
@@ -82,17 +84,18 @@ public abstract class TemplateLanguage {
 
     /**
      * Returns if the template can specify its own charset inside the template. If so, {@link #parse(String, String,
-     * Reader, Configuration, ParserConfiguration, Charset, InputStream)} can throw
+     * Reader, Configuration, TemplateConfiguration, Charset, InputStream)} can throw
      * {@link WrongTemplateCharsetException}, and it might gets a non-{@code null} for the {@link InputStream}
      * parameter.
      */
     public abstract boolean getCanSpecifyCharsetInContent();
 
     /**
-     * See {@link Template#Template(String, String, Reader, Configuration, ParserConfiguration, Charset, InputStream)}.
+     * See {@link Template#Template(String, String, Reader, Configuration, TemplateConfiguration, Charset,
+     * InputStream)}.
      */
     public abstract Template parse(String name, String sourceName, Reader reader,
-                                   Configuration cfg, ParserConfiguration customParserConfiguration,
+                                   Configuration cfg, TemplateConfiguration templateConfiguration,
                                    Charset encoding, InputStream streamToUnmarkWhenEncEstabd)
             throws IOException, ParseException;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java b/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java
new file mode 100644
index 0000000..e9758cf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.nio.charset.Charset;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Adds {@link Configuration} fallback to the {@link ParserConfiguration} part of a {@link TemplateConfiguration}.
+ */
+final class TemplateParserConfigurationWithFallback implements ParserConfiguration {
+
+    private final Configuration cfg;
+    private final TemplateConfiguration tCfg;
+
+    TemplateParserConfigurationWithFallback(Configuration cfg, TemplateConfiguration tCfg) {
+        this.cfg = cfg;
+        this.tCfg = tCfg;
+    }
+
+    @Override
+    public TemplateLanguage getTemplateLanguage() {
+        return tCfg.isTemplateLanguageSet() ? tCfg.getTemplateLanguage() : cfg.getTemplateLanguage();
+    }
+
+    @Override
+    public boolean isTemplateLanguageSet() {
+        return true;
+    }
+
+    @Override
+    public int getTagSyntax() {
+        return tCfg.isTagSyntaxSet() ? tCfg.getTagSyntax() : cfg.getTagSyntax();
+    }
+
+    @Override
+    public boolean isTagSyntaxSet() {
+        return true;
+    }
+
+    @Override
+    public int getNamingConvention() {
+        return tCfg.isNamingConventionSet() ? tCfg.getNamingConvention() : cfg.getNamingConvention();
+    }
+
+    @Override
+    public boolean isNamingConventionSet() {
+        return true;
+    }
+
+    @Override
+    public boolean getWhitespaceStripping() {
+        return tCfg.isWhitespaceStrippingSet() ? tCfg.getWhitespaceStripping() : cfg.getWhitespaceStripping();
+    }
+
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return true;
+    }
+
+    @Override
+    public ArithmeticEngine getArithmeticEngine() {
+        return tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine();
+    }
+
+    @Override
+    public boolean isArithmeticEngineSet() {
+        return true;
+    }
+
+    @Override
+    public int getAutoEscapingPolicy() {
+        return tCfg.isAutoEscapingPolicySet() ? tCfg.getAutoEscapingPolicy() : cfg.getAutoEscapingPolicy();
+    }
+
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return true;
+    }
+
+    @Override
+    public OutputFormat getOutputFormat() {
+        return tCfg.isOutputFormatSet() ? tCfg.getOutputFormat() : cfg.getOutputFormat();
+    }
+
+    @Override
+    public boolean isOutputFormatSet() {
+        return true;
+    }
+
+    @Override
+    public boolean getRecognizeStandardFileExtensions() {
+        return tCfg.isRecognizeStandardFileExtensionsSet() ? tCfg.getRecognizeStandardFileExtensions()
+                : cfg.getRecognizeStandardFileExtensions();
+    }
+
+    @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return true;
+    }
+
+    @Override
+    public Version getIncompatibleImprovements() {
+        // This can be only set on the Configuration-level
+        return cfg.getIncompatibleImprovements();
+    }
+
+    @Override
+    public int getTabSize() {
+        return tCfg.isTabSizeSet() ? tCfg.getTabSize() : cfg.getTabSize();
+    }
+
+    @Override
+    public boolean isTabSizeSet() {
+        return true;
+    }
+
+    @Override
+    public Charset getSourceEncoding() {
+        return tCfg.isSourceEncodingSet() ? tCfg.getSourceEncoding() : cfg.getSourceEncoding();
+    }
+
+    @Override
+    public boolean isSourceEncodingSet() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
deleted file mode 100644
index c9c7380..0000000
--- a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.freemarker.core;
-
-import java.nio.charset.Charset;
-
-import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
-import org.apache.freemarker.core.outputformat.OutputFormat;
-
-/**
- * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- */ 
-public final class _ParserConfigurationWithInheritedFormat implements ParserConfiguration {
-
-    private final OutputFormat outputFormat;
-    private final Integer autoEscapingPolicy;
-    private final ParserConfiguration wrappedPCfg;
-
-    public _ParserConfigurationWithInheritedFormat(ParserConfiguration wrappedPCfg, OutputFormat outputFormat,
-            Integer autoEscapingPolicy) {
-        this.outputFormat = outputFormat;
-        this.autoEscapingPolicy = autoEscapingPolicy;
-        this.wrappedPCfg = wrappedPCfg;
-    }
-
-    @Override
-    public boolean getWhitespaceStripping() {
-        return wrappedPCfg.getWhitespaceStripping();
-    }
-
-    @Override
-    public boolean isWhitespaceStrippingSet() {
-        return wrappedPCfg.isWhitespaceStrippingSet();
-    }
-
-    @Override
-    public int getTagSyntax() {
-        return wrappedPCfg.getTagSyntax();
-    }
-
-    @Override
-    public boolean isTagSyntaxSet() {
-        return wrappedPCfg.isTagSyntaxSet();
-    }
-
-    @Override
-    public TemplateLanguage getTemplateLanguage() {
-        return wrappedPCfg.getTemplateLanguage();
-    }
-
-    @Override
-    public boolean isTemplateLanguageSet() {
-        return wrappedPCfg.isTemplateLanguageSet();
-    }
-
-    @Override
-    public OutputFormat getOutputFormat() {
-        return outputFormat != null ? outputFormat : wrappedPCfg.getOutputFormat();
-    }
-
-    @Override
-    public boolean isOutputFormatSet() {
-        return wrappedPCfg.isOutputFormatSet();
-    }
-
-    @Override
-    public boolean getRecognizeStandardFileExtensions() {
-        return false;
-    }
-
-    @Override
-    public boolean isRecognizeStandardFileExtensionsSet() {
-        return wrappedPCfg.isRecognizeStandardFileExtensionsSet();
-    }
-
-    @Override
-    public int getNamingConvention() {
-        return wrappedPCfg.getNamingConvention();
-    }
-
-    @Override
-    public boolean isNamingConventionSet() {
-        return wrappedPCfg.isNamingConventionSet();
-    }
-
-    @Override
-    public Version getIncompatibleImprovements() {
-        return wrappedPCfg.getIncompatibleImprovements();
-    }
-
-    @Override
-    public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy : wrappedPCfg.getAutoEscapingPolicy();
-    }
-
-    @Override
-    public boolean isAutoEscapingPolicySet() {
-        return wrappedPCfg.isAutoEscapingPolicySet();
-    }
-
-    @Override
-    public ArithmeticEngine getArithmeticEngine() {
-        return wrappedPCfg.getArithmeticEngine();
-    }
-
-    @Override
-    public boolean isArithmeticEngineSet() {
-        return wrappedPCfg.isArithmeticEngineSet();
-    }
-
-    @Override
-    public int getTabSize() {
-        return wrappedPCfg.getTabSize();
-    }
-
-    @Override
-    public boolean isTabSizeSet() {
-        return wrappedPCfg.isTabSizeSet();
-    }
-
-    @Override
-    public Charset getSourceEncoding() {
-        return wrappedPCfg.getSourceEncoding();
-    }
-
-    @Override
-    public boolean isSourceEncodingSet() {
-        return wrappedPCfg.isSourceEncodingSet();
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
index ac6cb71..8fab61f 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 
 /**
@@ -63,14 +62,4 @@ public class ConditionalTemplateConfigurationFactory extends TemplateConfigurati
         }
     }
 
-    @Override
-    protected void setConfigurationOfChildren(Configuration cfg) {
-        if (templateConfiguration != null) {
-            templateConfiguration.setParentConfiguration(cfg);
-        }
-        if (templateConfigurationFactory != null) {
-            templateConfigurationFactory.setConfiguration(cfg);
-        }
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
index 9b3b665..0f09d3d 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.util._StringUtil;
 
@@ -108,11 +107,4 @@ public class FirstMatchTemplateConfigurationFactory extends TemplateConfiguratio
         return this;
     }
 
-    @Override
-    protected void setConfigurationOfChildren(Configuration cfg) {
-        for (TemplateConfigurationFactory templateConfigurationFactory : templateConfigurationFactories) {
-            templateConfigurationFactory.setConfiguration(cfg);
-        }
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
index 5e9c37b..9b3106f 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 
 /**
@@ -58,27 +57,7 @@ public class MergingTemplateConfigurationFactory extends TemplateConfigurationFa
             }
         }
 
-        if (mergedTCBuilder == null) {
-            return firstResultTC; // Maybe null
-        } else {
-            TemplateConfiguration mergedTC = mergedTCBuilder.build();
-
-            Configuration cfg = getConfiguration();
-            if (cfg == null) {
-                throw new IllegalStateException(
-                        "The TemplateConfigurationFactory wasn't associated to a Configuration yet.");
-            }
-            mergedTC.setParentConfiguration(cfg);
-
-            return mergedTC;
-        }
-    }
-    
-    @Override
-    protected void setConfigurationOfChildren(Configuration cfg) {
-        for (TemplateConfigurationFactory templateConfigurationFactory : templateConfigurationFactories) {
-            templateConfigurationFactory.setConfiguration(cfg);
-        }
+        return mergedTCBuilder == null ? firstResultTC /* Maybe null */ : mergedTCBuilder.build();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
index 5b0e0cd..fe9255d 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateConfiguration;
 
@@ -30,8 +29,6 @@ import org.apache.freemarker.core.TemplateConfiguration;
  * @since 2.3.24
  */
 public abstract class TemplateConfigurationFactory {
-    
-    private Configuration cfg;
 
     /**
      * Returns (maybe creates) the {@link TemplateConfiguration} for the given template source.
@@ -53,38 +50,5 @@ public abstract class TemplateConfigurationFactory {
      */
     public abstract TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
             throws IOException, TemplateConfigurationFactoryException;
-    
-    /**
-     * Binds this {@link TemplateConfigurationFactory} to a {@link Configuration}. Once it's bound, it can't be bound to
-     * another {@link Configuration} any more. This is automatically called by
-     * {@link Configuration#setTemplateConfigurations(TemplateConfigurationFactory)}.
-     */
-    public final void setConfiguration(Configuration cfg) {
-        if (this.cfg != null) {
-            if (cfg != this.cfg) {
-                throw new IllegalStateException(
-                        "The TemplateConfigurationFactory is already bound to another Configuration");
-            }
-        } else {
-            this.cfg = cfg;
-            setConfigurationOfChildren(cfg);
-        }
-    }
-    
-    /**
-     * Returns the configuration this object belongs to, or {@code null} if it isn't yet bound to a
-     * {@link Configuration}.
-     */
-    public Configuration getConfiguration() {
-        return cfg;
-    }
-    
-    /**
-     * Calls {@link TemplateConfiguration#setParentConfiguration(Configuration)} on each enclosed
-     * {@link TemplateConfiguration} and {@link TemplateConfigurationFactory#setConfiguration(Configuration)}
-     * on each enclosed {@link TemplateConfigurationFactory} objects. It only supposed to call these on the direct
-     * "children" of this object, not on the children of the children.
-     */
-    protected abstract void setConfigurationOfChildren(Configuration cfg);
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
index 857b15d..2253384 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
@@ -198,10 +198,7 @@ public final class TemplateLoadingResult {
      * merged with them, with properties coming from the returned {@link TemplateConfiguration} having the highest
      * priority.
      * 
-     * @return {@code null}, or a {@link TemplateConfiguration}. The parent configuration of the
-     *         {@link TemplateConfiguration} need not be set. The returned {@link TemplateConfiguration} won't be
-     *         modified. (If the caller needs to modify it, such as to call
-     *         {@link TemplateConfiguration#setParentConfiguration(Configuration)}, it has to copy it first.)
+     * @return {@code null}, or a {@link TemplateConfiguration}.
      */
     public TemplateConfiguration getTemplateConfiguration() {
         return templateConfiguration;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
index e06eba1..48650e8 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
@@ -532,16 +532,15 @@ public class DefaultTemplateResolver extends TemplateResolver {
             } catch (TemplateConfigurationFactoryException e) {
                 throw newIOException("Error while getting TemplateConfiguration; see cause exception.", e);
             }
-            TemplateConfiguration resultTC = templateLoaderResult.getTemplateConfiguration();
-            if (resultTC != null) {
+            TemplateConfiguration templateLoaderResultTC = templateLoaderResult.getTemplateConfiguration();
+            if (templateLoaderResultTC != null) {
                 TemplateConfiguration.Builder mergedTCBuilder = new TemplateConfiguration.Builder();
                 if (cfgTC != null) {
                     mergedTCBuilder.merge(cfgTC);
                 }
-                mergedTCBuilder.merge(resultTC);
+                mergedTCBuilder.merge(templateLoaderResultTC);
 
                 tc = mergedTCBuilder.build();
-                tc.setParentConfiguration(config);
             } else {
                 tc = cfgTC;
             }
@@ -550,8 +549,10 @@ public class DefaultTemplateResolver extends TemplateResolver {
         if (tc != null && tc.isLocaleSet()) {
             locale = tc.getLocale();
         }
-        Charset initialEncoding = tc != null ? tc.getSourceEncoding() : config.getSourceEncoding();
-        TemplateLanguage templateLanguage = tc != null ? tc.getTemplateLanguage() : config .getTemplateLanguage();
+        Charset initialEncoding = tc != null && tc.isSourceEncodingSet() ? tc.getSourceEncoding()
+                : config.getSourceEncoding();
+        TemplateLanguage templateLanguage = tc != null && tc.isTemplateLanguageSet() ? tc.getTemplateLanguage()
+                : config.getTemplateLanguage();
 
         Template template;
         {
@@ -614,11 +615,7 @@ public class DefaultTemplateResolver extends TemplateResolver {
             }
         }
 
-        if (tc != null) {
-            tc.apply(template);
-        }
-        
-        template.setLocale(locale);
+        template.setLookupLocale(locale);
         template.setCustomLookupCondition(customLookupCondition);
         return template;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a7bbc2a..661cb5d 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -95,8 +95,12 @@ public class FMParser {
     private LinkedList escapes = new LinkedList();
     private int mixedContentNesting; // for stripText
 
-    FMParser(Template template, Reader reader, ParserConfiguration pCfg, InputStream streamToUnmarkWhenEncEstabd) {
-        this(template, true, readerToTokenManager(reader, pCfg), pCfg, streamToUnmarkWhenEncEstabd);
+    FMParser(Template template, Reader reader,
+            ParserConfiguration pCfg, OutputFormat outputFormat, Integer autoEscapingPolicy,
+            InputStream streamToUnmarkWhenEncEstabd) {
+        this(template, true, readerToTokenManager(reader, pCfg),
+                pCfg, outputFormat, autoEscapingPolicy,
+                streamToUnmarkWhenEncEstabd);
     }
 
     private static FMParserTokenManager readerToTokenManager(Reader reader, ParserConfiguration pCfg) {
@@ -105,7 +109,8 @@ public class FMParser {
         return new FMParserTokenManager(simpleCharStream);
     }
 
-    FMParser(Template template, boolean newTemplate, FMParserTokenManager tkMan, ParserConfiguration pCfg,
+    FMParser(Template template, boolean newTemplate, FMParserTokenManager tkMan,
+            ParserConfiguration pCfg, OutputFormat contextOutputFormat, Integer contextAutoEscapingPolicy,
     		InputStream streamToUnmarkWhenEncEstabd) {
         this(tkMan);
 
@@ -122,16 +127,14 @@ public class FMParser {
         this.incompatibleImprovements = incompatibleImprovements;
 
         {
-            OutputFormat outputFormatFromExt;
-            if (!pCfg.getRecognizeStandardFileExtensions()
-                    || (outputFormatFromExt = getFormatFromStdFileExt()) == null) {
-                autoEscapingPolicy = pCfg.getAutoEscapingPolicy();
-                outputFormat = pCfg.getOutputFormat();
-            } else {
-                // Override it
-                autoEscapingPolicy = Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY;
-                outputFormat = outputFormatFromExt;
-            }
+            OutputFormat outputFormatFromExt = pCfg.getRecognizeStandardFileExtensions() ? getFormatFromStdFileExt()
+                    : null;
+            outputFormat = contextOutputFormat != null ? contextOutputFormat
+                    : outputFormatFromExt != null ? outputFormatFromExt
+                    : pCfg.getOutputFormat();
+            autoEscapingPolicy = contextAutoEscapingPolicy != null ? contextAutoEscapingPolicy
+                    : outputFormatFromExt != null ? Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY
+                    : pCfg.getAutoEscapingPolicy();
         }
         recalculateAutoEscapingField();
 
@@ -169,7 +172,7 @@ public class FMParser {
         // If this is a Template under construction, we do the below.
         // If this is just the enclosing Template for ?eval or such, we must not modify it.
         if (newTemplate) {
-            template.setAutoEscaping(autoEscaping);
+            template.setAutoEscapingPolicy(autoEscapingPolicy);
             template.setOutputFormat(outputFormat);
         }
     }
@@ -192,7 +195,7 @@ public class FMParser {
     private OutputFormat getFormatFromStdFileExt() {
         String sourceName = template.getSourceName();
         if (sourceName == null) {
-            return null; // Not possible anyway...
+            return null;
         }
 
         int ln = sourceName.length();
@@ -3925,7 +3928,8 @@ void HeaderElement() :
                                 autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
                             }
                             recalculateAutoEscapingField();
-                            template.setAutoEscaping(autoEscaping);
+
+                            template.setAutoEscapingPolicy(autoEscapingPolicy);
                         } else if (ks.equalsIgnoreCase("output_format") || ks.equals("outputFormat")) {
                             if (vs == null) {
                                 throw new ParseException("Expected a string constant for \"" + ks + "\".", exp);
@@ -3937,9 +3941,9 @@ void HeaderElement() :
 					        } catch (UnregisteredOutputFormatException e) {
 					            throw new ParseException(e.getMessage(), exp, e.getCause());
 					        }
-                            recalculateAutoEscapingField();                                
+                            recalculateAutoEscapingField();
+
                             template.setOutputFormat(outputFormat);
-                            template.setAutoEscaping(autoEscaping);
                         } else if (ks.equalsIgnoreCase("ns_prefixes") || ks.equals("nsPrefixes")) {
                             if (!(value instanceof TemplateHashModelEx)) {
                                 throw new ParseException("Expecting a hash of prefixes to namespace URI's.", exp);
@@ -3972,7 +3976,13 @@ void HeaderElement() :
                                 for (TemplateModelIterator it = keys.iterator(); it.hasNext();) {
                                         String attName = ((TemplateScalarModel) it.next()).getAsString();
                                         Object attValue = DeepUnwrap.unwrap(attributeMap.get(attName));
-                                        template.setCustomAttribute(attName, attValue);
+                                        if (attValue != null && !(attValue instanceof Serializable)) {
+                                            throw new ParseException(
+                                                    "Value of attribute " + _StringUtil.jQuote(attName)
+                                                    + " should implement java.io.Serializable.",
+                                                    exp);
+                                        }
+                                        template.setCustomAttribute(attName, (Serializable) attValue);
                                 }
                             } catch (TemplateModelException tme) {
                             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/manual/en_US/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/src/manual/en_US/FM3-CHANGE-LOG.txt b/src/manual/en_US/FM3-CHANGE-LOG.txt
index 1847aba..ba0267b 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -188,6 +188,8 @@ the FreeMarer 3 changelog here:
     TemplateConfiguration.encoding and Template.encoding to sourceEncoding. (Before this, defaultEncoding was exclusive
     to Configuration, but now it's like any other ParserConfiguration setting that can be overidden on the 3 levels.)
   - Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder.
+  - 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).
 - Settings that have contained a charset name (sourceEncoding, outputEncoding, URLEscapingCharset) are now of type Charset,
   not String. For string based configuration sources (such as .properties files) this means that:
   - Unrecognized charset names are now errors
@@ -200,3 +202,5 @@ the FreeMarer 3 changelog here:
   equivalent of the sourceEncoding ParserConfiguration setting. This is in line with Template.actualTagSyntax and the
   other "actual" properties. (Just as in FM2, Template.getParserConfiguration() still can be used get the
   sourceEncoding used during parsing.)
+- Made TemplateModel classes used by the parser for literals Serializable. (Without this attribute values set in the #ftl
+  header wouldn't be always Serializable, which in turn will sabotage making Template-s Serializable in the future.)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
index 9b643b9..bff5dc0 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
@@ -161,7 +161,7 @@ public class ConfigurableTest {
     }
     
     private MutableProcessingConfiguration createConfigurable() throws IOException {
-        return new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
+        return new TemplateConfiguration.Builder();
     }
 
     private boolean keyFieldExists(String name) throws Exception {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index f24c63e..b5c382f 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -1209,67 +1209,121 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testHasCustomFormats() throws IOException, TemplateException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        Environment env = t.createProcessingEnvironment(null, null);
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
-        env.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertFalse(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        t.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertFalse(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        
-        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
-        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        
-        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            Template t = new Template(null, "", cfg, null);
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+            env.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertTrue(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
         // Same with number formats:
-        
-        env.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertFalse(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        t.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertFalse(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        
-        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        t.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        env.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
-        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            Template t = new Template(null, "", cfg, null);
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+            env.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertTrue(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
     }
     
     public void testNamingConventionSetSetting() throws ConfigurationException {
@@ -1379,11 +1433,13 @@ public class ConfigurationTest extends TestCase {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         for (boolean camelCase : new boolean[] { false, true }) {
             List<String> names = new ArrayList<>(cfg.getSettingNames(camelCase));
-            List<String> cfgableNames = new ArrayList<>(new Template(null, "", cfg).getSettingNames(camelCase));
-            assertStartsWith(names, cfgableNames);
+            List<String> procCfgNames = new ArrayList<>(new Template(null, "", cfg)
+                    .createProcessingEnvironment(null, _NullWriter.INSTANCE)
+                    .getSettingNames(camelCase));
+            assertStartsWith(names, procCfgNames);
             
             String prevName = null;
-            for (int i = cfgableNames.size(); i < names.size(); i++) {
+            for (int i = procCfgNames.size(); i < names.size(); i++) {
                 String name = names.get(i);
                 if (prevName != null) {
                     assertThat(name, greaterThan(prevName));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
index cf336bd..00771cd 100644
--- a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
+++ b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
@@ -28,6 +28,7 @@ import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 
 @SuppressWarnings("boxing")
 public class CustomAttributeTest {
@@ -35,10 +36,12 @@ public class CustomAttributeTest {
     private static final String KEY_1 = "key1";
     private static final String KEY_2 = "key2";
     private static final String KEY_3 = "key3";
-    
-    private static final Object VALUE_1 = new Object();
+    private static final Integer KEY_4 = 4;
+
+    private static final Integer VALUE_1 = 1; // Serializable
     private static final Object VALUE_2 = new Object();
     private static final Object VALUE_3 = new Object();
+    private static final Object VALUE_4 = new Object();
     private static final Object VALUE_LIST = ImmutableList.<Object>of(
             "s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A"));
     private static final Object VALUE_BIGDECIMAL = BigDecimal.valueOf(22);
@@ -47,120 +50,114 @@ public class CustomAttributeTest {
 
     @Test
     public void testStringKey() throws Exception {
-        Template t = new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
-        assertEquals(0, t.getCustomAttributeNames().length);        
-        assertNull(t.getCustomAttribute(KEY_1));
-        
-        t.setCustomAttribute(KEY_1, VALUE_1);
-        assertArrayEquals(new String[] { KEY_1 }, t.getCustomAttributeNames());        
-        assertSame(VALUE_1, t.getCustomAttribute(KEY_1));
-        
-        t.setCustomAttribute(KEY_2, VALUE_2);
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));        
-        assertSame(VALUE_1, t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
-        
-        t.setCustomAttribute(KEY_1, VALUE_2);
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));        
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
+        // Need some MutableProcessingConfiguration:
+        TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder();
+
+        assertEquals(0, mpc.getCustomAttributeNames().length);
+        assertNull(mpc.getCustomAttribute(KEY_1));
         
-        t.setCustomAttribute(KEY_1, null);
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));        
-        assertNull(t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
+        mpc.setCustomAttribute(KEY_1, VALUE_1);
+        assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames());
+        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
         
-        t.removeCustomAttribute(KEY_1);
-        assertArrayEquals(new String[] { KEY_2 }, t.getCustomAttributeNames());        
-        assertNull(t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
+        mpc.setCustomAttribute(KEY_2, VALUE_2);
+        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames()));
+        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
+
+        mpc.setCustomAttribute(KEY_1, VALUE_2);
+        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames()));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
+
+        mpc.setCustomAttribute(KEY_1, null);
+        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames()));
+        assertNull(mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
+
+        mpc.removeCustomAttribute(KEY_1);
+        assertArrayEquals(new String[] { KEY_2 }, mpc.getCustomAttributeNames());
+        assertNull(mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
     }
 
     @Test
     public void testRemoveFromEmptySet() throws Exception {
-        Template t = new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
-        t.removeCustomAttribute(KEY_1);
-        assertEquals(0, t.getCustomAttributeNames().length);        
-        assertNull(t.getCustomAttribute(KEY_1));
-        
-        t.setCustomAttribute(KEY_1, VALUE_1);
-        assertArrayEquals(new String[] { KEY_1 }, t.getCustomAttributeNames());        
-        assertSame(VALUE_1, t.getCustomAttribute(KEY_1));
+        // Need some MutableProcessingConfiguration:
+        TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder();
+
+        mpc.removeCustomAttribute(KEY_1);
+        assertEquals(0, mpc.getCustomAttributeNames().length);
+        assertNull(mpc.getCustomAttribute(KEY_1));
+
+        mpc.setCustomAttribute(KEY_1, VALUE_1);
+        assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames());
+        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
     }
-    
+
     @Test
-    public void testFtlHeader() throws Exception {
+    public void testAttrsFromFtlHeaderOnly() throws Exception {
         Template t = new Template(null, "<#ftl attributes={"
                 + "'" + KEY_1 + "': [ 's', 2, true, {  'a': 'A' } ], "
                 + "'" + KEY_2 + "': " + VALUE_BIGDECIMAL + " "
                 + "}>",
                 new Configuration(Configuration.VERSION_3_0_0));
-        
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));
+
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet());
         assertEquals(VALUE_LIST, t.getCustomAttribute(KEY_1));
         assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2));
-        
+
         t.setCustomAttribute(KEY_1, VALUE_1);
         assertEquals(VALUE_1, t.getCustomAttribute(KEY_1));
         assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2));
+
+        t.setCustomAttribute(KEY_1, null);
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet());
+        assertNull(t.getCustomAttribute(KEY_1));
     }
-    
+
     @Test
-    public void testFtl2Header() throws Exception {
+    public void testAttrsFromFtlHeaderAndFromTemplateConfiguration() throws Exception {
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomAttribute(KEY_3, VALUE_3);
+        tcb.setCustomAttribute(KEY_4, VALUE_4);
         Template t = new Template(null, "<#ftl attributes={"
                 + "'" + KEY_1 + "': 'a', "
                 + "'" + KEY_2 + "': 'b', "
                 + "'" + KEY_3 + "': 'c' "
                 + "}>",
-                new Configuration(Configuration.VERSION_3_0_0));
-        
-        assertArrayEquals(new String[] { KEY_1, KEY_2, KEY_3 }, sort(t.getCustomAttributeNames()));
+                new Configuration(Configuration.VERSION_3_0_0),
+                tcb.build());
+
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet());
         assertEquals("a", t.getCustomAttribute(KEY_1));
         assertEquals("b", t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
-        
-        t.removeCustomAttribute(KEY_2);
-        assertArrayEquals(new String[] { KEY_1, KEY_3 }, sort(t.getCustomAttributeNames()));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertNull(t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
+        assertEquals("c", t.getCustomAttribute(KEY_3)); // Has overridden TC attribute
+        assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); // Inherited TC attribute
+
+        t.setCustomAttribute(KEY_3, null);
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet());
+        assertNull("null value shouldn't cause fallback to TC attribute", t.getCustomAttribute(KEY_3));
     }
 
+
     @Test
-    public void testFtl3Header() throws Exception {
-        Template t = new Template(null, "<#ftl attributes={"
-                + "'" + KEY_1 + "': 'a', "
-                + "'" + KEY_2 + "': 'b', "
-                + "'" + KEY_3 + "': 'c' "
-                + "}>",
-                new Configuration(Configuration.VERSION_3_0_0));
-        
-        assertArrayEquals(new String[] { KEY_1, KEY_2, KEY_3 }, sort(t.getCustomAttributeNames()));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertEquals("b", t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
-        
-        t.setCustomAttribute(KEY_2, null);
-        assertArrayEquals(new String[] { KEY_1, KEY_2, KEY_3 }, sort(t.getCustomAttributeNames()));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertNull(t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
+    public void testAttrsFromTemplateConfigurationOnly() throws Exception {
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomAttribute(KEY_3, VALUE_3);
+        tcb.setCustomAttribute(KEY_4, VALUE_4);
+        Template t = new Template(null, "",
+                new Configuration(Configuration.VERSION_3_0_0),
+                tcb.build());
+
+        assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomAttributes().keySet());
+        assertEquals(VALUE_3, t.getCustomAttribute(KEY_3));
+        assertEquals(VALUE_4, t.getCustomAttribute(KEY_4));
     }
-    
+
     private Object[] sort(String[] customAttributeNames) {
         Arrays.sort(customAttributeNames);
         return customAttributeNames;
     }
 
-    @Test
-    public void testObjectKey() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        assertNull(t.getCustomAttribute(CUST_ATT_KEY));
-        cfg.setCustomAttribute(CUST_ATT_KEY, "cfg");
-        assertEquals("cfg", t.getCustomAttribute(CUST_ATT_KEY));
-        t.setCustomAttribute(CUST_ATT_KEY, "t");
-        assertEquals("t", t.getCustomAttribute(CUST_ATT_KEY));
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
index baee63b..7d37d95 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -282,21 +282,28 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         dropConfiguration();
         Configuration cfg = getConfiguration();
         cfg.addAutoImport("t1", "t1.ftl");
-        Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg);
 
+        TemplateConfiguration tc;
+        if (layer == Template.class) {
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            setLazynessOfConfigurable(tcb, lazyImports, lazyAutoImports, setLazyAutoImports);
+            tc = tcb.build();
+        } else {
+            tc = null;
+        }
+
+        Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg, tc);
         StringWriter sw = new StringWriter();
         Environment env = t.createProcessingEnvironment(null, sw);
-        
+
         if (layer == Configuration.class) {
             setLazynessOfConfigurable(cfg, lazyImports, lazyAutoImports, setLazyAutoImports);
-        } else if (layer == Template.class) {
-            setLazynessOfConfigurable(t, lazyImports, lazyAutoImports, setLazyAutoImports);
         } else if (layer == Environment.class) {
             setLazynessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports);
-        } else {
+        } else if (layer != Template.class) {
             throw new IllegalArgumentException();
         }
-        
+
         env.process();
         assertEquals(expectedOutput, sw.toString());
     }