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/05/05 21:00:24 UTC

[01/11] incubator-freemarker git commit: (Common Builder API cleanup)

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 78a0bddb6 -> 7d61a45d9


(Common Builder API cleanup)


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

Branch: refs/heads/3
Commit: 7c85a5630976a126911a6b583f3a17b5e36bfcf8
Parents: 78a0bdd
Author: ddekany <dd...@apache.org>
Authored: Tue Apr 18 13:09:50 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Tue Apr 18 13:09:50 2017 +0200

----------------------------------------------------------------------
 .../core/model/impl/ClassIntrospector.java      | 37 +++++++----
 .../core/model/impl/DefaultObjectWrapper.java   | 67 ++++++++++++--------
 .../DefaultObjectWrapperTCCLSingletonUtil.java  |  4 +-
 .../freemarker/core/util/FluentBuilder.java     | 36 -----------
 .../core/util/ProductWrappingBuilder.java       |  3 +-
 5 files changed, 65 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7c85a563/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index 2a54618..2159f31 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -50,7 +50,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core._CoreLogs;
 import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.FluentBuilder;
+import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util._JavaVersions;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.slf4j.Logger;
@@ -1073,18 +1073,18 @@ class ClassIntrospector {
         }
     }
 
-    static final class Builder extends FluentBuilder<ClassIntrospector, Builder> implements Cloneable {
+    static final class Builder implements CommonBuilder<ClassIntrospector>, Cloneable {
 
         private static final Map/*<PropertyAssignments, Reference<ClassIntrospector>>*/ INSTANCE_CACHE = new HashMap();
         private static final ReferenceQueue INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue();
 
         // Properties and their *defaults*:
         private int exposureLevel = DefaultObjectWrapper.EXPOSE_SAFE;
-        private boolean exposureLevelExplicitlySet;
+        private boolean exposureLevelSet;
         private boolean exposeFields;
-        private boolean exposeFieldsExplicitlySet;
+        private boolean exposeFieldsSet;
         private MethodAppearanceFineTuner methodAppearanceFineTuner;
-        private boolean methodAppearanceFineTunerExplicitlySet;
+        private boolean methodAppearanceFineTunerSet;
         private MethodSorter methodSorter;
         // Attention:
         // - This is also used as a cache key, so non-normalized field values should be avoided.
@@ -1151,11 +1151,14 @@ class ClassIntrospector {
             }
 
             this.exposureLevel = exposureLevel;
-            exposureLevelExplicitlySet = true;
+            exposureLevelSet = true;
         }
 
-        public boolean isExposureLevelExplicitlySet() {
-            return exposureLevelExplicitlySet;
+        /**
+         * Tells if the property was explicitly set, as opposed to just holding its default value.
+         */
+        public boolean isExposureLevelSet() {
+            return exposureLevelSet;
         }
 
         public boolean getExposeFields() {
@@ -1165,11 +1168,14 @@ class ClassIntrospector {
         /** See {@link DefaultObjectWrapper.ExtendableBuilder#setExposeFields(boolean)}. */
         public void setExposeFields(boolean exposeFields) {
             this.exposeFields = exposeFields;
-            exposeFieldsExplicitlySet = true;
+            exposeFieldsSet = true;
         }
 
-        public boolean isExposeFieldsExplicitlySet() {
-            return exposeFieldsExplicitlySet;
+        /**
+         * Tells if the property was explicitly set, as opposed to just holding its default value.
+         */
+        public boolean isExposeFieldsSet() {
+            return exposeFieldsSet;
         }
 
         public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
@@ -1178,11 +1184,14 @@ class ClassIntrospector {
 
         public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
             this.methodAppearanceFineTuner = methodAppearanceFineTuner;
-            methodAppearanceFineTunerExplicitlySet = true;
+            methodAppearanceFineTunerSet = true;
         }
 
-        public boolean isMethodAppearanceFineTunerExplicitlySet() {
-            return methodAppearanceFineTunerExplicitlySet;
+        /**
+         * Tells if the property was explicitly set, as opposed to just holding its default value.
+         */
+        public boolean isMethodAppearanceFineTunerSet() {
+            return methodAppearanceFineTunerSet;
         }
 
         public MethodSorter getMethodSorter() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7c85a563/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
index d01c902..a20695a 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
@@ -64,7 +64,7 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.FluentBuilder;
+import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.dom.NodeModel;
 import org.slf4j.Logger;
@@ -1326,7 +1326,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
      */
     protected abstract static class ExtendableBuilder<
             ProductT extends DefaultObjectWrapper, SelfT extends ExtendableBuilder<ProductT, SelfT>>
-            extends FluentBuilder<ProductT, SelfT> implements Cloneable {
+            implements CommonBuilder<ProductT>, Cloneable {
 
         private final Version incompatibleImprovements;
 
@@ -1335,15 +1335,15 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
 
         // Properties and their *defaults*:
         private int defaultDateType = TemplateDateModel.UNKNOWN;
-        private boolean defaultDataTypeExplicitlySet;
+        private boolean defaultDataTypeSet;
         private ObjectWrapper outerIdentity;
-        private boolean outerIdentityExplicitlySet;
+        private boolean outerIdentitySet;
         private boolean strict;
-        private boolean strictExplicitlySet;
+        private boolean strictSet;
         private boolean useModelCache;
-        private boolean useModelCacheExplicitlySet;
+        private boolean useModelCacheSet;
         private boolean usePrivateCaches;
-        private boolean usePrivateCachesExplicitlySet;
+        private boolean usePrivateCachesSet;
         // Attention!
         // - As this object is a cache key, non-normalized field values should be avoided.
         // - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens,
@@ -1395,6 +1395,11 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             classIntrospectorBuilder = new ClassIntrospector.Builder(incompatibleImprovements);
         }
 
+        @SuppressWarnings("unchecked")
+        protected SelfT self() {
+            return (SelfT) this;
+        }
+
         /**
          * Calculate a content-based hash that could be used when looking up the product object that {@link #build()}
          * returns from a cache. If you override {@link ExtendableBuilder} and add new fields, don't forget to take
@@ -1490,7 +1495,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          */
         public void setDefaultDateType(int defaultDateType) {
             this.defaultDateType = defaultDateType;
-            defaultDataTypeExplicitlySet = true;
+            defaultDataTypeSet = true;
         }
 
         /**
@@ -1504,8 +1509,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         /**
          * Tells if the property was explicitly set, as opposed to just holding its default value.
          */
-        public boolean isDefaultDataTypeExplicitlySet() {
-            return defaultDataTypeExplicitlySet;
+        public boolean isDefaultDateTypeSet() {
+            return defaultDataTypeSet;
         }
 
         /**
@@ -1526,7 +1531,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          */
         public void setOuterIdentity(ObjectWrapper outerIdentity) {
             this.outerIdentity = outerIdentity;
-            outerIdentityExplicitlySet = true;
+            outerIdentitySet = true;
         }
 
         /**
@@ -1540,8 +1545,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         /**
          * Tells if the property was explicitly set, as opposed to just holding its default value.
          */
-        public boolean isOuterIdentityExplicitlySet() {
-            return outerIdentityExplicitlySet;
+        public boolean isOuterIdentitySet() {
+            return outerIdentitySet;
         }
 
         /**
@@ -1574,7 +1579,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          */
         public void setStrict(boolean strict) {
             this.strict = strict;
-            strictExplicitlySet = true;
+            strictSet = true;
         }
 
         /**
@@ -1588,8 +1593,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         /**
          * Tells if the property was explicitly set, as opposed to just holding its default value.
          */
-        public boolean isStrictExplicitlySet() {
-            return strictExplicitlySet;
+        public boolean isStrictSet() {
+            return strictSet;
         }
 
         public boolean getUseModelCache() {
@@ -1602,7 +1607,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         // [FM3] Remove
         public void setUseModelCache(boolean useModelCache) {
             this.useModelCache = useModelCache;
-            useModelCacheExplicitlySet = true;
+            useModelCacheSet = true;
         }
 
         /**
@@ -1617,8 +1622,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         /**
          * Tells if the property was explicitly set, as opposed to just holding its default value.
          */
-        public boolean isUseModelCacheExplicitlySet() {
-            return useModelCacheExplicitlySet;
+        public boolean isUseModelCacheSet() {
+            return useModelCacheSet;
         }
 
         /**
@@ -1634,14 +1639,14 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          * */
         public void setUsePrivateCaches(boolean usePrivateCaches) {
             this.usePrivateCaches = usePrivateCaches;
-            usePrivateCachesExplicitlySet = true;
+            usePrivateCachesSet = true;
         }
 
         /**
          * Tells if the property was explicitly set, as opposed to just holding its default value.
          */
-        public boolean isUsePrivateCachesExplicitlySet() {
-            return usePrivateCachesExplicitlySet;
+        public boolean isUsePrivateCachesSet() {
+            return usePrivateCachesSet;
         }
 
         /**
@@ -1673,8 +1678,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         /**
          * Tells if the property was explicitly set, as opposed to just holding its default value.
          */
-        public boolean setExposureLevelExplicitlySet() {
-            return classIntrospectorBuilder.isExposureLevelExplicitlySet();
+        public boolean setExposureLevelSet() {
+            return classIntrospectorBuilder.isExposureLevelSet();
         }
 
         /**
@@ -1706,8 +1711,11 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             return self();
         }
 
-        public boolean isExposeFieldsExplicitlySet() {
-            return classIntrospectorBuilder.isExposeFieldsExplicitlySet();
+        /**
+         * Tells if the property was explicitly set, as opposed to just holding its default value.
+         */
+        public boolean isExposeFieldsSet() {
+            return classIntrospectorBuilder.isExposeFieldsSet();
         }
 
         /**
@@ -1735,8 +1743,11 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             return self();
         }
 
-        public boolean isMethodAppearanceFineTunerExplicitlySet() {
-            return classIntrospectorBuilder.isMethodAppearanceFineTunerExplicitlySet();
+        /**
+         * Tells if the property was explicitly set, as opposed to just holding its default value.
+         */
+        public boolean isMethodAppearanceFineTunerSet() {
+            return classIntrospectorBuilder.isMethodAppearanceFineTunerSet();
         }
 
         /**

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7c85a563/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java b/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java
deleted file mode 100644
index a5e1e5a..0000000
--- a/src/main/java/org/apache/freemarker/core/util/FluentBuilder.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.util;
-
-/**
- * Common superclass for implementing {@link CommonBuilder}; adds helper method to implement a fluent API.
- */
-public abstract class FluentBuilder<ProductT, SelfT extends FluentBuilder<ProductT, SelfT>>
-        implements CommonBuilder<ProductT> {
-
-    @Override
-    public abstract ProductT build();
-
-    @SuppressWarnings("unchecked")
-    protected SelfT self() {
-        return (SelfT) this;
-    }
-
-}

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


[04/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 d651b9d..00b6b3c 100644
--- a/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.freemarker.core;
 
+import static org.apache.freemarker.core.ParsingConfiguration.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
@@ -35,10 +36,13 @@ import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
 import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
 import org.apache.freemarker.core.templateresolver.OrMatcher;
+import org.apache.freemarker.core.templateresolver.impl.NullCacheStorage;
 import org.apache.freemarker.core.userpkg.CustomHTMLOutputFormat;
 import org.apache.freemarker.core.userpkg.DummyOutputFormat;
 import org.apache.freemarker.core.userpkg.SeldomEscapedOutputFormat;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Before;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -51,34 +55,35 @@ public class OutputFormatTest extends TemplateTest {
         addTemplate("t.xml", "${.outputFormat}");
         addTemplate("tWithHeader", "<#ftl outputFormat='HTML'>${.outputFormat}");
         
-        Configuration cfg = getConfiguration();
+        TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
         for (OutputFormat cfgOutputFormat
                 : new OutputFormat[] { UndefinedOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE } ) {
             if (!cfgOutputFormat.equals(UndefinedOutputFormat.INSTANCE)) {
-                cfg.setOutputFormat(cfgOutputFormat);
+                cfgB.setOutputFormat(cfgOutputFormat);
             }
-            
-            assertEquals(cfgOutputFormat, cfg.getOutputFormat());
+            setConfiguration(cfgB.build());
+
+            assertEquals(cfgOutputFormat, getConfiguration().getOutputFormat());
             
             {
-                Template t = cfg.getTemplate("t");
+                Template t = getConfiguration().getTemplate("t");
                 assertEquals(cfgOutputFormat, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
             
             {
-                Template t = cfg.getTemplate("t.xml");
+                Template t = getConfiguration().getTemplate("t.xml");
                 assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
             
             {
-                Template t = cfg.getTemplate("tWithHeader");
+                Template t = getConfiguration().getTemplate("tWithHeader");
                 assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
             
-            cfg.clearTemplateCache();
+            getConfiguration().clearTemplateCache();
         }
     }
     
@@ -95,7 +100,7 @@ public class OutputFormatTest extends TemplateTest {
         addTemplate("t.fTlX", commonContent);
         addTemplate("tWithHeader.ftlx", "<#ftl outputFormat='HTML'>" + commonContent);
         
-        Configuration cfg = getConfiguration();
+        TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
         for (int setupNumber = 1; setupNumber <= 3; setupNumber++) {
             final OutputFormat cfgOutputFormat;
             final OutputFormat ftlhOutputFormat;
@@ -108,16 +113,16 @@ public class OutputFormatTest extends TemplateTest {
                 break;
             case 2:
                 cfgOutputFormat = RTFOutputFormat.INSTANCE;
-                cfg.setOutputFormat(cfgOutputFormat);
+                cfgB.setOutputFormat(cfgOutputFormat);
                 ftlhOutputFormat = HTMLOutputFormat.INSTANCE;
                 ftlxOutputFormat = XMLOutputFormat.INSTANCE;
                 break;
             case 3:
                 cfgOutputFormat = UndefinedOutputFormat.INSTANCE;
-                cfg.unsetOutputFormat();
+                cfgB.unsetOutputFormat();
                 TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
                 tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
-                cfg.setTemplateConfigurations(
+                cfgB.setTemplateConfigurations(
                         new ConditionalTemplateConfigurationFactory(
                                 new OrMatcher(
                                         new FileNameGlobMatcher("*.ftlh"),
@@ -130,40 +135,41 @@ public class OutputFormatTest extends TemplateTest {
             default:
                 throw new AssertionError();
             }
-            
-            assertEquals(cfgOutputFormat, cfg.getOutputFormat());
+
+            setConfiguration(cfgB.build());
+            assertEquals(cfgOutputFormat, getConfiguration().getOutputFormat());
             
             {
-                Template t = cfg.getTemplate("t");
+                Template t = getConfiguration().getTemplate("t");
                 assertEquals(cfgOutputFormat, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
             
             {
-                Template t = cfg.getTemplate("t.ftl");
+                Template t = getConfiguration().getTemplate("t.ftl");
                 assertEquals(cfgOutputFormat, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
             
             for (String name : new String[] { "t.ftlh", "t.FTLH", "t.fTlH" }) {
-                Template t = cfg.getTemplate(name);
+                Template t = getConfiguration().getTemplate(name);
                 assertEquals(ftlhOutputFormat, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
             
             for (String name : new String[] { "t.ftlx", "t.FTLX", "t.fTlX" }) {
-                Template t = cfg.getTemplate(name);
+                Template t = getConfiguration().getTemplate(name);
                 assertEquals(ftlxOutputFormat, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
 
             {
-                Template t = cfg.getTemplate("tWithHeader.ftlx");
+                Template t = getConfiguration().getTemplate("tWithHeader.ftlx");
                 assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat());
                 assertOutput(t, t.getOutputFormat().getName());
             }
-            
-            cfg.clearTemplateCache();
+
+            getConfiguration().clearTemplateCache();
         }
     }
     
@@ -174,49 +180,62 @@ public class OutputFormatTest extends TemplateTest {
         addTemplate("t.ftl",
                 "${'{}'} ${'{}'?esc} ${'{}'?noEsc}");
         
-        TemplateConfiguration.Builder tcbHTML = new TemplateConfiguration.Builder();
-        tcbHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
         ConditionalTemplateConfigurationFactory tcfHTML = new ConditionalTemplateConfigurationFactory(
-                new FileNameGlobMatcher("t.*"), tcbHTML.build());
+                new FileNameGlobMatcher("t.*"),
+                new TemplateConfiguration.Builder()
+                        .outputFormat(HTMLOutputFormat.INSTANCE)
+                        .build());
 
-        TemplateConfiguration.Builder tcbNoAutoEsc = new TemplateConfiguration.Builder();
-        tcbNoAutoEsc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
         ConditionalTemplateConfigurationFactory tcfNoAutoEsc = new ConditionalTemplateConfigurationFactory(
-                new FileNameGlobMatcher("t.*"), tcbNoAutoEsc.build());
+                new FileNameGlobMatcher("t.*"),
+                new TemplateConfiguration.Builder()
+                        .autoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY)
+                        .build());
 
-        Configuration cfg = getConfiguration();
-        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setTemplateConfigurations(tcfHTML);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setTemplateConfigurations(tcfNoAutoEsc);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        
-        cfg.setTemplateConfigurations(null);
-        cfg.unsetOutputFormat();
-        cfg.setRecognizeStandardFileExtensions(false);
-        assertErrorContainsForNamed("t.ftlx", UndefinedOutputFormat.INSTANCE.getName());
-        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
-        assertOutputForNamed("t.ftlx", "&#39; &#39; '");
-        cfg.setOutputFormat(XMLOutputFormat.INSTANCE);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");
-        cfg.setTemplateConfigurations(tcfHTML);
-        assertOutputForNamed("t.ftlx", "&#39; &#39; '");
-        cfg.setTemplateConfigurations(tcfNoAutoEsc);
-        assertOutputForNamed("t.ftlx", "' &apos; '");
-        
-        cfg.setRecognizeStandardFileExtensions(true);
-        cfg.setTemplateConfigurations(tcfHTML);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setTemplateConfigurations(tcfNoAutoEsc);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        
-        cfg.setTemplateConfigurations(null);
-        cfg.unsetOutputFormat();
-        cfg.setTemplateConfigurations(tcfHTML);
-        assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setRecognizeStandardFileExtensions(false);
-        assertOutputForNamed("t.ftlx", "&#39; &#39; '");
+        {
+            TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+
+            setConfiguration(cfgB.outputFormat(HTMLOutputFormat.INSTANCE).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
+            setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
+            setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
+        }
+
+        {
+            TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+
+            setConfiguration(cfgB.recognizeStandardFileExtensions(false).build());
+            assertErrorContainsForNamed("t.ftlx", UndefinedOutputFormat.INSTANCE.getName());
+            setConfiguration(cfgB.outputFormat(HTMLOutputFormat.INSTANCE).build());
+            assertOutputForNamed("t.ftlx", "&#39; &#39; '");
+            setConfiguration(cfgB.outputFormat(XMLOutputFormat.INSTANCE).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");
+            setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+            assertOutputForNamed("t.ftlx", "&#39; &#39; '");
+            setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build());
+            assertOutputForNamed("t.ftlx", "' &apos; '");
+        }
+
+        {
+            TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+            cfgB.setRecognizeStandardFileExtensions(true);
+
+            setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
+            setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
+        }
+
+        {
+            TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+
+            setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+            assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
+            setConfiguration(cfgB.recognizeStandardFileExtensions(false).build());
+            assertOutputForNamed("t.ftlx", "&#39; &#39; '");
+        }
     }
 
     @Test
@@ -249,10 +268,17 @@ public class OutputFormatTest extends TemplateTest {
     @Test
     public void testStandardFileExtensionsFormatterImplOverriding() throws Exception {
         addTemplate("t.ftlh", "${'a&x'}");
+
         assertOutputForNamed("t.ftlh", "a&amp;x");
-        getConfiguration().setRegisteredCustomOutputFormats(Collections.singleton(CustomHTMLOutputFormat.INSTANCE));
+
+        setConfiguration(new TestConfigurationBuilder()
+                .registeredCustomOutputFormats(Collections.singleton(CustomHTMLOutputFormat.INSTANCE))
+                .build());
         assertOutputForNamed("t.ftlh", "a&amp;X");
-        getConfiguration().setRegisteredCustomOutputFormats(Collections.<OutputFormat>emptyList());
+
+        setConfiguration(new TestConfigurationBuilder()
+                .registeredCustomOutputFormats(Collections.<OutputFormat>emptyList())
+                .build());
         assertOutputForNamed("t.ftlh", "a&amp;x");
     }
     
@@ -262,47 +288,47 @@ public class OutputFormatTest extends TemplateTest {
         addTemplate("tWithHeaderFalse", "<#ftl autoEsc=false>${'a&b'}");
         addTemplate("tWithHeaderTrue", "<#ftl autoEsc=true>${'a&b'}");
         
-        Configuration cfg = getConfiguration();
-        
-        assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
-        
-        cfg.setOutputFormat(XMLOutputFormat.INSTANCE);
-        
+        TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder().outputFormat(XMLOutputFormat.INSTANCE);
+        assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
+
         for (boolean cfgAutoEscaping : new boolean[] { true, false }) {
             if (!cfgAutoEscaping) {
-                cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+                cfgB.setAutoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY);
             }
-            
+            setConfiguration(cfgB.build());
+
             {
-                Template t = cfg.getTemplate("t");
+                Template t = getConfiguration().getTemplate("t");
                 if (cfgAutoEscaping) {
-                    assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+                    assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                     assertOutput(t, "a&amp;b");
                 } else {
-                    assertEquals(Configuration.DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+                    assertEquals(DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                     assertOutput(t, "a&b");
                 }
             }
             
             {
-                Template t = cfg.getTemplate("tWithHeaderFalse");
-                assertEquals(Configuration.DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+                Template t = getConfiguration().getTemplate("tWithHeaderFalse");
+                assertEquals(DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                 assertOutput(t, "a&b");
             }
             
             {
-                Template t = cfg.getTemplate("tWithHeaderTrue");
-                assertEquals(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+                Template t = getConfiguration().getTemplate("tWithHeaderTrue");
+                assertEquals(ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
                 assertOutput(t, "a&amp;b");
             }
-            
-            cfg.clearTemplateCache();
+
+            getConfiguration().clearTemplateCache();
         }
     }
     
     @Test
     public void testNumericalInterpolation() throws IOException, TemplateException {
-        getConfiguration().setRegisteredCustomOutputFormats(Collections.singleton(DummyOutputFormat.INSTANCE));
+        setConfiguration(new TestConfigurationBuilder()
+                .registeredCustomOutputFormats(Collections.singleton(DummyOutputFormat.INSTANCE))
+                .build());
         assertOutput(
                 "<#ftl outputFormat='dummy'>#{1.5}; #{1.5; m3}; ${'a.b'}",
                 "1\\.5; 1\\.500; a\\.b");
@@ -331,13 +357,15 @@ public class OutputFormatTest extends TemplateTest {
     
     @Test
     public void testAutoEscapingOnMOs() throws IOException, TemplateException {
-        for (int autoEsc = 0; autoEsc < 2; autoEsc++) {
+        for (boolean cfgAutoEscaping : new boolean[] { true, false }) {
             String commonAutoEscFtl = "<#ftl outputFormat='HTML'>${'&'}";
-            if (autoEsc == 0) {
+            if (cfgAutoEscaping) {
                 // Cfg default is autoEscaping true
                 assertOutput(commonAutoEscFtl, "&amp;");
             } else {
-                getConfiguration().setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+                setConfiguration(createDefaultConfigurationBuilder()
+                        .autoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY)
+                        .build());
                 assertOutput(commonAutoEscFtl, "&");
             }
             
@@ -408,7 +436,7 @@ public class OutputFormatTest extends TemplateTest {
         }
         
         {
-            getConfiguration().setOutputFormat(HTMLOutputFormat.INSTANCE);
+            setConfiguration(new TestConfigurationBuilder().outputFormat(HTMLOutputFormat.INSTANCE).build());
             Template t = Template.createPlainTextTemplate("x", content, getConfiguration());
             Writer sw = new StringWriter();
             t.process(null, sw);
@@ -621,7 +649,9 @@ public class OutputFormatTest extends TemplateTest {
         assertErrorContains(
                 "<#outputFormat 'dummy'></#outputFormat>",
                 "dummy", "nregistered");
-        getConfiguration().setRegisteredCustomOutputFormats(Collections.singleton(DummyOutputFormat.INSTANCE));
+        setConfiguration(new TestConfigurationBuilder()
+                .registeredCustomOutputFormats(Collections.singleton(DummyOutputFormat.INSTANCE))
+                .build());
         assertOutput(
                 "<#outputFormat 'dummy'>${.outputFormat}</#outputFormat>",
                 "dummy");
@@ -692,7 +722,9 @@ public class OutputFormatTest extends TemplateTest {
                 "<#noautoEsc></#noautoEsc>",
                 "Unknown directive");
 
-        getConfiguration().setOutputFormat(XMLOutputFormat.INSTANCE);
+        setConfiguration(new TestConfigurationBuilder()
+                .outputFormat(XMLOutputFormat.INSTANCE)
+                .build());
         
         // Empty block:
         assertOutput(
@@ -746,36 +778,41 @@ public class OutputFormatTest extends TemplateTest {
 
     @Test
     public void testAutoEscPolicy() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setRegisteredCustomOutputFormats(ImmutableList.of(
+        TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+        cfgB.setRegisteredCustomOutputFormats(ImmutableList.of(
                 SeldomEscapedOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE));
-        assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+        assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
         
         String commonFTL = "${'.'} ${.autoEsc?c}";
         String notEsced = ". false";
         String esced = "\\. true";
 
         for (int autoEscPolicy : new int[] {
-                Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,
-                Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY,
-                Configuration.DISABLE_AUTO_ESCAPING_POLICY }) {
-            cfg.setAutoEscapingPolicy(autoEscPolicy);
+                ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,
+                ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY,
+                DISABLE_AUTO_ESCAPING_POLICY }) {
+            cfgB.setAutoEscapingPolicy(autoEscPolicy);
             
-            String sExpted = autoEscPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY ? esced : notEsced;
-            cfg.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE);
+            String sExpted = autoEscPolicy == ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY ? esced : notEsced;
+            cfgB.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput(commonFTL, sExpted);
-            cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+            cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput("<#ftl outputFormat='seldomEscaped'>" + commonFTL, sExpted);
             assertOutput("<#outputFormat 'seldomEscaped'>" + commonFTL + "</#outputFormat>", sExpted);
             
-            String dExpted = autoEscPolicy == Configuration.DISABLE_AUTO_ESCAPING_POLICY ? notEsced : esced;
-            cfg.setOutputFormat(DummyOutputFormat.INSTANCE);
+            String dExpted = autoEscPolicy == DISABLE_AUTO_ESCAPING_POLICY ? notEsced : esced;
+            cfgB.setOutputFormat(DummyOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput(commonFTL, dExpted);
-            cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+            cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput("<#ftl outputFormat='dummy'>" + commonFTL, dExpted);
             assertOutput("<#outputFormat 'dummy'>" + commonFTL + "</#outputFormat>", dExpted);
             
-            cfg.setOutputFormat(DummyOutputFormat.INSTANCE);
+            cfgB.setOutputFormat(DummyOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput(
                     commonFTL
                     + "<#outputFormat 'seldomEscaped'>"
@@ -882,8 +919,6 @@ public class OutputFormatTest extends TemplateTest {
     @Test
     public void testBannedBIsWhenAutoEscaping() throws Exception {
         for (String biName : new String[] { "html", "xhtml", "rtf", "xml" }) {
-            getConfiguration().setIncompatibleImprovements(Configuration.VERSION_3_0_0);
-            
             String commonFTL = "${'x'?" + biName + "}";
             assertOutput(commonFTL, "x");
             assertErrorContains("<#ftl outputFormat='HTML'>" + commonFTL,
@@ -1003,24 +1038,31 @@ public class OutputFormatTest extends TemplateTest {
         assertOutput("${m1?isMarkupOutput?c} ${m2?isMarkupOutput?c} ${s?isMarkupOutput?c}", "true true false");
         assertOutput("${m1?is_markup_output?c}", "true");
     }
-    
-    @Override
-    protected Configuration createConfiguration() throws TemplateModelException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
-        TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
-        tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.xml"), tcbXML.build()));
 
-        cfg.setSharedVariable("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b"));
-        cfg.setSharedVariable("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c"));
-        cfg.setSharedVariable("htmlPlain", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {h'}"));
-        cfg.setSharedVariable("htmlMarkup", HTMLOutputFormat.INSTANCE.fromMarkup("<p>c"));
-        cfg.setSharedVariable("xmlPlain", XMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {x'}"));
-        cfg.setSharedVariable("xmlMarkup", XMLOutputFormat.INSTANCE.fromMarkup("<p>c</p>"));
-        
-        return cfg;
+    private TestConfigurationBuilder createDefaultConfigurationBuilder() throws TemplateModelException {
+        return new TestConfigurationBuilder()
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("*.xml"),
+                                new TemplateConfiguration.Builder()
+                                        .outputFormat(XMLOutputFormat.INSTANCE)
+                                        .build()))
+                .cacheStorage(NullCacheStorage.INSTANCE); // Prevent caching as we change the cfgB between build().
+    }
+
+    @Before
+    public void addCommonDataModelVariables() throws TemplateModelException {
+        addToDataModel("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b"));
+        addToDataModel("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c"));
+        addToDataModel("htmlPlain", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {h'}"));
+        addToDataModel("htmlMarkup", HTMLOutputFormat.INSTANCE.fromMarkup("<p>c"));
+        addToDataModel("xmlPlain", XMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {x'}"));
+        addToDataModel("xmlMarkup", XMLOutputFormat.INSTANCE.fromMarkup("<p>c</p>"));
+    }
+
+    @Override
+    protected Configuration createDefaultConfiguration() throws TemplateModelException {
+        return createDefaultConfigurationBuilder().build();
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
index 8190e6e..8f20d6c 100644
--- a/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
+++ b/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
@@ -24,15 +24,15 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 
 import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class ParsingErrorMessagesTest {
 
-    private Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-    {
-        cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
-    }
-    
+    private Configuration cfg = new TestConfigurationBuilder()
+            .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX)
+            .build();
+
     @Test
     public void testNeedlessInterpolation() {
         assertErrorContains("<#if ${x} == 3></#if>", "instead of ${");
@@ -89,7 +89,7 @@ public class ParsingErrorMessagesTest {
                 ftl = ftl.replace('<', '[').replace('>', ']');
             }
             new Template("adhoc", ftl, cfg);
-            fail("The tempalte had to fail");
+            fail("The template had to fail");
         } catch (ParseException e) {
             String msg = e.getMessage();
             for (String needle: expectedSubstrings) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java b/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
index aae82f6..cf14b93 100644
--- a/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
+++ b/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
@@ -132,10 +132,12 @@ public class SQLTimeZoneTest extends TemplateTest {
         TimeZone prevSysDefTz = TimeZone.getDefault();
         TimeZone.setDefault(GMT_P02);
         try {
-            Configuration cfg = getConfiguration();
-            cfg.unsetTimeZone();
-            assertNull(cfg.getSQLDateAndTimeTimeZone());
-            assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
+            Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder();
+            cfgB.unsetTimeZone();
+            setConfiguration(cfgB.build());
+
+            assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+            assertEquals(TimeZone.getDefault(), getConfiguration().getTimeZone());
             
             assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME);
         } finally {
@@ -148,10 +150,10 @@ public class SQLTimeZoneTest extends TemplateTest {
         TimeZone prevSysDefTz = TimeZone.getDefault();
         TimeZone.setDefault(GMT_P02);
         try {
-            Configuration cfg = getConfiguration();
-            cfg.unsetTimeZone();
-            cfg.setSQLDateAndTimeTimeZone(GMT_P02);
-            
+            Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder();
+            cfgB.sqlDateAndTimeTimeZone(GMT_P02).unsetTimeZone();
+            setConfiguration(cfgB.build());
+
             assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT);
         } finally {
             TimeZone.setDefault(prevSysDefTz);
@@ -160,48 +162,53 @@ public class SQLTimeZoneTest extends TemplateTest {
     
     @Test
     public void testWithGMT1AndNullSQL() throws Exception {
-        Configuration cfg = getConfiguration();
-        assertNull(cfg.getSQLDateAndTimeTimeZone());
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT+01:00"));
-        
+        setConfiguration(createConfigurationBuilder()
+                .timeZone(TimeZone.getTimeZone("GMT+01:00"))
+                .build());
+        assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+
         assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_SAME + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME);
     }
 
     @Test
     public void testWithGMT1AndGMT2SQL() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setSQLDateAndTimeTimeZone(GMT_P02);
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT+01:00"));
-        
+        setConfiguration(createConfigurationBuilder()
+                .sqlDateAndTimeTimeZone(GMT_P02)
+                .timeZone(TimeZone.getTimeZone("GMT+01:00"))
+                .build());
+
         assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_DIFFERENT + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT);
     }
 
     @Test
     public void testWithGMT2AndNullSQL() throws Exception {
-        Configuration cfg = getConfiguration();
-        assertNull(cfg.getSQLDateAndTimeTimeZone());
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT+02"));
-        
+        setConfiguration(createConfigurationBuilder()
+                .timeZone(TimeZone.getTimeZone("GMT+02"))
+                .build());
+        assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+
         assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME);
     }
 
     @Test
     public void testWithGMT2AndGMT2SQL() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setSQLDateAndTimeTimeZone(GMT_P02);
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT+02"));
+        setConfiguration(createConfigurationBuilder()
+            .sqlDateAndTimeTimeZone(GMT_P02)
+            .timeZone(TimeZone.getTimeZone("GMT+02"))
+            .build());
         
         assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT);
     }
     
     @Test
     public void testCacheFlushings() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setTimeZone(_DateUtil.UTC);
-        cfg.setDateFormat("yyyy-MM-dd E");
-        cfg.setTimeFormat("HH:mm:ss E");
-        cfg.setDateTimeFormat("yyyy-MM-dd'T'HH:mm:ss E");
-        
+        Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder()
+                .timeZone(_DateUtil.UTC)
+                .dateFormat("yyyy-MM-dd E")
+                .timeFormat("HH:mm:ss E")
+                .dateTimeFormat("yyyy-MM-dd'T'HH:mm:ss E");
+
+        setConfiguration(cfgB.build());
         assertOutput(
                 "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
                 + "<#setting locale='de'>\n"
@@ -226,8 +233,8 @@ public class SQLTimeZoneTest extends TemplateTest {
                 + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
                 "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
                 + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05, 2014-07-12T10:30:05, 2014-07-12 Sat, 10:30:05 Sat\n");
-        
-        cfg.setSQLDateAndTimeTimeZone(GMT_P02);
+
+        setConfiguration(cfgB.sqlDateAndTimeTimeZone(GMT_P02).build());
         assertOutput(
                 "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
                 + "<#setting locale='de'>\n"
@@ -256,9 +263,11 @@ public class SQLTimeZoneTest extends TemplateTest {
 
     @Test
     public void testDateAndTimeBuiltInsHasNoEffect() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setTimeZone(_DateUtil.UTC);
-        cfg.setSQLDateAndTimeTimeZone(GMT_P02);
+        setConfiguration(createConfigurationBuilder()
+                .timeZone(_DateUtil.UTC)
+                .sqlDateAndTimeTimeZone(GMT_P02)
+                .build());
+
         assertOutput(
                 "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} "
                 + "${sqlDate?date} ${sqlTime?time}\n"
@@ -275,10 +284,11 @@ public class SQLTimeZoneTest extends TemplateTest {
 
     @Test
     public void testChangeSettingInTemplate() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setTimeZone(_DateUtil.UTC);
-        
-        assertNull(cfg.getSQLDateAndTimeTimeZone());
+        setConfiguration(createConfigurationBuilder()
+                .timeZone(_DateUtil.UTC)
+                .build());
+
+        assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
 
         assertOutput(
                 "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
@@ -311,9 +321,10 @@ public class SQLTimeZoneTest extends TemplateTest {
     
     @Test
     public void testFormatUTCFlagHasNoEffect() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setSQLDateAndTimeTimeZone(GMT_P02);
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT-01"));
+        setConfiguration(createConfigurationBuilder()
+                .sqlDateAndTimeTimeZone(GMT_P02)
+                .timeZone(TimeZone.getTimeZone("GMT-01"))
+                .build());
         
         assertOutput(
                 "<#setting date_format='xs fz'><#setting time_format='xs fz'>\n"
@@ -335,17 +346,15 @@ public class SQLTimeZoneTest extends TemplateTest {
                 + "2014-07-11-01:00, 09:30:05-01:00, 09:30:05-01:00\n"
                 + "2014-07-11Z, 10:30:05Z, 10:30:05Z\n");
     }
-    
-    @Override
-    protected Configuration createConfiguration() {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        cfg.setDateFormat("yyyy-MM-dd");
-        cfg.setTimeFormat("HH:mm:ss");
-        cfg.setDateTimeFormat("yyyy-MM-dd'T'HH:mm:ss");
-        return cfg;
+
+    private Configuration.ExtendableBuilder<?> createConfigurationBuilder() {
+        return new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .locale(Locale.US)
+                .dateFormat("yyyy-MM-dd")
+                .timeFormat("HH:mm:ss")
+                .dateTimeFormat("yyyy-MM-dd'T'HH:mm:ss");
     }
-    
+
     @Override
     protected Object createDataModel() {
         return this;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java b/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java
index 468f069..7e17fc7 100644
--- a/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java
+++ b/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java
@@ -49,11 +49,12 @@ public class SpecialVariableTest extends TemplateTest {
 
     @Test
     public void testIncompationImprovements() throws Exception {
+        setConfiguration(new Configuration.Builder(Configuration.VERSION_3_0_0).build());
         assertOutput(
                 "${.incompatibleImprovements}",
                 getConfiguration().getIncompatibleImprovements().toString());
         
-        getConfiguration().setIncompatibleImprovements(new Version(3, 0, 0));
+        setConfiguration(new Configuration.Builder(Configuration.getVersion()).build());
         assertOutput(
                 "${.incompatible_improvements}",
                 getConfiguration().getIncompatibleImprovements().toString());
@@ -61,30 +62,41 @@ public class SpecialVariableTest extends TemplateTest {
 
     @Test
     public void testAutoEsc() throws Exception {
-        Configuration cfg = getConfiguration();
-        
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+
         for (int autoEscaping : new int[] {
-                Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY }) {
-            cfg.setAutoEscapingPolicy(autoEscaping);
-            cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
+                ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY }) {
+            cfgB.setAutoEscapingPolicy(autoEscaping);
+            cfgB.setOutputFormat(HTMLOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput("${.autoEsc?c}", "true");
             assertOutput("<#ftl autoEsc=false>${.autoEsc?c}", "false");
-            cfg.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+
+            cfgB.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput("${.autoEsc?c}", "false");
-            cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+
+            cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+            setConfiguration(cfgB.build());
             assertOutput("${.autoEsc?c}", "false");
         }
         
-        cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
-        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        cfgB.setAutoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY);
+        cfgB.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        setConfiguration(cfgB.build());
         assertOutput("${.autoEsc?c}", "false");
         assertOutput("<#ftl autoEsc=true>${.autoEsc?c}", "true");
-        cfg.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+
+        cfgB.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+        setConfiguration(cfgB.build());
         assertOutput("${.autoEsc?c}", "false");
-        cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+
+        cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+        setConfiguration(cfgB.build());
         assertOutput("${.autoEsc?c}", "false");
 
-        cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
+        cfgB.setAutoEscapingPolicy(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
+        setConfiguration(cfgB.build());
         assertOutput(
                 "${.autoEsc?c} "
                 + "<#outputFormat 'HTML'>${.autoEsc?c}</#outputFormat> "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java b/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java
index bad9905..5dc4da6 100644
--- a/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java
+++ b/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
 import org.apache.freemarker.core.userpkg.PrintfGTemplateNumberFormatFactory;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 @SuppressWarnings("boxing")
@@ -103,10 +104,11 @@ public class StringLiteralInterpolationTest extends TemplateTest {
     
     @Test
     public void markup() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.setCustomNumberFormats(Collections.singletonMap("G", PrintfGTemplateNumberFormatFactory.INSTANCE));
-        cfg.setNumberFormat("@G 3");
-        
+        setConfiguration(new TestConfigurationBuilder()
+                .customNumberFormats(Collections.singletonMap("G", PrintfGTemplateNumberFormatFactory.INSTANCE))
+                .numberFormat("@G 3")
+                .build());
+
         assertOutput("${\"${1000}\"}", "1.00*10<sup>3</sup>");
         assertOutput("${\"&_${1000}\"}", "&amp;_1.00*10<sup>3</sup>");
         assertOutput("${\"${1000}_&\"}", "1.00*10<sup>3</sup>_&amp;");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TabSizeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TabSizeTest.java b/src/test/java/org/apache/freemarker/core/TabSizeTest.java
index 56691cb..7945b5e 100644
--- a/src/test/java/org/apache/freemarker/core/TabSizeTest.java
+++ b/src/test/java/org/apache/freemarker/core/TabSizeTest.java
@@ -23,13 +23,14 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class TabSizeTest extends TemplateTest {
 
     @Override
-    protected Configuration createConfiguration() throws Exception {
-        return super.createConfiguration();
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return super.createDefaultConfiguration();
     }
 
     @Test
@@ -38,8 +39,8 @@ public class TabSizeTest extends TemplateTest {
         assertErrorColumnNumber(8 + 3, "\t${*}");
         assertErrorColumnNumber(16 + 3, "\t\t${*}");
         assertErrorColumnNumber(16 + 3, "  \t  \t${*}");
-        
-        getConfiguration().setTabSize(1);
+
+        setConfiguration(new TestConfigurationBuilder().tabSize(1).build());
         assertErrorColumnNumber(3, "${*}");
         assertErrorColumnNumber(1 + 3, "\t${*}");
         assertErrorColumnNumber(2 + 3, "\t\t${*}");
@@ -49,14 +50,14 @@ public class TabSizeTest extends TemplateTest {
     @Test
     public void testEvalBI() throws Exception {
         assertErrorContains("${r'\t~'?eval}", "column 9");
-        getConfiguration().setTabSize(4);
+        setConfiguration(new TestConfigurationBuilder().tabSize(4).build());
         assertErrorContains("${r'\t~'?eval}", "column 5");
     }
 
     @Test
     public void testInterpretBI() throws Exception {
         assertErrorContains("<@'\\t$\\{*}'?interpret />", "column 11");
-        getConfiguration().setTabSize(4);
+        setConfiguration(new TestConfigurationBuilder().tabSize(4).build());
         assertErrorContains("<@'\\t$\\{*}'?interpret />", "column 7");
     }
     
@@ -64,7 +65,7 @@ public class TabSizeTest extends TemplateTest {
     public void testStringLiteralInterpolation() throws Exception {
         assertErrorColumnNumber(6, "${'${*}'}");
         assertErrorColumnNumber(9, "${'${\t*}'}");
-        getConfiguration().setTabSize(16);
+        setConfiguration(new TestConfigurationBuilder().tabSize(16).build());
         assertErrorColumnNumber(17, "${'${\t*}'}");
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java b/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
index 07b39e4..fa21c76 100644
--- a/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
+++ b/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
@@ -24,6 +24,7 @@ import java.io.StringReader;
 import java.io.StringWriter;
 
 import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 
 import junit.framework.TestCase;
 
@@ -59,93 +60,101 @@ public class TagSyntaxVariationsTest extends TestCase {
 
     public final void test()
             throws TemplateException, IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-
-        // Permutations 
+        // Permutations
         for (int ifOrAssign = 0; ifOrAssign < 2; ifOrAssign++) {
-            String dir_ang = ifOrAssign == 0 ? IF_ANG : ASSIGN_ANG; 
-            String dir_squ = ifOrAssign == 0 ? IF_SQU : ASSIGN_SQU; 
-            String dir_out = ifOrAssign == 0 ? IF_OUT : ASSIGN_OUT; 
-            
+            String dir_ang = ifOrAssign == 0 ? IF_ANG : ASSIGN_ANG;
+            String dir_squ = ifOrAssign == 0 ? IF_SQU : ASSIGN_SQU;
+            String dir_out = ifOrAssign == 0 ? IF_OUT : ASSIGN_OUT;
+
             // Permutations 
             for (int angOrSqu = 0; angOrSqu < 2; angOrSqu++) {
-                cfg.setTagSyntax(angOrSqu == 0
-                        ? Configuration.ANGLE_BRACKET_TAG_SYNTAX
-                        : Configuration.SQUARE_BRACKET_TAG_SYNTAX);
-                
+                Configuration cfg = new TestConfigurationBuilder()
+                        .tagSyntax(angOrSqu == 0
+                                ? ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX
+                                : ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX)
+                        .build();
+
                 String dir_xxx = angOrSqu == 0 ? dir_ang : dir_squ;
                 String cust_xxx = angOrSqu == 0 ? CUST_ANG : CUST_SQU;
                 String hdr_xxx = angOrSqu == 0 ? HDR_ANG : HDR_SQU;
                 String wrong_xxx = angOrSqu == 0 ? WRONG_ANG : WRONG_SQU;
                 String wrongc_xxx = angOrSqu == 0 ? WRONGC_ANG : WRONGC_SQU;
-                
+
                 test(cfg,
                         dir_xxx + cust_xxx,
                         dir_out + CUST_OUT);
-                
+
                 // Permutations 
                 for (int wrongOrWrongc = 0; wrongOrWrongc < 2; wrongOrWrongc++) {
                     String wrongx_xxx = wrongOrWrongc == 0 ? wrong_xxx : wrongc_xxx;
-                    
+
                     test(cfg,
                             wrongx_xxx + dir_xxx,
                             null);
-    
+
                     test(cfg,
                             dir_xxx + wrongx_xxx,
                             null);
-                    
+
                     test(cfg,
                             hdr_xxx + wrongx_xxx,
                             null);
-                    
+
                     test(cfg,
                             cust_xxx + wrongx_xxx + dir_xxx,
                             null);
                 } // for wrongc
             } // for squ
-            
-            cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
-            for (int perm = 0; perm < 4; perm++) {
-                // All 4 permutations
-                String wrong_xxx = (perm & 1) == 0 ? WRONG_ANG : WRONG_SQU;
-                String dir_xxx = (perm & 2) == 0 ? dir_ang : dir_squ;
-                
-                test(cfg,
-                        wrong_xxx + dir_xxx,
-                        null);
-            } // for perm
-    
-            // Permutations 
-            for (int angOrSquStart = 0; angOrSquStart < 2; angOrSquStart++) {
-                String hdr_xxx = angOrSquStart == 0 ? HDR_ANG : HDR_SQU;
-                String cust_xxx = angOrSquStart == 0 ? CUST_ANG : CUST_SQU;
-                String wrong_yyy = angOrSquStart != 0 ? WRONG_ANG : WRONG_SQU;
-                String dir_xxx = angOrSquStart == 0 ? dir_ang : dir_squ;
-                String dir_yyy = angOrSquStart != 0 ? dir_ang : dir_squ;
-                
-                test(cfg,
-                        cust_xxx + wrong_yyy + dir_xxx,
-                        CUST_OUT + wrong_yyy + dir_out);
-                
-                test(cfg,
-                        hdr_xxx + wrong_yyy + dir_xxx,
-                        wrong_yyy + dir_out);
-                
-                test(cfg,
-                        cust_xxx + wrong_yyy + dir_yyy,
-                        CUST_OUT + wrong_yyy + dir_yyy);
-                
-                test(cfg,
-                        hdr_xxx + wrong_yyy + dir_yyy,
-                        wrong_yyy + dir_yyy);
-                
-                test(cfg,
-                        dir_xxx + wrong_yyy + dir_yyy,
-                        dir_out + wrong_yyy + dir_yyy);
-            } // for squStart
-            
-        } // for assign
+
+            {
+                Configuration cfg = new TestConfigurationBuilder()
+                        .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX)
+                        .build();
+                for (int perm = 0; perm < 4; perm++) {
+                    // All 4 permutations
+                    String wrong_xxx = (perm & 1) == 0 ? WRONG_ANG : WRONG_SQU;
+                    String dir_xxx = (perm & 2) == 0 ? dir_ang : dir_squ;
+
+                    test(cfg,
+                            wrong_xxx + dir_xxx,
+                            null);
+                } // for perm
+            }
+
+            {
+                Configuration cfg = new TestConfigurationBuilder()
+                        .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX)
+                        .build();
+                // Permutations
+                for (int angOrSquStart = 0; angOrSquStart < 2; angOrSquStart++) {
+                    String hdr_xxx = angOrSquStart == 0 ? HDR_ANG : HDR_SQU;
+                    String cust_xxx = angOrSquStart == 0 ? CUST_ANG : CUST_SQU;
+                    String wrong_yyy = angOrSquStart != 0 ? WRONG_ANG : WRONG_SQU;
+                    String dir_xxx = angOrSquStart == 0 ? dir_ang : dir_squ;
+                    String dir_yyy = angOrSquStart != 0 ? dir_ang : dir_squ;
+
+                    test(cfg,
+                            cust_xxx + wrong_yyy + dir_xxx,
+                            CUST_OUT + wrong_yyy + dir_out);
+
+                    test(cfg,
+                            hdr_xxx + wrong_yyy + dir_xxx,
+                            wrong_yyy + dir_out);
+
+                    test(cfg,
+                            cust_xxx + wrong_yyy + dir_yyy,
+                            CUST_OUT + wrong_yyy + dir_yyy);
+
+                    test(cfg,
+                            hdr_xxx + wrong_yyy + dir_yyy,
+                            wrong_yyy + dir_yyy);
+
+                    test(cfg,
+                            dir_xxx + wrong_yyy + dir_yyy,
+                            dir_out + wrong_yyy + dir_yyy);
+                } // for squStart
+            } // for assign
+        }
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 593b912..5b1cda9 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -62,6 +62,7 @@ import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFac
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 import org.apache.freemarker.test.MonitoredTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -110,15 +111,18 @@ public class TemplateConfigurationTest {
         }
     }
 
-    private static final Version ICI = Configuration.VERSION_3_0_0;
-
-    private static final Configuration DEFAULT_CFG = new Configuration(ICI);
+    private static final Configuration DEFAULT_CFG;
     static {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder();
         StringTemplateLoader stl = new StringTemplateLoader();
         stl.putTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>In t1;");
         stl.putTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>In t2;");
         stl.putTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>In t3;");
-        DEFAULT_CFG.setTemplateLoader(stl);
+        try {
+            DEFAULT_CFG = cfgB.templateLoader(stl).build();
+        } catch (ConfigurationException e) {
+            throw new IllegalStateException("Faild to create default configuration", e);
+        }
     }
 
     private static final TimeZone NON_DEFAULT_TZ;
@@ -158,7 +162,8 @@ public class TemplateConfigurationTest {
         SETTING_ASSIGNMENTS.put("logTemplateExceptions", true);
         SETTING_ASSIGNMENTS.put("newBuiltinClassResolver", TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
         SETTING_ASSIGNMENTS.put("numberFormat", "0.0000");
-        SETTING_ASSIGNMENTS.put("objectWrapper", new RestrictedObjectWrapper.Builder(ICI).build());
+        SETTING_ASSIGNMENTS.put("objectWrapper",
+                new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
         SETTING_ASSIGNMENTS.put("outputEncoding", StandardCharsets.UTF_16);
         SETTING_ASSIGNMENTS.put("showErrorTips", false);
         SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE_HANDLER);
@@ -173,11 +178,11 @@ public class TemplateConfigurationTest {
 
         // Parser-only settings:
         SETTING_ASSIGNMENTS.put("templateLanguage", TemplateLanguage.STATIC_TEXT);
-        SETTING_ASSIGNMENTS.put("tagSyntax", Configuration.SQUARE_BRACKET_TAG_SYNTAX);
-        SETTING_ASSIGNMENTS.put("namingConvention", Configuration.LEGACY_NAMING_CONVENTION);
+        SETTING_ASSIGNMENTS.put("tagSyntax", ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX);
+        SETTING_ASSIGNMENTS.put("namingConvention", ParsingConfiguration.LEGACY_NAMING_CONVENTION);
         SETTING_ASSIGNMENTS.put("whitespaceStripping", false);
         SETTING_ASSIGNMENTS.put("strictSyntaxMode", false);
-        SETTING_ASSIGNMENTS.put("autoEscapingPolicy", Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+        SETTING_ASSIGNMENTS.put("autoEscapingPolicy", ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY);
         SETTING_ASSIGNMENTS.put("outputFormat", HTMLOutputFormat.INSTANCE);
         SETTING_ASSIGNMENTS.put("recognizeStandardFileExtensions", false);
         SETTING_ASSIGNMENTS.put("tabSize", 1);
@@ -215,7 +220,6 @@ public class TemplateConfigurationTest {
         }
 
         Collections.sort(settingPropDescs, new Comparator<PropertyDescriptor>() {
-
             @Override
             public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
                 return o1.getName().compareToIgnoreCase(o2.getName());
@@ -483,7 +487,7 @@ public class TemplateConfigurationTest {
             Method tReaderMethod = Template.class.getMethod(pd.getReadMethod().getName());
 
             // Without TC
-            assertNotEquals("For \"" + pd.getName() + "\"", newValue,
+            assertNotEquals("For \"" + pd.getName() + "\"",
                     tReaderMethod.invoke(new Template(null, "", DEFAULT_CFG)));
             // With TC
             assertEquals("For \"" + pd.getName() + "\"", newValue,
@@ -493,10 +497,11 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testConfigureCustomAttributes() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setCustomAttribute("k1", "c");
-        cfg.setCustomAttribute("k2", "c");
-        cfg.setCustomAttribute("k3", "c");
+        Configuration cfg = new TestConfigurationBuilder()
+                .customAttribute("k1", "c")
+                .customAttribute("k2", "c")
+                .customAttribute("k3", "c")
+                .build();
 
         TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
         tcb.setCustomAttribute("k2", "tc");
@@ -536,18 +541,18 @@ public class TemplateConfigurationTest {
         
         {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-            tcb.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+            tcb.setTagSyntax(ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX);
             TemplateConfiguration tc = tcb.build();
             assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y");
-            testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.TAG_SYNTAX_KEY_CAMEL_CASE);
         }
         
         {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-            tcb.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+            tcb.setNamingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
             TemplateConfiguration tc = tcb.build();
             assertOutputWithoutAndWithTC(tc, "<#if true>y<#elseif false>n</#if>", "y", null);
-            testedProps.add(Configuration.NAMING_CONVENTION_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.NAMING_CONVENTION_KEY_CAMEL_CASE);
         }
         
         {
@@ -555,7 +560,7 @@ public class TemplateConfigurationTest {
             tcb.setWhitespaceStripping(false);
             TemplateConfiguration tc = tcb.build();
             assertOutputWithoutAndWithTC(tc, "<#if true>\nx\n</#if>\n", "x\n", "\nx\n\n");
-            testedProps.add(Configuration.WHITESPACE_STRIPPING_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.WHITESPACE_STRIPPING_KEY_CAMEL_CASE);
         }
 
         {
@@ -563,7 +568,7 @@ public class TemplateConfigurationTest {
             tcb.setArithmeticEngine(new DummyArithmeticEngine());
             TemplateConfiguration tc = tcb.build();
             assertOutputWithoutAndWithTC(tc, "${1} ${1+1}", "1 2", "11 22");
-            testedProps.add(Configuration.ARITHMETIC_ENGINE_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.ARITHMETIC_ENGINE_KEY_CAMEL_CASE);
         }
 
         {
@@ -573,16 +578,16 @@ public class TemplateConfigurationTest {
             assertOutputWithoutAndWithTC(tc, "${.outputFormat} ${\"a'b\"}",
                     UndefinedOutputFormat.INSTANCE.getName() + " a'b",
                     XMLOutputFormat.INSTANCE.getName() + " a&apos;b");
-            testedProps.add(Configuration.OUTPUT_FORMAT_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE);
         }
 
         {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setOutputFormat(XMLOutputFormat.INSTANCE);
-            tcb.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+            tcb.setAutoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY);
             TemplateConfiguration tc = tcb.build();
             assertOutputWithoutAndWithTC(tc, "${'a&b'}", "a&b", "a&b");
-            testedProps.add(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE);
         }
         
         {
@@ -592,7 +597,7 @@ public class TemplateConfigurationTest {
             tc.setParentConfiguration(new Configuration(new Version(2, 3, 0)));
             assertOutputWithoutAndWithTC(tc, "<#foo>", null, "<#foo>");
             */
-            testedProps.add(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE);
         }
 
         {
@@ -601,7 +606,7 @@ public class TemplateConfigurationTest {
             TemplateConfiguration tc = tcb.build();
             assertOutputWithoutAndWithTC(tc, "adhoc.ftlh", "${.outputFormat}",
                     HTMLOutputFormat.INSTANCE.getName(), UndefinedOutputFormat.INSTANCE.getName());
-            testedProps.add(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE);
         }
 
         {
@@ -614,7 +619,7 @@ public class TemplateConfigurationTest {
                     + "${.error?replace('(?s).*?column ([0-9]+).*', '$1', 'r')}"
                     + "</#attempt>",
                     "13", "8");
-            testedProps.add(Configuration.TAB_SIZE_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.TAB_SIZE_KEY_CAMEL_CASE);
         }
 
         {
@@ -624,15 +629,17 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setTemplateLanguage(TemplateLanguage.STATIC_TEXT);
 
-            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileExtensionMatcher
-                    ("txt"), tcb.build()));
+            TestConfigurationBuilder cfgB = new TestConfigurationBuilder();
+            cfgB.setTemplateConfigurations(
+                    new ConditionalTemplateConfigurationFactory(new FileExtensionMatcher("txt"), tcb.build()));
 
             StringTemplateLoader templateLoader = new StringTemplateLoader();
             templateLoader.putTemplate("adhoc.ftl", "${1+1}");
             templateLoader.putTemplate("adhoc.txt", "${1+1}");
-            cfg.setTemplateLoader(templateLoader);
+            cfgB.setTemplateLoader(templateLoader);
 
+            Configuration cfg = cfgB.build();
+            
             {
                 StringWriter out = new StringWriter();
                 cfg.getTemplate("adhoc.ftl").process(null, out);
@@ -644,7 +651,7 @@ public class TemplateConfigurationTest {
                 assertEquals("${1+1}", out.toString());
             }
 
-            testedProps.add(Configuration.TEMPLATE_LANGUAGE_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.TEMPLATE_LANGUAGE_KEY_CAMEL_CASE);
         }
 
         {
@@ -654,16 +661,18 @@ public class TemplateConfigurationTest {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
             tcb.setSourceEncoding(StandardCharsets.ISO_8859_1);
 
-            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            cfg.setSourceEncoding(StandardCharsets.UTF_8);
-            cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher
-                    ("latin1.ftl"), tcb.build()));
+            TestConfigurationBuilder cfgB = new TestConfigurationBuilder();
+            cfgB.setSourceEncoding(StandardCharsets.UTF_8);
+            cfgB.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
+                    new FileNameGlobMatcher("latin1.ftl"), tcb.build()));
 
             MonitoredTemplateLoader templateLoader = new MonitoredTemplateLoader();
             templateLoader.putBinaryTemplate("utf8.ftl", "próba", StandardCharsets.UTF_8, 1);
             templateLoader.putBinaryTemplate("latin1.ftl", "próba", StandardCharsets.ISO_8859_1, 1);
-            cfg.setTemplateLoader(templateLoader);
+            cfgB.setTemplateLoader(templateLoader);
 
+            Configuration cfg = cfgB.build();
+            
             {
                 StringWriter out = new StringWriter();
                 cfg.getTemplate("utf8.ftl").process(null, out);
@@ -675,7 +684,7 @@ public class TemplateConfigurationTest {
                 assertEquals("próba", out.toString());
             }
 
-            testedProps.add(Configuration.SOURCE_ENCODING_KEY_CAMEL_CASE);
+            testedProps.add(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY_CAMEL_CASE);
         }
 
         if (!PARSER_PROP_NAMES.equals(testedProps)) {
@@ -780,7 +789,7 @@ public class TemplateConfigurationTest {
 
             {
                 // Force camelCase:
-                tcb.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+                tcb.setNamingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
 
                 TemplateConfiguration tc = tcb.build();
 
@@ -790,7 +799,7 @@ public class TemplateConfigurationTest {
 
             {
                 // Force legacy:
-                tcb.setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+                tcb.setNamingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION);
 
                 TemplateConfiguration tc = tcb.build();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
index 24c7ec6..4cd50eb 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
@@ -33,6 +33,7 @@ import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurati
 import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class TemplateConfigurationWithDefaultTemplateResolverTest {
@@ -92,23 +93,24 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
 
     @Test
     public void testLocale() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("(de).ftl", "${.locale}");
-        tl.putTemplate("default.ftl", "${.locale}");
-        tl.putTemplate("(de)-fr.ftl",
+        StringTemplateLoader loader = new StringTemplateLoader();
+        loader.putTemplate("(de).ftl", "${.locale}");
+        loader.putTemplate("default.ftl", "${.locale}");
+        loader.putTemplate("(de)-fr.ftl",
                 ("<#ftl locale='fr_FR'>${.locale}"));
-        tl.putTemplate("default-fr.ftl",
+        loader.putTemplate("default-fr.ftl",
                 ("<#ftl locale='fr_FR'>${.locale}"));
-        cfg.setTemplateLoader(tl);
 
-        TemplateConfiguration.Builder tcDe = new TemplateConfiguration.Builder();
-        tcDe.setLocale(Locale.GERMANY);
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(de)*"), tcDe.build()));
-        
+        Configuration cfg = new TestConfigurationBuilder()
+                .templateLoader(loader)
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("*(de)*"),
+                                new TemplateConfiguration.Builder()
+                                        .locale(Locale.GERMANY)
+                                        .build()))
+                .build();
+
         {
             Template t = cfg.getTemplate("(de).ftl");
             assertEquals(Locale.GERMANY, t.getLocale());
@@ -133,31 +135,28 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
 
     @Test
     public void testConfigurableSettings() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        
-        TemplateConfiguration.Builder tcFR = new TemplateConfiguration.Builder();
-        tcFR.setLocale(Locale.FRANCE);
-        TemplateConfiguration.Builder tcYN = new TemplateConfiguration.Builder();
-        tcYN.setBooleanFormat("Y,N");
-        TemplateConfiguration.Builder tc00 = new TemplateConfiguration.Builder();
-        tc00.setNumberFormat("0.00");
-        cfg.setTemplateConfigurations(
-                new MergingTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(fr)*"), tcFR.build()),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(yn)*"), tcYN.build()),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(00)*"), tc00.build())
-                )
-        );
-        
         String commonFTL = "${.locale} ${true?string} ${1.2}";
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("default", commonFTL);
-        tl.putTemplate("(fr)", commonFTL);
-        tl.putTemplate("(yn)(00)", commonFTL);
-        tl.putTemplate("(00)(fr)", commonFTL);
-        cfg.setTemplateLoader(tl);
-        
+        StringTemplateLoader loader = new StringTemplateLoader();
+        loader.putTemplate("default", commonFTL);
+        loader.putTemplate("(fr)", commonFTL);
+        loader.putTemplate("(yn)(00)", commonFTL);
+        loader.putTemplate("(00)(fr)", commonFTL);
+
+        Configuration cfg = new TestConfigurationBuilder()
+                .templateConfigurations(
+                        new MergingTemplateConfigurationFactory(
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*(fr)*"),
+                                        new TemplateConfiguration.Builder().locale(Locale.FRANCE).build()),
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*(yn)*"),
+                                        new TemplateConfiguration.Builder().booleanFormat("Y,N").build()),
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*(00)*"),
+                                        new TemplateConfiguration.Builder().numberFormat("0.00").build())))
+                .templateLoader(loader)
+                .build();
+
         assertEquals("en_US true 1.2", getTemplateOutput(cfg.getTemplate("default")));
         assertEquals("fr_FR true 1,2", getTemplateOutput(cfg.getTemplate("(fr)")));
         assertEquals("en_US Y 1.20", getTemplateOutput(cfg.getTemplate("(yn)(00)")));
@@ -166,33 +165,33 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
     
     @Test
     public void testCustomAttributes() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
-        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
-        tc1.setCustomAttribute("a1", "a1tc1");
-        tc1.setCustomAttribute("a2", "a2tc1");
-        tc1.setCustomAttribute("a3", "a3tc1");
-        tc1.setCustomAttribute(CUST_ATT_1, "ca1tc1");
-        tc1.setCustomAttribute(CUST_ATT_2, "ca2tc1");
-        
-        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
-        tc2.setCustomAttribute("a1", "a1tc2");
-        tc2.setCustomAttribute(CUST_ATT_1, "ca1tc2");
-        
-        cfg.setTemplateConfigurations(
-                new MergingTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc1)*"), tc1.build()),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc2)*"), tc2.build())
-                )
-        );
-        
         String commonFTL = "<#ftl attributes={ 'a3': 'a3temp' }>";
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("(tc1)", commonFTL);
         tl.putTemplate("(tc1)noHeader", "");
         tl.putTemplate("(tc2)", commonFTL);
         tl.putTemplate("(tc1)(tc2)", commonFTL);
-        cfg.setTemplateLoader(tl);
+
+        Configuration cfg = new TestConfigurationBuilder()
+                .templateConfigurations(
+                        new MergingTemplateConfigurationFactory(
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*(tc1)*"),
+                                        new TemplateConfiguration.Builder()
+                                                .customAttribute("a1", "a1tc1")
+                                                .customAttribute("a2", "a2tc1")
+                                                .customAttribute("a3", "a3tc1")
+                                                .customAttribute(CUST_ATT_1, "ca1tc1")
+                                                .customAttribute(CUST_ATT_2, "ca2tc1")
+                                                .build()),
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*(tc2)*"),
+                                        new TemplateConfiguration.Builder()
+                                                .customAttribute("a1", "a1tc2")
+                                                .customAttribute(CUST_ATT_1, "ca1tc2")
+                                                .build())))
+                .templateLoader(tl)
+                .build();
 
         {
             Template t = cfg.getTemplate("(tc1)");
@@ -235,10 +234,6 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
     }
 
     private Configuration createCommonEncodingTesterConfig() throws UnsupportedEncodingException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
-        cfg.setLocale(Locale.US);
-        
         ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
         tl.putTemplate("utf8.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_8));
         tl.putTemplate("utf16.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_16LE));
@@ -247,18 +242,26 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
                 ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2));
         tl.putTemplate("default-latin2.ftl",
                 ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2));
-        cfg.setTemplateLoader(tl);
-        
-        TemplateConfiguration.Builder tcUtf8 = new TemplateConfiguration.Builder();
-        tcUtf8.setSourceEncoding(StandardCharsets.UTF_8);
-        TemplateConfiguration.Builder tcUtf16 = new TemplateConfiguration.Builder();
-        tcUtf16.setSourceEncoding(StandardCharsets.UTF_16LE);
-        cfg.setTemplateConfigurations(
-                new FirstMatchTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf8*"), tcUtf8.build()),
-                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf16*"), tcUtf16.build())
-                ).allowNoMatch(true));
-        return cfg;
+
+        return new TestConfigurationBuilder()
+                .sourceEncoding(StandardCharsets.ISO_8859_1)
+                .locale(Locale.US)
+                .templateLoader(tl)
+                .templateConfigurations(
+                        new FirstMatchTemplateConfigurationFactory(
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*utf8*"),
+                                        new TemplateConfiguration.Builder()
+                                                .sourceEncoding(StandardCharsets.UTF_8)
+                                                .build()),
+                                new ConditionalTemplateConfigurationFactory(
+                                        new FileNameGlobMatcher("*utf16*"),
+                                        new TemplateConfiguration.Builder()
+                                                .sourceEncoding(StandardCharsets.UTF_16LE)
+                                                .build())
+                        )
+                        .allowNoMatch(true))
+                .build();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java b/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java
index 93a57b1..97c43ad 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java
@@ -28,6 +28,7 @@ import java.io.StringReader;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 public class TemplateConstructorsTest {
 
@@ -36,8 +37,7 @@ public class TemplateConstructorsTest {
     
     @Test
     public void test() throws IOException {
-        final Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
+        final Configuration cfg = new TestConfigurationBuilder().sourceEncoding(StandardCharsets.ISO_8859_1).build();
         
         final String name = "foo/bar.ftl";
         final String sourceName = "foo/bar_de.ftl";

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java b/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
index d678a67..4b5bf59 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
@@ -35,10 +35,9 @@ public class TemplateGetEncodingTest {
 
     @Test
     public void test() throws IOException {
-
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         {
-            cfg.setSourceEncoding(ISO_8859_2);
+            cfgB.setSourceEncoding(ISO_8859_2);
             MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
             tl.putBinaryTemplate("bin", "test");
             tl.putBinaryTemplate("bin-static", "<#test>");
@@ -46,13 +45,14 @@ public class TemplateGetEncodingTest {
             tl.putTextTemplate("text-static", "<#test>");
             TemplateConfiguration.Builder staticTextTCB = new TemplateConfiguration.Builder();
             staticTextTCB.setTemplateLanguage(TemplateLanguage.STATIC_TEXT);
-            cfg.setTemplateConfigurations(
+            cfgB.setTemplateConfigurations(
                     new ConditionalTemplateConfigurationFactory(
                             new FileNameGlobMatcher("*-static*"), staticTextTCB.build()));
-            cfg.setTemplateLoader(tl);
-            cfg.setCacheStorage(new StrongCacheStorage());
+            cfgB.setTemplateLoader(tl);
+            cfgB.setCacheStorage(new StrongCacheStorage());
         }
 
+        Configuration cfg = cfgB.build();
         assertEquals(ISO_8859_2, cfg.getTemplate("bin").getActualSourceEncoding());
         assertEquals(ISO_8859_2, cfg.getTemplate("bin-static").getActualSourceEncoding());
         assertNull(cfg.getTemplate("text").getActualSourceEncoding());


[05/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/DateFormatTest.java b/src/test/java/org/apache/freemarker/core/DateFormatTest.java
index 4fdb1c7..049655f 100644
--- a/src/test/java/org/apache/freemarker/core/DateFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/DateFormatTest.java
@@ -44,7 +44,7 @@ import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.UndefinedCustomFormatException;
 import org.apache.freemarker.core.valueformat.impl.AliasTemplateDateFormatFactory;
 import org.apache.freemarker.test.TemplateTest;
-import org.junit.Before;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableMap;
@@ -55,20 +55,22 @@ public class DateFormatTest extends TemplateTest {
     private static long T = 1441540800000L;
     private static TemplateDateModel TM = new SimpleDate(new Date(T), TemplateDateModel.DATETIME);
     
-    @Before
-    public void setup() {
-        Configuration cfg = getConfiguration();
-        cfg.setIncompatibleImprovements(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT+01:00"));
-        cfg.setSQLDateAndTimeTimeZone(TimeZone.getTimeZone("UTC"));
-        
-        cfg.setCustomDateFormats(ImmutableMap.of(
-                "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
-                "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
-                "div", EpochMillisDivTemplateDateFormatFactory.INSTANCE,
-                "appMeta", AppMetaTemplateDateFormatFactory.INSTANCE,
-                "htmlIso", HTMLISOTemplateDateFormatFactory.INSTANCE));
+    private TestConfigurationBuilder createConfigurationBuilder() {
+        return new TestConfigurationBuilder()
+                .locale(Locale.US)
+                .timeZone(TimeZone.getTimeZone("GMT+01:00"))
+                .sqlDateAndTimeTimeZone(TimeZone.getTimeZone("UTC"))
+                .customDateFormats(ImmutableMap.of(
+                        "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
+                        "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
+                        "div", EpochMillisDivTemplateDateFormatFactory.INSTANCE,
+                        "appMeta", AppMetaTemplateDateFormatFactory.INSTANCE,
+                        "htmlIso", HTMLISOTemplateDateFormatFactory.INSTANCE));
+    }
+
+    @Override
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return createConfigurationBuilder().build();
     }
 
     @Test
@@ -77,14 +79,14 @@ public class DateFormatTest extends TemplateTest {
         assertOutput(
                 "${d?string.@epoch} ${d?string.@epoch} <#setting locale='de_DE'>${d?string.@epoch}",
                 "123456789 123456789 123456789");
-        
-        getConfiguration().setDateTimeFormat("@epoch");
+
+        setConfigurationWithDateTimeFormat("@epoch");
         assertOutput(
                 "<#assign d = d?datetime>"
                 + "${d} ${d?string} <#setting locale='de_DE'>${d}",
                 "123456789 123456789 123456789");
-        
-        getConfiguration().setDateTimeFormat("@htmlIso");
+
+        setConfigurationWithDateTimeFormat("@htmlIso");
         assertOutput(
                 "<#assign d = d?datetime>"
                 + "${d} ${d?string} <#setting locale='de_DE'>${d}",
@@ -105,8 +107,8 @@ public class DateFormatTest extends TemplateTest {
                 "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 "
                 + "123456789@de_DE:GMT+01:00 123456789@de_DE:GMT+01:00 "
                 + "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00");
-        
-        getConfiguration().setDateTimeFormat("@loc");
+
+        setConfigurationWithDateTimeFormat("@loc");
         assertOutput(
                 "<#assign d = d?datetime>"
                 + "${d} ${d?string} "
@@ -122,7 +124,7 @@ public class DateFormatTest extends TemplateTest {
     @Test
     public void testTimeZoneChange() throws Exception {
         addToDataModel("d", new Date(123456789));
-        getConfiguration().setDateTimeFormat("iso");
+        setConfigurationWithDateTimeFormat("iso");
         assertOutput(
                 "${d?string.@loc} ${d?string.@loc} ${d?datetime?isoLocal} "
                 + "<#setting timeZone='GMT+02:00'>"
@@ -132,8 +134,8 @@ public class DateFormatTest extends TemplateTest {
                 "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 1970-01-02T11:17:36+01:00 "
                 + "123456789@en_US:GMT+02:00 123456789@en_US:GMT+02:00 1970-01-02T12:17:36+02:00 "
                 + "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 1970-01-02T11:17:36+01:00");
-        
-        getConfiguration().setDateTimeFormat("@loc");
+
+        setConfigurationWithDateTimeFormat("@loc");
         assertOutput(
                 "<#assign d = d?datetime>"
                 + "${d} ${d?string} "
@@ -148,31 +150,41 @@ public class DateFormatTest extends TemplateTest {
     
     @Test
     public void testWrongFormatStrings() throws Exception {
-        getConfiguration().setDateTimeFormat("x1");
+        setConfigurationWithDateTimeFormat("x1");
         assertErrorContains("${.now}", "\"x1\"", "'x'");
         assertErrorContains("${.now?string}", "\"x1\"", "'x'");
-        getConfiguration().setDateTimeFormat("short");
+        setConfigurationWithDateTimeFormat("short");
         assertErrorContains("${.now?string('x2')}", "\"x2\"", "'x'");
+        assertErrorContains("${.now?string('[wrong]')}", "format string", "[wrong]");
+
+        setConfiguration(createConfigurationBuilder()
+                .dateFormat("[wrong d]")
+                .dateTimeFormat("[wrong dt]")
+                .timeFormat("[wrong t]")
+                .build());
+        assertErrorContains("${.now?date}", "\"date_format\"", "[wrong d]");
+        assertErrorContains("${.now?datetime}", "\"datetime_format\"", "[wrong dt]");
+        assertErrorContains("${.now?time}", "\"time_format\"", "[wrong t]");
     }
 
     @Test
     public void testCustomParameterized() throws Exception {
         Configuration cfg = getConfiguration();
         addToDataModel("d", new SimpleDate(new Date(12345678L), TemplateDateModel.DATETIME));
-        cfg.setDateTimeFormat("@div 1000");
+        setConfigurationWithDateTimeFormat("@div 1000");
         assertOutput("${d}", "12345");
         assertOutput("${d?string}", "12345");
         assertOutput("${d?string.@div_100}", "123456");
         
         assertErrorContains("${d?string.@div_xyz}", "\"@div_xyz\"", "\"xyz\"");
-        cfg.setDateTimeFormat("@div");
+        setConfigurationWithDateTimeFormat("@div");
         assertErrorContains("${d}", "\"datetime_format\"", "\"@div\"", "format parameter is required");
     }
     
     @Test
     public void testUnknownCustomFormat() throws Exception {
         {
-            getConfiguration().setDateTimeFormat("@noSuchFormat");
+            setConfigurationWithDateTimeFormat("@noSuchFormat");
             Throwable exc = assertErrorContains(
                     "${.now}",
                     "\"@noSuchFormat\"", "\"noSuchFormat\"", "\"datetime_format\"");
@@ -180,26 +192,30 @@ public class DateFormatTest extends TemplateTest {
             
         }
         {
-            getConfiguration().setDateFormat("@noSuchFormatD");
+            setConfiguration(createConfigurationBuilder().dateFormat("@noSuchFormatD").build());
             assertErrorContains(
                     "${.now?date}",
                     "\"@noSuchFormatD\"", "\"noSuchFormatD\"", "\"date_format\"");
         }
         {
-            getConfiguration().setTimeFormat("@noSuchFormatT");
+            setConfiguration(createConfigurationBuilder().timeFormat("@noSuchFormatT").build());
             assertErrorContains(
                     "${.now?time}",
                     "\"@noSuchFormatT\"", "\"noSuchFormatT\"", "\"time_format\"");
         }
 
         {
-            getConfiguration().setDateTimeFormat("");
+            setConfigurationWithDateTimeFormat("");
             Throwable exc = assertErrorContains("${.now?string('@noSuchFormat2')}",
                     "\"@noSuchFormat2\"", "\"noSuchFormat2\"");
             assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
         }
     }
-    
+
+    private void setConfigurationWithDateTimeFormat(String formatString) {
+        setConfiguration(createConfigurationBuilder().dateTimeFormat(formatString).build());
+    }
+
     @Test
     public void testNullInModel() throws Exception {
         addToDataModel("d", new MutableTemplateDateModel());
@@ -209,37 +225,40 @@ public class DateFormatTest extends TemplateTest {
     
     @Test
     public void testIcIAndEscaping() throws Exception {
-        Configuration cfg = getConfiguration();
         addToDataModel("d", new SimpleDate(new Date(12345678L), TemplateDateModel.DATETIME));
         
-        cfg.setDateTimeFormat("@epoch");
+        setConfigurationWithDateTimeFormat("@epoch");
         assertOutput("${d}", "12345678");
-        cfg.setDateTimeFormat("'@'yyyy");
+        setConfigurationWithDateTimeFormat("'@'yyyy");
         assertOutput("${d}", "@1970");
-        cfg.setDateTimeFormat("@@yyyy");
+        setConfigurationWithDateTimeFormat("@@yyyy");
         assertOutput("${d}", "@@1970");
-        
-        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        
-        cfg.setDateTimeFormat("@epoch");
+
+        setConfiguration(createConfigurationBuilder()
+                .customDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap())
+                .dateTimeFormat("@epoch")
+                .build());
         assertErrorContains("${d}", "custom", "\"epoch\"");
     }
 
     @Test
     public void testEnvironmentGetters() throws Exception {
-        Template t = new Template(null, "", getConfiguration());
-        Environment env = t.createProcessingEnvironment(null, null);
-        
-        Configuration cfg = getConfiguration();
-        
         String dateFormatStr = "yyyy.MM.dd. (Z)";
         String timeFormatStr = "HH:mm";
         String dateTimeFormatStr = "yyyy.MM.dd. HH:mm";
-        cfg.setDateFormat(dateFormatStr);
-        cfg.setTimeFormat(timeFormatStr);
-        cfg.setDateTimeFormat(dateTimeFormatStr);
+
+        setConfiguration(createConfigurationBuilder()
+                .dateFormat(dateFormatStr)
+                .timeFormat(timeFormatStr)
+                .dateTimeFormat(dateTimeFormatStr)
+                .build());
+
+        Configuration cfg = getConfiguration();
+
+        Template t = new Template(null, "", cfg);
+        Environment env = t.createProcessingEnvironment(null, null);
         
-        // Test that values are coming from the cache if possible 
+        // Test that values are coming from the cache if possible
         for (Class dateClass : new Class[] { Date.class, Timestamp.class, java.sql.Date.class, Time.class } ) {
             for (int dateType
                     : new int[] { TemplateDateModel.DATE, TemplateDateModel.TIME, TemplateDateModel.DATETIME }) {
@@ -314,32 +333,25 @@ public class DateFormatTest extends TemplateTest {
             assertEquals("2015.09.06. 13:00 Sunday", format2.formatToPlainText(TM));
             assertSame(format1, format2);
         }
-        
-        addToDataModel("d", TM);
-        assertErrorContains("${d?string('[wrong]')}", "format string", "[wrong]");
-        cfg.setDateFormat("[wrong d]");
-        cfg.setDateTimeFormat("[wrong dt]");
-        cfg.setTimeFormat("[wrong t]");
-        assertErrorContains("${d?date}", "\"date_format\"", "[wrong d]");
-        assertErrorContains("${d?datetime}", "\"datetime_format\"", "[wrong dt]");
-        assertErrorContains("${d?time}", "\"time_format\"", "[wrong t]");
     }
-    
+
     @Test
-    public void testAlieses() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setCustomDateFormats(ImmutableMap.of(
-                "d", new AliasTemplateDateFormatFactory("yyyy-MMM-dd"),
-                "m", new AliasTemplateDateFormatFactory("yyyy-MMM"),
-                "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setCustomDateFormats(ImmutableMap.of(
-                "m", new AliasTemplateDateFormatFactory("yyyy-MMMM"),
-                "i", new AliasTemplateDateFormatFactory("@epoch")));
-        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
-                new FileNameGlobMatcher("*2*"), tcb.build()));
-        
+    public void testAliases() throws Exception {
+        setConfiguration(createConfigurationBuilder()
+                .customDateFormats(ImmutableMap.of(
+                        "d", new AliasTemplateDateFormatFactory("yyyy-MMM-dd"),
+                        "m", new AliasTemplateDateFormatFactory("yyyy-MMM"),
+                        "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE))
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("*2*"),
+                                new TemplateConfiguration.Builder()
+                                        .customDateFormats(ImmutableMap.of(
+                                                "m", new AliasTemplateDateFormatFactory("yyyy-MMMM"),
+                                                "i", new AliasTemplateDateFormatFactory("@epoch")))
+                                        .build()))
+                .build());
+
         addToDataModel("d", TM);
         String commonFtl = "${d?string.@d} ${d?string.@m} "
                 + "<#setting locale='fr_FR'>${d?string.@m} "
@@ -353,15 +365,17 @@ public class DateFormatTest extends TemplateTest {
     }
     
     @Test
-    public void testAlieses2() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setCustomDateFormats(ImmutableMap.of(
-                "d", new AliasTemplateDateFormatFactory("yyyy-MMM",
-                        ImmutableMap.of(
-                                new Locale("en"), "yyyy-MMM'_en'",
-                                Locale.UK, "yyyy-MMM'_en_GB'",
-                                Locale.FRANCE, "yyyy-MMM'_fr_FR'"))));
-        cfg.setDateTimeFormat("@d");
+    public void testAliases2() throws Exception {
+        setConfiguration(
+                createConfigurationBuilder()
+                .customDateFormats(ImmutableMap.of(
+                        "d", new AliasTemplateDateFormatFactory("yyyy-MMM",
+                                ImmutableMap.of(
+                                        new Locale("en"), "yyyy-MMM'_en'",
+                                        Locale.UK, "yyyy-MMM'_en_GB'",
+                                        Locale.FRANCE, "yyyy-MMM'_fr_FR'"))))
+                .dateTimeFormat("@d")
+                .build());
         addToDataModel("d", TM);
         assertOutput(
                 "<#setting locale='en_US'>${d} "
@@ -377,11 +391,13 @@ public class DateFormatTest extends TemplateTest {
      */
     @Test
     public void testZeroArgDateBI() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.setDateFormat("@epoch");
-        cfg.setDateTimeFormat("@epoch");
-        cfg.setTimeFormat("@epoch");
-        
+        setConfiguration(
+                createConfigurationBuilder()
+                .dateFormat("@epoch")
+                .dateTimeFormat("@epoch")
+                .timeFormat("@epoch")
+                .build());
+
         addToDataModel("t", String.valueOf(T));
         
         assertOutput(
@@ -397,11 +413,13 @@ public class DateFormatTest extends TemplateTest {
 
     @Test
     public void testAppMetaRoundtrip() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.setDateFormat("@appMeta");
-        cfg.setDateTimeFormat("@appMeta");
-        cfg.setTimeFormat("@appMeta");
-        
+        setConfiguration(
+                createConfigurationBuilder()
+                .dateFormat("@appMeta")
+                .dateTimeFormat("@appMeta")
+                .timeFormat("@appMeta")
+                .build());
+
         addToDataModel("t", String.valueOf(T) + "/foo");
         
         assertOutput(

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java b/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
index 15f96dd..29220c4 100644
--- a/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
+++ b/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
@@ -36,12 +36,7 @@ import org.apache.freemarker.test.TemplateTest;
 import org.junit.Test;
 
 public class DirectiveCallPlaceTest extends TemplateTest {
-    
-    @Override
-    protected Configuration createConfiguration() {
-        return new Configuration(Configuration.VERSION_3_0_0);
-    }
-    
+
     @Test
     public void testCustomDataBasics() throws IOException, TemplateException {
         addTemplate(

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java b/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
index ece57c1..b1acead 100644
--- a/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
+++ b/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
@@ -27,6 +27,7 @@ import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.junit.Test;
 
 public class EncodingOverrideTest {
@@ -52,10 +53,10 @@ public class EncodingOverrideTest {
     }
     
     private Configuration createConfig(Charset charset) {
-       Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-       cfg.setClassForTemplateLoading(EncodingOverrideTest.class, "");
-       cfg.setSourceEncoding(charset);
-       return cfg;
+       return new Configuration.Builder(Configuration.VERSION_3_0_0)
+               .templateLoader(new ClassTemplateLoader(EncodingOverrideTest.class, ""))
+               .sourceEncoding(charset)
+               .build();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java b/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
index a82b29c..b79559c 100644
--- a/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
+++ b/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
@@ -32,6 +32,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class EnvironmentGetTemplateVariantsTest extends TemplateTest {
@@ -187,10 +188,10 @@ public class EnvironmentGetTemplateVariantsTest extends TemplateTest {
     }
     
     private Configuration createConfiguration(Version iciVersion) {
-        Configuration cfg = new Configuration(iciVersion);
-        cfg.setTemplateLoader(TEMPLATES);
-        cfg.setWhitespaceStripping(false);
-        return cfg;
+        return new TestConfigurationBuilder(iciVersion)
+                .templateLoader(TEMPLATES)
+                .whitespaceStripping(false)
+                .build();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/ExceptionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ExceptionTest.java b/src/test/java/org/apache/freemarker/core/ExceptionTest.java
index e70869e..19c3b6e 100644
--- a/src/test/java/org/apache/freemarker/core/ExceptionTest.java
+++ b/src/test/java/org/apache/freemarker/core/ExceptionTest.java
@@ -34,6 +34,7 @@ import java.util.Locale;
 
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 
 import junit.framework.TestCase;
 public class ExceptionTest extends TestCase {
@@ -43,9 +44,8 @@ public class ExceptionTest extends TestCase {
     }
 
     public void testParseExceptionSerializable() throws IOException, ClassNotFoundException {
-        Configuration cfg = new Configuration();
         try {
-            new Template("<string>", new StringReader("<@>"), cfg);
+            new Template(null, new StringReader("<@>"), new TestConfigurationBuilder().build());
             fail();
         } catch (ParseException e) {
             ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -55,8 +55,8 @@ public class ExceptionTest extends TestCase {
     }
 
     public void testTemplateErrorSerializable() throws IOException, ClassNotFoundException {
-        Configuration cfg = new Configuration();
-        Template tmp = new Template("<string>", new StringReader("${noSuchVar}"), cfg);
+        Template tmp = new Template(null, new StringReader("${noSuchVar}"),
+                new TestConfigurationBuilder().build());
         try {
             tmp.process(Collections.EMPTY_MAP, new StringWriter());
             fail();
@@ -69,13 +69,11 @@ public class ExceptionTest extends TestCase {
     
     @SuppressWarnings("boxing")
     public void testTemplateExceptionLocationInformation() throws IOException {
-        Configuration cfg = new Configuration();
-        
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("foo_en.ftl", "\n\nxxx${noSuchVariable}");
-        cfg.setTemplateLoader(tl);
-        
-        Template t = cfg.getTemplate("foo.ftl", Locale.US);
+
+        Template t = new TestConfigurationBuilder().templateLoader(tl).build()
+                .getTemplate("foo.ftl", Locale.US);
         try {
             t.process(null, _NullWriter.INSTANCE);
             fail();
@@ -94,14 +92,12 @@ public class ExceptionTest extends TestCase {
 
     @SuppressWarnings("cast")
     public void testParseExceptionLocationInformation() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("foo_en.ftl", "\n\nxxx<#noSuchDirective>");
-        cfg.setTemplateLoader(tl);
-        
+
         try {
-            cfg.getTemplate("foo.ftl", Locale.US);
+            new TestConfigurationBuilder().templateLoader(tl).build()
+                    .getTemplate("foo.ftl", Locale.US);
             fail();
         } catch (ParseException e) {
             System.out.println(e.getMessage());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/GetSourceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/GetSourceTest.java b/src/test/java/org/apache/freemarker/core/GetSourceTest.java
index 81feec6..1462bfc 100644
--- a/src/test/java/org/apache/freemarker/core/GetSourceTest.java
+++ b/src/test/java/org/apache/freemarker/core/GetSourceTest.java
@@ -20,30 +20,30 @@ package org.apache.freemarker.core;
 
 import static org.junit.Assert.*;
 
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class GetSourceTest {
 
     @Test
     public void testGetSource() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         {
             // Note: Default tab size is 8.
-            Template t = new Template(null, "a\n\tb\nc", cfg);
+            Template t = new Template(null, "a\n\tb\nc",
+                    new TestConfigurationBuilder().build());
             // A historical quirk we keep for B.C.: it repaces tabs with spaces.
             assertEquals("a\n        b\nc", t.getSource(1, 1, 1, 3));
         }
         
         {
-            cfg.setTabSize(4);
-            Template t = new Template(null, "a\n\tb\nc", cfg);
+            Template t = new Template(null, "a\n\tb\nc",
+                    new TestConfigurationBuilder().tabSize(4).build());
             assertEquals("a\n    b\nc", t.getSource(1, 1, 1, 3));
         }
         
         {
-            cfg.setTabSize(1);
-            Template t = new Template(null, "a\n\tb\nc", cfg);
+            Template t = new Template(null, "a\n\tb\nc",
+                    new TestConfigurationBuilder().tabSize(1).build());
             // If tab size is 1, it behaves as it always should have: it keeps the tab.
             assertEquals("a\n\tb\nc", t.getSource(1, 1, 1, 3));
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
index 87bae62..6f8851d 100644
--- a/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
+++ b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
@@ -22,16 +22,14 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class HeaderParsingTest extends TemplateTest {
 
-    private final Configuration cfgStripWS = new Configuration(Configuration.VERSION_3_0_0);
-    private final Configuration cfgNoStripWS = new Configuration(Configuration.VERSION_3_0_0);
-    {
-        cfgNoStripWS.setWhitespaceStripping(false);
-    }
-    
+    private final Configuration cfgStripWS = new TestConfigurationBuilder().build();
+    private final Configuration cfgNoStripWS = new TestConfigurationBuilder().whitespaceStripping(false).build();
+
     @Test
     public void test() throws IOException, TemplateException {
         assertOutput("<#ftl>text", "text", "text");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 7d37d95..738b4de 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -24,20 +24,26 @@ import java.io.StringWriter;
 
 import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
-import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
-public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class IncludeAndImportConfigurableLayersTest {
 
     @Test
     public void test3LayerImportNoClashes() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoImport("t1", "t1.ftl");
-
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.addAutoImport("t2", "t2.ftl");
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
+        TestConfigurationBuilder cfgB = createConfigurationBuilder()
+                .autoImports(ImmutableMap.of("t1", "t1.ftl"))
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("main.ftl"),
+                                new TemplateConfiguration.Builder()
+                                        .autoImports(ImmutableMap.of("t2", "t2.ftl"))
+                                        .build()));
+        Configuration cfg = cfgB.build();
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -68,8 +74,9 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
             assertEquals("In main2: t1;t3;", sw.toString());
         }
         
-        cfg.removeAutoImport("t1");
-        
+        cfgB.removeAutoImport("t1");
+        cfg = cfgB.build();
+
         {
             Template t = cfg.getTemplate("main.ftl");
             StringWriter sw = new StringWriter();
@@ -83,15 +90,18 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
     
     @Test
     public void test3LayerImportClashes() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoImport("t1", "t1.ftl");
-        cfg.addAutoImport("t2", "t2.ftl");
-        cfg.addAutoImport("t3", "t3.ftl");
-
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.addAutoImport("t2", "t2b.ftl");
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
+        Configuration cfg = createConfigurationBuilder()
+                .autoImports(ImmutableMap.of(
+                        "t1", "t1.ftl",
+                        "t2", "t2.ftl",
+                        "t3", "t3.ftl"))
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("main.ftl"),
+                                new TemplateConfiguration.Builder()
+                                        .autoImports(ImmutableMap.of("t2", "t2b.ftl"))
+                                        .build()))
+                .build();
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -125,13 +135,16 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
 
     @Test
     public void test3LayerIncludesNoClashes() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoInclude("t1.ftl");
+        TestConfigurationBuilder cfgB = createConfigurationBuilder()
+                .autoIncludes(ImmutableList.of("t1.ftl"))
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("main.ftl"),
+                                new TemplateConfiguration.Builder()
+                                        .autoIncludes(ImmutableList.of("t2.ftl"))
+                                        .build()));
 
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.addAutoInclude("t2.ftl");
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
+        Configuration cfg = cfgB.build();
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -162,8 +175,9 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
             assertEquals("T1;T3;In main2: t1;t3;", sw.toString());
         }
         
-        cfg.removeAutoInclude("t1.ftl");
-        
+        cfgB.removeAutoInclude("t1.ftl");
+        cfg = cfgB.build();
+
         {
             Template t = cfg.getTemplate("main.ftl");
             StringWriter sw = new StringWriter();
@@ -177,15 +191,17 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
 
     @Test
     public void test3LayerIncludeClashes() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoInclude("t1.ftl");
-        cfg.addAutoInclude("t2.ftl");
-        cfg.addAutoInclude("t3.ftl");
-
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.addAutoInclude("t2.ftl");
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
+        Configuration cfg = createConfigurationBuilder()
+                .autoIncludes(ImmutableList.of(
+                        "t1.ftl",
+                        "t2.ftl",
+                        "t3.ftl"))
+                .templateConfigurations(new ConditionalTemplateConfigurationFactory(
+                        new FileNameGlobMatcher("main.ftl"),
+                        new TemplateConfiguration.Builder()
+                                .autoIncludes(ImmutableList.of("t2.ftl"))
+                                .build()))
+                .build();
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -229,15 +245,15 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
     
     @Test
     public void test3LayerIncludesClashes2() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoInclude("t1.ftl");
-        cfg.addAutoInclude("t1.ftl");
-
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.addAutoInclude("t2.ftl");
-        tcb.addAutoInclude("t2.ftl");
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tcb.build()));
+        Configuration cfg = createConfigurationBuilder()
+                .autoIncludes(ImmutableList.of("t1.ftl", "t1.ftl"))
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("main.ftl"),
+                                new TemplateConfiguration.Builder()
+                                        .autoIncludes(ImmutableList.of("t2.ftl", "t2.ftl"))
+                                        .build()))
+                .build();
 
         {
             Template t = cfg.getTemplate("main.ftl");
@@ -254,39 +270,45 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
     }
     
     @Test
-    public void test3LayerLazyness() throws Exception {
+    public void test3LayerLaziness() throws Exception {
         for (Class<?> layer : new Class<?>[] { Configuration.class, Template.class, Environment.class }) {
-            test3LayerLazyness(layer, null, null, false, "t1;t2;");
-            test3LayerLazyness(layer, null, null, true, "t1;t2;");
-            test3LayerLazyness(layer, null, false, true, "t1;t2;");
-            test3LayerLazyness(layer, null, true, true, "t2;");
+            test3LayerLaziness(layer, null, null, false, "t1;t2;");
+            test3LayerLaziness(layer, null, null, true, "t1;t2;");
+            test3LayerLaziness(layer, null, false, true, "t1;t2;");
+            test3LayerLaziness(layer, null, true, true, "t2;");
             
-            test3LayerLazyness(layer, false, null, false, "t1;t2;");
-            test3LayerLazyness(layer, false, null, true, "t1;t2;");
-            test3LayerLazyness(layer, false, false, true, "t1;t2;");
-            test3LayerLazyness(layer, false, true, true, "t2;");
+            test3LayerLaziness(layer, false, null, false, "t1;t2;");
+            test3LayerLaziness(layer, false, null, true, "t1;t2;");
+            test3LayerLaziness(layer, false, false, true, "t1;t2;");
+            test3LayerLaziness(layer, false, true, true, "t2;");
 
-            test3LayerLazyness(layer, true, null, false, "");
-            test3LayerLazyness(layer, true, null, true, "");
-            test3LayerLazyness(layer, true, false, true, "t1;");
-            test3LayerLazyness(layer, true, true, true, "");
+            test3LayerLaziness(layer, true, null, false, "");
+            test3LayerLaziness(layer, true, null, true, "");
+            test3LayerLaziness(layer, true, false, true, "t1;");
+            test3LayerLaziness(layer, true, true, true, "");
         }
     }
     
-    private void test3LayerLazyness(
+    private void test3LayerLaziness(
             Class<?> layer,
             Boolean lazyImports,
             Boolean lazyAutoImports, boolean setLazyAutoImports,
             String expectedOutput)
             throws Exception {
-        dropConfiguration();
-        Configuration cfg = getConfiguration();
-        cfg.addAutoImport("t1", "t1.ftl");
+        Configuration cfg;
+        {
+            TestConfigurationBuilder cfgB = createConfigurationBuilder()
+                    .autoImports(ImmutableMap.of("t1", "t1.ftl"));
+            if (layer == Configuration.class) {
+                setLazinessOfConfigurable(cfgB, lazyImports, lazyAutoImports, setLazyAutoImports);
+            }
+            cfg = cfgB.build();
+        }
 
         TemplateConfiguration tc;
         if (layer == Template.class) {
             TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-            setLazynessOfConfigurable(tcb, lazyImports, lazyAutoImports, setLazyAutoImports);
+            setLazinessOfConfigurable(tcb, lazyImports, lazyAutoImports, setLazyAutoImports);
             tc = tcb.build();
         } else {
             tc = null;
@@ -294,21 +316,17 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
 
         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 == Environment.class) {
-            setLazynessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports);
-        } else if (layer != Template.class) {
-            throw new IllegalArgumentException();
+        Environment env = t.createProcessingEnvironment(null, sw);
+        if (layer == Environment.class) {
+            setLazinessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports);
         }
 
         env.process();
         assertEquals(expectedOutput, sw.toString());
     }
 
-    private void setLazynessOfConfigurable(
+    private void setLazinessOfConfigurable(
             MutableProcessingConfiguration<?> cfg,
             Boolean lazyImports, Boolean lazyAutoImports, boolean setLazyAutoImports) {
         if (lazyImports != null) {
@@ -319,21 +337,18 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         }
     }
     
-    @Override
-    protected Configuration createConfiguration() throws Exception {
-        return new Configuration(Configuration.VERSION_3_0_0);
-    }
+    private TestConfigurationBuilder createConfigurationBuilder() {
+        StringTemplateLoader loader = new StringTemplateLoader();
+        loader.putTemplate("main.ftl", "In main: ${loaded}");
+        loader.putTemplate("main2.ftl", "In main2: ${loaded}");
+        loader.putTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>T1;");
+        loader.putTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>T2;");
+        loader.putTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>T3;");
+        loader.putTemplate("t1b.ftl", "<#global loaded = (loaded!) + 't1b;'>T1b;");
+        loader.putTemplate("t2b.ftl", "<#global loaded = (loaded!) + 't2b;'>T2b;");
+        loader.putTemplate("t3b.ftl", "<#global loaded = (loaded!) + 't3b;'>T3b;");
 
-    @Override
-    protected void addCommonTemplates() {
-        addTemplate("main.ftl", "In main: ${loaded}");
-        addTemplate("main2.ftl", "In main2: ${loaded}");
-        addTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>T1;");
-        addTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>T2;");
-        addTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>T3;");
-        addTemplate("t1b.ftl", "<#global loaded = (loaded!) + 't1b;'>T1b;");
-        addTemplate("t2b.ftl", "<#global loaded = (loaded!) + 't2b;'>T2b;");
-        addTemplate("t3b.ftl", "<#global loaded = (loaded!) + 't3b;'>T3b;");
+        return new TestConfigurationBuilder().templateLoader(loader);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
index d35003d..fa767ff 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
@@ -29,14 +29,17 @@ import org.apache.freemarker.core.Environment.LazilyInitializedNamespace;
 import org.apache.freemarker.core.Environment.Namespace;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
 import org.apache.freemarker.test.TemplateTest;
-import org.junit.Before;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
 @SuppressWarnings("boxing")
 public class IncludeAndImportTest extends TemplateTest {
 
-    @Before
-    public void setup() {
+    @Override
+    protected void addCommonTemplates() {
         addTemplate("inc1.ftl", "[inc1]<#global inc1Cnt = (inc1Cnt!0) + 1><#global history = (history!) + 'I'>");
         addTemplate("inc2.ftl", "[inc2]");
         addTemplate("inc3.ftl", "[inc3]");
@@ -90,10 +93,15 @@ public class IncludeAndImportTest extends TemplateTest {
      */
     @Test
     public void autoIncludeAndAutoImport() throws IOException, TemplateException {
-        getConfiguration().addAutoInclude("inc1.ftl");
-        getConfiguration().addAutoInclude("inc2.ftl");
-        getConfiguration().addAutoImport("lib1", "lib1.ftl");
-        getConfiguration().addAutoImport("lib2", "lib2CallsLib1.ftl");
+        setConfiguration(new TestConfigurationBuilder()
+                .autoImports(ImmutableMap.of(
+                        "lib1", "lib1.ftl",
+                        "lib2", "lib2CallsLib1.ftl"
+                ))
+                .autoIncludes(ImmutableList.of(
+                        "inc1.ftl",
+                        "inc2.ftl"))
+                .build());
         assertOutput(
                 "<#include 'inc3.ftl'>[main] ${inc1Cnt}, ${history}, <@lib1.m/>, <@lib2.m/>",
                 "[inc1][inc2][inc3][main] 1, L1L2I, In lib1, In lib2 (In lib1)");
@@ -130,7 +138,7 @@ public class IncludeAndImportTest extends TemplateTest {
         
         assertOutput(ftl, "In lib2, In lib1; L1L2L3");
         
-        getConfiguration().setLazyImports(true);
+        setConfiguration(new TestConfigurationBuilder().lazyImports(true).build());
         assertOutput(ftl, "In lib2, In lib1; L2L1");
         
         assertOutput(ftlImports + "<@l3.m/>, " + ftlCalls, "In lib3 (In lib1), In lib2, In lib1; L3L1L2");
@@ -138,7 +146,7 @@ public class IncludeAndImportTest extends TemplateTest {
 
     @Test
     public void lazyImportAndLocale() throws IOException, TemplateException {
-        getConfiguration().setLazyImports(true);
+        setConfiguration(new TestConfigurationBuilder().lazyImports(true).build());
         assertOutput("<#setting locale = 'de_DE'><#import 'lib.ftl' as lib>"
                 + "[${history!}] "
                 + "<#setting locale = 'en'>"
@@ -148,47 +156,59 @@ public class IncludeAndImportTest extends TemplateTest {
 
     @Test
     public void lazyAutoImportSettings() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoImport("l1", "lib1.ftl");
-        cfg.addAutoImport("l2", "lib2.ftl");
-        cfg.addAutoImport("l3", "lib3.ftl");
-        
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder()
+                .autoImports(ImmutableMap.of(
+                        "l1", "lib1.ftl",
+                        "l2", "lib2.ftl",
+                        "l3", "lib3.ftl"
+                ));
+
         String ftl = "<@l2.m/>, <@l1.m/>; ${history}";
         String expectedEagerOutput = "In lib2, In lib1; L1L2L3";
         String expecedLazyOutput = "In lib2, In lib1; L2L1";
-        
+
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expectedEagerOutput);
-        cfg.setLazyImports(true);
+        cfgB.setLazyImports(true);
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expecedLazyOutput);
-        cfg.setLazyImports(false);
+        cfgB.setLazyImports(false);
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expectedEagerOutput);
-        cfg.setLazyAutoImports(true);
+        cfgB.setLazyAutoImports(true);
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expecedLazyOutput);
-        cfg.setLazyAutoImports(null);
+        cfgB.setLazyAutoImports(null);
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expectedEagerOutput);
-        cfg.setLazyImports(true);
-        cfg.setLazyAutoImports(false);
+        cfgB.setLazyImports(true);
+        cfgB.setLazyAutoImports(false);
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expectedEagerOutput);
     }
     
     @Test
     public void lazyAutoImportMixedWithManualImport() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.addAutoImport("l1", "lib1.ftl");
-        cfg.addAutoImport("l2", "/./lib2.ftl");
-        cfg.addAutoImport("l3", "lib3.ftl");
-        cfg.setLazyAutoImports(true);
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder()
+                .autoImports(ImmutableMap.of(
+                        "l1", "lib1.ftl",
+                        "l2", "/./lib2.ftl",
+                        "l3", "lib3.ftl"))
+                .lazyAutoImports(true);
 
         String ftl = "<@l2.m/>, <@l1.m/>; ${history}";
         String expectOutputWithoutHistory = "In lib2, In lib1; ";
         String expecedOutput = expectOutputWithoutHistory + "L2L1";
-        
+
+        setConfiguration(cfgB.build());
         assertOutput(ftl, expecedOutput);
         assertOutput("<#import 'lib1.ftl' as l1>" + ftl, expectOutputWithoutHistory + "L1L2");
         assertOutput("<#import './x/../lib1.ftl' as l1>" + ftl, expectOutputWithoutHistory + "L1L2");
         assertOutput("<#import 'lib2.ftl' as l2>" + ftl, expecedOutput);
         assertOutput("<#import 'lib3.ftl' as l3>" + ftl, expectOutputWithoutHistory + "L3L2L1");
-        cfg.setLazyImports(true);
+
+        cfgB.setLazyImports(true);
+        setConfiguration(cfgB.build());
         assertOutput("<#import 'lib1.ftl' as l1>" + ftl, expecedOutput);
         assertOutput("<#import './x/../lib1.ftl' as l1>" + ftl, expecedOutput);
         assertOutput("<#import 'lib2.ftl' as l2>" + ftl, expecedOutput);
@@ -197,12 +217,14 @@ public class IncludeAndImportTest extends TemplateTest {
 
     @Test
     public void lazyImportErrors() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.setLazyImports(true);
-        
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder();
+        cfgB.setLazyImports(true);
+
+        setConfiguration(cfgB.build());
         assertOutput("<#import 'noSuchTemplate.ftl' as wrong>x", "x");
         
-        cfg.addAutoImport("wrong", "noSuchTemplate.ftl");
+        cfgB.addAutoImport("wrong", "noSuchTemplate.ftl");
+        setConfiguration(cfgB.build());
         assertOutput("x", "x");
 
         try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java b/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
index b9848a5..f4908b8 100644
--- a/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
@@ -24,6 +24,7 @@ import java.io.StringReader;
 import java.io.StringWriter;
 
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 
 import junit.framework.TestCase;
 
@@ -34,13 +35,13 @@ public class IncudeFromNamelessTest extends TestCase {
     }
     
     public void test() throws IOException, TemplateException {
-        Configuration cfg = new Configuration();
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("i.ftl", "[i]");
-        tl.putTemplate("sub/i.ftl", "[sub/i]");
-        tl.putTemplate("import.ftl", "<#assign x = 1>");
-        cfg.setTemplateLoader(tl);
-        
+        StringTemplateLoader loader = new StringTemplateLoader();
+        loader.putTemplate("i.ftl", "[i]");
+        loader.putTemplate("sub/i.ftl", "[sub/i]");
+        loader.putTemplate("import.ftl", "<#assign x = 1>");
+
+        Configuration cfg = new TestConfigurationBuilder().templateLoader(loader).build();
+
         Template t = new Template(null, new StringReader(
                     "<#include 'i.ftl'>\n"
                     + "<#include '/i.ftl'>\n"

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
index 0d5e20b..ff5897f 100644
--- a/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
+++ b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 /**
@@ -48,8 +49,8 @@ public class InterpretAndEvalTemplateNameTest extends TemplateTest {
                     + "i{<@[r'" + getTemplateNames + " {<#include \"a.ftl\">','named_interpreted']?interpret />}}");
             tl.putTemplate("sub/a.ftl", "In sub/a.ftl, " + getTemplateNames);
             tl.putTemplate("a.ftl", "In a.ftl");
-            
-            getConfiguration().setTemplateLoader(tl);
+
+            setConfiguration(new TestConfigurationBuilder().templateLoader(tl).build());
             
             assertOutputForNamed("main.ftl",
                     "c=main.ftl, m=main.ftl "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
index cd0a912..2d061d7 100644
--- a/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
+++ b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
@@ -21,6 +21,7 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 /**
@@ -40,21 +41,25 @@ public class InterpretSettingInheritanceTest  extends TemplateTest {
 
     @Test
     public void tagSyntaxTest() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        
-        cfg.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX);
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX)
+                .build());
         assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
         assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_A);
         assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
         assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_A);
-        
-        cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX)
+                .build());
         assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_S);
         assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
         assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_S);
         assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
-        
-        cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX)
+                .build());
         assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
         assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
         assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
@@ -65,13 +70,17 @@ public class InterpretSettingInheritanceTest  extends TemplateTest {
     @Test
     public void whitespaceStrippingTest() throws IOException, TemplateException {
         Configuration cfg = getConfiguration();
-        
-        cfg.setWhitespaceStripping(true);
+
+        setConfiguration(new TestConfigurationBuilder()
+                .whitespaceStripping(true)
+                .build());
         assertOutput("<#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "XY");
         assertOutput("<#ftl stripWhitespace=false><#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "\nXY");
         assertOutput("<#assign x = 1>\nX<@'<#ftl stripWhitespace=false><#assign x = 1>\\nY'?interpret />", "X\nY");
-        
-        cfg.setWhitespaceStripping(false);
+
+        setConfiguration(new TestConfigurationBuilder()
+                .whitespaceStripping(false)
+                .build());
         assertOutput("<#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "\nX\nY");
         assertOutput("<#ftl stripWhitespace=true><#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "X\nY");
         assertOutput("<#assign x = 1>\nX<@'<#ftl stripWhitespace=true><#assign x = 1>\\nY'?interpret />", "\nXY");
@@ -79,13 +88,15 @@ public class InterpretSettingInheritanceTest  extends TemplateTest {
 
     @Test
     public void evalTest() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        
-        cfg.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX);
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX)
+                .build());
         assertOutput("<@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval />", OUT_S_A_WHEN_SYNTAX_IS_A);
         assertOutput("[#ftl][@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval /]", OUT_S_A_WHEN_SYNTAX_IS_A);
-        
-        cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX)
+                .build());
         assertOutput("[@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval /]", OUT_S_A_WHEN_SYNTAX_IS_S);
         assertOutput("<#ftl><@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval />", OUT_S_A_WHEN_SYNTAX_IS_S);
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/JavaCCExceptionAsEOFFixTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/JavaCCExceptionAsEOFFixTest.java b/src/test/java/org/apache/freemarker/core/JavaCCExceptionAsEOFFixTest.java
index 0a74f96..0fa3f79 100644
--- a/src/test/java/org/apache/freemarker/core/JavaCCExceptionAsEOFFixTest.java
+++ b/src/test/java/org/apache/freemarker/core/JavaCCExceptionAsEOFFixTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.io.Reader;
 
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -89,7 +90,7 @@ public class JavaCCExceptionAsEOFFixTest {
     @Test
     public void testIOException() throws IOException {
         try {
-            new Template(null, new FailingReader(new IOException("test")), new Configuration());
+            new Template(null, new FailingReader(new IOException("test")), new TestConfigurationBuilder().build());
             fail();
         } catch (IOException e) {
             assertEquals("test", e.getMessage());
@@ -99,7 +100,7 @@ public class JavaCCExceptionAsEOFFixTest {
     @Test
     public void testRuntimeException() throws IOException {
         try {
-            new Template(null, new FailingReader(new NullPointerException("test")), new Configuration());
+            new Template(null, new FailingReader(new NullPointerException("test")), new TestConfigurationBuilder().build());
             fail();
         } catch (NullPointerException e) {
             assertEquals("test", e.getMessage());
@@ -109,7 +110,7 @@ public class JavaCCExceptionAsEOFFixTest {
     @Test
     public void testError() throws IOException {
         try {
-            new Template(null, new FailingReader(new OutOfMemoryError("test")), new Configuration());
+            new Template(null, new FailingReader(new OutOfMemoryError("test")), new TestConfigurationBuilder().build());
             fail();
         } catch (OutOfMemoryError e) {
             assertEquals("test", e.getMessage());
@@ -118,7 +119,7 @@ public class JavaCCExceptionAsEOFFixTest {
 
     @Test
     public void testNoException() throws IOException {
-        Template t = new Template(null, new FailingReader(null), new Configuration());
+        Template t = new Template(null, new FailingReader(null), new TestConfigurationBuilder().build());
         assertEquals("abc", t.toString());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
index 438ee28..1903e05 100644
--- a/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
+++ b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
@@ -21,6 +21,7 @@ package org.apache.freemarker.core;
 
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class MiscErrorMessagesTest extends TemplateTest {
@@ -32,8 +33,8 @@ public class MiscErrorMessagesTest extends TemplateTest {
     
     @Test
     public void wrongTemplateNameFormat() {
-        getConfiguration().setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
-        
+        setConfiguration(new TestConfigurationBuilder().templateNameFormat(DefaultTemplateNameFormat.INSTANCE).build());
+
         assertErrorContains("<#include 'foo:/bar:baaz'>", "Malformed template name", "':'");
         assertErrorContains("<#include '../baaz'>", "Malformed template name", "root");
         assertErrorContains("<#include '\u0000'>", "Malformed template name", "\\u0000");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
index c5a025f..5fcee97 100644
--- a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
+++ b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
@@ -30,6 +30,7 @@ import org.apache.freemarker.core.Environment.Namespace;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 /**
@@ -39,11 +40,11 @@ public class MistakenlyPublicImportAPIsTest {
 
     @Test
     public void testImportCopying() throws IOException, TemplateException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("imp1", "<#macro m>1</#macro>");
         tl.putTemplate("imp2", "<#assign x = 2><#macro m>${x}</#macro>");
-        cfg.setTemplateLoader(tl);
+
+        Configuration cfg = new TestConfigurationBuilder().templateLoader(tl).build();
         
         Template t1 = new Template(null, "<#import 'imp1' as i1><#import 'imp2' as i2>", cfg);
         List<ASTDirImport> imports = t1.getImports();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
index f1eb7bd..9c87e61 100644
--- a/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
+++ b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
@@ -28,6 +28,7 @@ import java.util.Map;
 
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 /**
@@ -35,7 +36,7 @@ import org.junit.Test;
  */
 public class MistakenlyPublicMacroAPIsTest {
 
-    private final Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+    private final Configuration cfg = new TestConfigurationBuilder().build();
     
     /**
      * Getting the macros from one template, and adding them to another.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/NewBiObjectWrapperRestrictionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/NewBiObjectWrapperRestrictionTest.java b/src/test/java/org/apache/freemarker/core/NewBiObjectWrapperRestrictionTest.java
index 40dfe13..d865deb 100644
--- a/src/test/java/org/apache/freemarker/core/NewBiObjectWrapperRestrictionTest.java
+++ b/src/test/java/org/apache/freemarker/core/NewBiObjectWrapperRestrictionTest.java
@@ -22,16 +22,15 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 
 import org.apache.freemarker.test.TemplateTest;
-import org.apache.freemarker.test.util.FullyCustomObjectWrapper;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.apache.freemarker.test.util.EntirelyCustomObjectWrapper;
 import org.junit.Test;
 
 public class NewBiObjectWrapperRestrictionTest extends TemplateTest {
 
     @Override
-    protected Configuration createConfiguration() throws Exception {
-        Configuration cfg = super.createConfiguration();
-        cfg.setObjectWrapper(new FullyCustomObjectWrapper());
-        return cfg;
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return new TestConfigurationBuilder().objectWrapper(new EntirelyCustomObjectWrapper()).build();
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
index 73e03f6..49534fa 100644
--- a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
@@ -356,7 +356,7 @@ public class ObjectBuilderSettingsTest {
 
     @Test
     public void configurationPropertiesTest() throws Exception {
-        final Configuration cfg = new Configuration(Configuration.getVersion());
+        final Configuration.Builder cfgB = new Configuration.Builder(Configuration.getVersion());
         
         {
             Properties props = new Properties();
@@ -366,23 +366,23 @@ public class ObjectBuilderSettingsTest {
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyArithmeticEngine");
             props.setProperty(MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateExceptionHandler");
-            props.setProperty(Configuration.CACHE_STORAGE_KEY,
+            props.setProperty(Configuration.ExtendableBuilder.CACHE_STORAGE_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyCacheStorage()");
             props.setProperty(MutableProcessingConfiguration.NEW_BUILTIN_CLASS_RESOLVER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyNewBuiltinClassResolver()");
-            props.setProperty(Configuration.SOURCE_ENCODING_KEY, "utf-8");
-            props.setProperty(Configuration.TEMPLATE_LOADER_KEY,
+            props.setProperty(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY, "utf-8");
+            props.setProperty(Configuration.ExtendableBuilder.TEMPLATE_LOADER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateLoader()");
-            cfg.setSettings(props);
-            assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
+            cfgB.setSettings(props);
+            assertEquals(DefaultObjectWrapper.class, cfgB.getObjectWrapper().getClass());
             assertEquals(
-                    Configuration.VERSION_3_0_0, ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
-            assertEquals(DummyArithmeticEngine.class, cfg.getArithmeticEngine().getClass());
-            assertEquals(DummyTemplateExceptionHandler.class, cfg.getTemplateExceptionHandler().getClass());
-            assertEquals(DummyCacheStorage.class, cfg.getCacheStorage().getClass());
-            assertEquals(DummyNewBuiltinClassResolver.class, cfg.getNewBuiltinClassResolver().getClass());
-            assertEquals(DummyTemplateLoader.class, cfg.getTemplateLoader().getClass());
-            assertEquals(StandardCharsets.UTF_8, cfg.getSourceEncoding());
+                    Configuration.VERSION_3_0_0, ((DefaultObjectWrapper) cfgB.getObjectWrapper()).getIncompatibleImprovements());
+            assertEquals(DummyArithmeticEngine.class, cfgB.getArithmeticEngine().getClass());
+            assertEquals(DummyTemplateExceptionHandler.class, cfgB.getTemplateExceptionHandler().getClass());
+            assertEquals(DummyCacheStorage.class, cfgB.getCacheStorage().getClass());
+            assertEquals(DummyNewBuiltinClassResolver.class, cfgB.getNewBuiltinClassResolver().getClass());
+            assertEquals(DummyTemplateLoader.class, cfgB.getTemplateLoader().getClass());
+            assertEquals(StandardCharsets.UTF_8, cfgB.getSourceEncoding());
         }
         
         {
@@ -392,19 +392,19 @@ public class ObjectBuilderSettingsTest {
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyArithmeticEngine(x = 1)");
             props.setProperty(MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateExceptionHandler(x = 1)");
-            props.setProperty(Configuration.CACHE_STORAGE_KEY,
+            props.setProperty(Configuration.ExtendableBuilder.CACHE_STORAGE_KEY,
                     "soft: 500, strong: 100");
             props.setProperty(MutableProcessingConfiguration.NEW_BUILTIN_CLASS_RESOLVER_KEY,
                     "allows_nothing");
-            cfg.setSettings(props);
-            assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
-            assertEquals(1, ((DummyArithmeticEngine) cfg.getArithmeticEngine()).getX());
-            assertEquals(1, ((DummyTemplateExceptionHandler) cfg.getTemplateExceptionHandler()).getX());
+            cfgB.setSettings(props);
+            assertEquals(DefaultObjectWrapper.class, cfgB.getObjectWrapper().getClass());
+            assertEquals(1, ((DummyArithmeticEngine) cfgB.getArithmeticEngine()).getX());
+            assertEquals(1, ((DummyTemplateExceptionHandler) cfgB.getTemplateExceptionHandler()).getX());
             assertEquals(Configuration.VERSION_3_0_0,
-                    ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
-            assertEquals(500, ((MruCacheStorage) cfg.getCacheStorage()).getSoftSizeLimit());
-            assertEquals(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER, cfg.getNewBuiltinClassResolver());
-            assertEquals(StandardCharsets.UTF_8, cfg.getSourceEncoding());
+                    ((DefaultObjectWrapper) cfgB.getObjectWrapper()).getIncompatibleImprovements());
+            assertEquals(500, ((MruCacheStorage) cfgB.getCacheStorage()).getSoftSizeLimit());
+            assertEquals(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER, cfgB.getNewBuiltinClassResolver());
+            assertEquals(StandardCharsets.UTF_8, cfgB.getSourceEncoding());
         }
 
         {
@@ -412,22 +412,22 @@ public class ObjectBuilderSettingsTest {
             props.setProperty(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "Default");
             props.setProperty(MutableProcessingConfiguration.ARITHMETIC_ENGINE_KEY, "bigdecimal");
             props.setProperty(MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY, "rethrow");
-            cfg.setSettings(props);
-            assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
-            assertSame(BigDecimalArithmeticEngine.INSTANCE, cfg.getArithmeticEngine());
-            assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfg.getTemplateExceptionHandler());
+            cfgB.setSettings(props);
+            assertEquals(DefaultObjectWrapper.class, cfgB.getObjectWrapper().getClass());
+            assertSame(BigDecimalArithmeticEngine.INSTANCE, cfgB.getArithmeticEngine());
+            assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfgB.getTemplateExceptionHandler());
             assertEquals(Configuration.VERSION_3_0_0,
-                    ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
+                    ((DefaultObjectWrapper) cfgB.getObjectWrapper()).getIncompatibleImprovements());
         }
         
         {
             Properties props = new Properties();
             props.setProperty(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "DefaultObjectWrapper(3.0.0)");
-            cfg.setSettings(props);
-            assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
+            cfgB.setSettings(props);
+            assertEquals(DefaultObjectWrapper.class, cfgB.getObjectWrapper().getClass());
             assertEquals(
                     Configuration.VERSION_3_0_0,
-                    ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
+                    ((DefaultObjectWrapper) cfgB.getObjectWrapper()).getIncompatibleImprovements());
         }
     }
     
@@ -911,7 +911,7 @@ public class ObjectBuilderSettingsTest {
         assertEqualsEvaled(123, "org.apache.freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST");
         
         // With shorthand class name:
-        assertEqualsEvaled(Configuration.AUTO_DETECT_TAG_SYNTAX, "Configuration.AUTO_DETECT_TAG_SYNTAX");
+        assertEqualsEvaled(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX, "Configuration.AUTO_DETECT_TAG_SYNTAX");
         
         try {
             _ObjectBuilderSettingEvaluator.eval(

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/OptInTemplateClassResolverTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/OptInTemplateClassResolverTest.java b/src/test/java/org/apache/freemarker/core/OptInTemplateClassResolverTest.java
index 569b4c0..06e863c 100644
--- a/src/test/java/org/apache/freemarker/core/OptInTemplateClassResolverTest.java
+++ b/src/test/java/org/apache/freemarker/core/OptInTemplateClassResolverTest.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Set;
 
 import org.apache.freemarker.core.util.OptInTemplateClassResolver;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 
 import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
@@ -51,10 +52,11 @@ public class OptInTemplateClassResolverTest extends TestCase {
     private OptInTemplateClassResolver resolver = new OptInTemplateClassResolver(
             ALLOWED_CLASSES, TRUSTED_TEMPLATES);
     
-    private Configuration dummyCfg = new Configuration();
-    private Template dummyTemp = Template.createPlainTextTemplate("foo.ftl", "", dummyCfg);
-    
+
     public void testOptIn() throws TemplateException {
+        Template dummyTemp = Template.createPlainTextTemplate("foo.ftl", "",
+                new TestConfigurationBuilder().build());
+
         assertEquals(String.class, resolver.resolve("java.lang.String", null, dummyTemp));
         assertEquals(Integer.class, resolver.resolve("java.lang.Integer", null, dummyTemp));
         try {
@@ -66,18 +68,20 @@ public class OptInTemplateClassResolverTest extends TestCase {
     }
 
     public void testTrusted() throws TemplateException {
+        Configuration cfg = new TestConfigurationBuilder().build();
+
         assertEquals(Long.class, resolver.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("lib/foo.ftl", "", dummyCfg)));
+                Template.createPlainTextTemplate("lib/foo.ftl", "", cfg)));
         assertEquals(String.class, resolver.resolve("java.lang.String", null,
-                Template.createPlainTextTemplate("lib/foo.ftl", "", dummyCfg)));
+                Template.createPlainTextTemplate("lib/foo.ftl", "", cfg)));
         assertEquals(Long.class, resolver.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("/lib/foo.ftl", "", dummyCfg)));
+                Template.createPlainTextTemplate("/lib/foo.ftl", "", cfg)));
         assertEquals(Long.class, resolver.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("include/foo.ftl", "", dummyCfg)));
+                Template.createPlainTextTemplate("include/foo.ftl", "", cfg)));
         assertEquals(Long.class, resolver.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("trusted.ftl", "", dummyCfg)));
+                Template.createPlainTextTemplate("trusted.ftl", "", cfg)));
         assertEquals(Long.class, resolver.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("/trusted.ftl", "", dummyCfg)));
+                Template.createPlainTextTemplate("/trusted.ftl", "", cfg)));
     }
 
     public void testCraftedTrusted() throws TemplateException {
@@ -112,7 +116,8 @@ public class OptInTemplateClassResolverTest extends TestCase {
     public void testTrusted_checkFails(String templateName) {
         try {
             resolver.resolve("java.lang.Long", null,
-                    Template.createPlainTextTemplate(templateName, "", dummyCfg));
+                    Template.createPlainTextTemplate(templateName, "",
+                            new TestConfigurationBuilder().build()));
             fail();
         } catch (TemplateException e) {
             // Expected
@@ -120,87 +125,106 @@ public class OptInTemplateClassResolverTest extends TestCase {
     }
     
     public void testSettingParser() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
-        cfg.setSetting("new_builtin_class_resolver",
-                "trusted_templates: foo.ftl, \"lib/*\"");
-        TemplateClassResolver res = cfg.getNewBuiltinClassResolver();
-        assertEquals(String.class, res.resolve("java.lang.String", null,
-                Template.createPlainTextTemplate("foo.ftl", "", cfg)));
-        assertEquals(String.class, res.resolve("java.lang.String", null,
-                Template.createPlainTextTemplate("lib/bar.ftl", "", cfg)));
-        try {
-            res.resolve("java.lang.String", null,
-                    Template.createPlainTextTemplate("bar.ftl", "", cfg));
-            fail();
-        } catch (TemplateException e) {
-            // Expected
-        }
+        {
+            Configuration cfg = new TestConfigurationBuilder()
+                    .setting(
+                            "new_builtin_class_resolver",
+                            "trusted_templates: foo.ftl, \"lib/*\"")
+                    .build();
 
-        cfg.setSetting("new_builtin_class_resolver",
-                "allowed_classes: java.lang.String, java.lang.Integer");
-        res = cfg.getNewBuiltinClassResolver();
-        assertEquals(String.class, res.resolve("java.lang.String", null,
-                Template.createPlainTextTemplate("foo.ftl", "", cfg)));
-        assertEquals(Integer.class, res.resolve("java.lang.Integer", null,
-                Template.createPlainTextTemplate("foo.ftl", "", cfg)));
-        try {
-            res.resolve("java.lang.Long", null,
-                    Template.createPlainTextTemplate("foo.ftl", "", cfg));
-            fail();
-        } catch (TemplateException e) {
-            // good
+            TemplateClassResolver res = cfg.getNewBuiltinClassResolver();
+            assertEquals(String.class, res.resolve("java.lang.String", null,
+                    Template.createPlainTextTemplate("foo.ftl", "", cfg)));
+            assertEquals(String.class, res.resolve("java.lang.String", null,
+                    Template.createPlainTextTemplate("lib/bar.ftl", "", cfg)));
+            try {
+                res.resolve("java.lang.String", null,
+                        Template.createPlainTextTemplate("bar.ftl", "", cfg));
+                fail();
+            } catch (TemplateException e) {
+                // Expected
+            }
         }
 
-        cfg.setSetting("new_builtin_class_resolver",
-                "trusted_templates: foo.ftl, 'lib/*', " +
-                "allowed_classes: 'java.lang.String', java.lang.Integer");
-        res = cfg.getNewBuiltinClassResolver();
-        assertEquals(String.class, res.resolve("java.lang.String", null,
-                Template.createPlainTextTemplate("x.ftl", "", cfg)));
-        assertEquals(Integer.class, res.resolve("java.lang.Integer", null,
-                Template.createPlainTextTemplate("x.ftl", "", cfg)));
-        try {
-            res.resolve("java.lang.Long", null,
-                    Template.createPlainTextTemplate("x.ftl", "", cfg));
-            fail();
-        } catch (TemplateException e) {
-            // Expected
+        {
+            Configuration cfg = new TestConfigurationBuilder()
+                    .setting(
+                            "new_builtin_class_resolver",
+                            "allowed_classes: java.lang.String, java.lang.Integer")
+                    .build();
+
+            TemplateClassResolver res = cfg.getNewBuiltinClassResolver();
+            assertEquals(String.class, res.resolve("java.lang.String", null,
+                    Template.createPlainTextTemplate("foo.ftl", "", cfg)));
+            assertEquals(Integer.class, res.resolve("java.lang.Integer", null,
+                    Template.createPlainTextTemplate("foo.ftl", "", cfg)));
+            try {
+                res.resolve("java.lang.Long", null,
+                        Template.createPlainTextTemplate("foo.ftl", "", cfg));
+                fail();
+            } catch (TemplateException e) {
+                // good
+            }
         }
-        assertEquals(Long.class, res.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("foo.ftl", "", cfg)));
-        assertEquals(Long.class, res.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("lib/bar.ftl", "", cfg)));
-        try {
-            res.resolve("java.lang.Long", null,
-                    Template.createPlainTextTemplate("x.ftl", "", cfg));
-            fail();
-        } catch (TemplateException e) {
-            // Expected
+
+        {
+            Configuration cfg = new TestConfigurationBuilder()
+                    .setting(
+                            "new_builtin_class_resolver",
+                            "trusted_templates: foo.ftl, 'lib/*', allowed_classes: 'java.lang.String',"
+                            + " java.lang.Integer")
+                    .build();
+            TemplateClassResolver res = cfg.getNewBuiltinClassResolver();
+            assertEquals(String.class, res.resolve("java.lang.String", null,
+                    Template.createPlainTextTemplate("x.ftl", "", cfg)));
+            assertEquals(Integer.class, res.resolve("java.lang.Integer", null,
+                    Template.createPlainTextTemplate("x.ftl", "", cfg)));
+            try {
+                res.resolve("java.lang.Long", null,
+                        Template.createPlainTextTemplate("x.ftl", "", cfg));
+                fail();
+            } catch (TemplateException e) {
+                // Expected
+            }
+            assertEquals(Long.class, res.resolve("java.lang.Long", null,
+                    Template.createPlainTextTemplate("foo.ftl", "", cfg)));
+            assertEquals(Long.class, res.resolve("java.lang.Long", null,
+                    Template.createPlainTextTemplate("lib/bar.ftl", "", cfg)));
+            try {
+                res.resolve("java.lang.Long", null,
+                        Template.createPlainTextTemplate("x.ftl", "", cfg));
+                fail();
+            } catch (TemplateException e) {
+                // Expected
+            }
         }
         
         try {
-            cfg.setSetting("new_builtin_class_resolver", "wrong: foo");
+            new TestConfigurationBuilder().setSetting("new_builtin_class_resolver", "wrong: foo");
             fail();
         } catch (ConfigurationException e) {
             // Expected
         }
-        
-        cfg.setSetting("new_builtin_class_resolver",
-                "\"allowed_classes\"  :  java.lang.String  ,  " +
-                "'trusted_templates' :\"lib:*\"");
-        res = cfg.getNewBuiltinClassResolver();
-        assertEquals(String.class, res.resolve("java.lang.String", null,
-                Template.createPlainTextTemplate("x.ftl", "", cfg)));
-        try {
-            res.resolve("java.lang.Long", null,
-                    Template.createPlainTextTemplate("x.ftl", "", cfg));
-            fail();
-        } catch (TemplateException e) {
-            // Expected
+
+        {
+            Configuration cfg = new TestConfigurationBuilder()
+                    .setting(
+                            "new_builtin_class_resolver",
+                            "\"allowed_classes\"  :  java.lang.String  ,  'trusted_templates' :\"lib:*\"")
+                    .build();
+            TemplateClassResolver res = cfg.getNewBuiltinClassResolver();
+            assertEquals(String.class, res.resolve("java.lang.String", null,
+                    Template.createPlainTextTemplate("x.ftl", "", cfg)));
+            try {
+                res.resolve("java.lang.Long", null,
+                        Template.createPlainTextTemplate("x.ftl", "", cfg));
+                fail();
+            } catch (TemplateException e) {
+                // Expected
+            }
+            assertEquals(Long.class, res.resolve("java.lang.Long", null,
+                    Template.createPlainTextTemplate("lib:bar.ftl", "", cfg)));
         }
-        assertEquals(Long.class, res.resolve("java.lang.Long", null,
-                Template.createPlainTextTemplate("lib:bar.ftl", "", cfg)));
     }
     
 }


[02/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/manualtest/ExamplesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/ExamplesTest.java b/src/test/java/org/apache/freemarker/manualtest/ExamplesTest.java
deleted file mode 100644
index c9cbffa..0000000
--- a/src/test/java/org/apache/freemarker/manualtest/ExamplesTest.java
+++ /dev/null
@@ -1,59 +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.manualtest;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
-import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
-import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
-import org.apache.freemarker.test.TemplateTest;
-import org.junit.Ignore;
-
-@Ignore
-public abstract class ExamplesTest extends TemplateTest {
-
-    protected Properties loadPropertiesFile(String name) throws IOException {
-        Properties props = new Properties();
-        InputStream in = getClass().getResourceAsStream(name);
-        try {
-            props.load(in);
-        } finally {
-            in.close();
-        }
-        return props;
-    }
-    
-    @Override
-    protected final Configuration createConfiguration() {
-        Configuration cfg = new Configuration(Configuration.getVersion());
-        setupTemplateLoaders(cfg);
-        return cfg;
-    }
-
-    protected void setupTemplateLoaders(Configuration cfg) {
-        cfg.setTemplateLoader(new MultiTemplateLoader(
-                new ByteArrayTemplateLoader(),
-                new ClassTemplateLoader(getClass(), "")));
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java b/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java
index e6a244a..a676bc4 100644
--- a/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java
+++ b/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateExceptionHandler;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.junit.Test;
 
 public class GettingStartedExample {
@@ -36,18 +37,19 @@ public class GettingStartedExample {
         /* ------------------------------------------------------------------------ */    
         /* You should do this ONLY ONCE in the whole application life-cycle:        */    
     
-        /* Create and adjust the configuration singleton */
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setClassForTemplateLoading(GettingStartedExample.class, "");
-        cfg.setSourceEncoding(StandardCharsets.UTF_8);
-        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
-        cfg.setLogTemplateExceptions(false);
+        /* Create the configuration singleton (using builder pattern) */
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .templateLoader(new ClassTemplateLoader(GettingStartedExample.class, ""))
+                .sourceEncoding(StandardCharsets.UTF_8)
+                .templateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER)
+                .logTemplateExceptions(false)
+                .build();
 
         /* ------------------------------------------------------------------------ */    
         /* You usually do these for MULTIPLE TIMES in the application life-cycle:   */    
 
         /* Create a data-model */
-        Map root = new HashMap();
+        Map<String, Object> root = new HashMap();
         root.put("user", "Big Joe");
         Product latest = new Product();
         latest.setUrl("products/greenmouse.html");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java b/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
index 6e8cc74..8c515bd 100644
--- a/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
+++ b/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
@@ -38,142 +38,147 @@ import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationF
 import org.apache.freemarker.core.templateresolver.OrMatcher;
 import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
 import org.apache.freemarker.core.util._DateUtil;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
-public class TemplateConfigurationExamples extends ExamplesTest {
+public class TemplateConfigurationExamples extends TemplateTest {
 
     @Test
-    public void example1() throws Exception {
-        Configuration cfg = getConfiguration();
+    public void example1JavaCfg() throws Exception {
+        example1(true);
+    }
 
-        addTemplate("t.xml", "");
-        
-        TemplateConfiguration.Builder tcbUTF8XML = new TemplateConfiguration.Builder();
-        tcbUTF8XML.setSourceEncoding(StandardCharsets.UTF_8);
-        tcbUTF8XML.setOutputFormat(XMLOutputFormat.INSTANCE);
-
-        {
-            cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
-                    new FileExtensionMatcher("xml"), tcbUTF8XML.build()));
-            
-            Template t = cfg.getTemplate("t.xml");
-            assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
-            assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
-        }
+    @Test
+    public void example1PropertiesCfg() throws Exception {
+        example1(false);
+    }
+
+    private void example1(boolean javaCfg) throws Exception {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(this.getClass());
+        if (javaCfg) {
+            cfgB.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
+                    new FileExtensionMatcher("xml"),
+                    new TemplateConfiguration.Builder()
+                            .sourceEncoding(StandardCharsets.UTF_8)
+                            .outputFormat(XMLOutputFormat.INSTANCE)
+                            .build()));
 
-        {
-            cfg.setTemplateConfigurations(null);
-            cfg.setSettings(loadPropertiesFile("TemplateConfigurationExamples1.properties"));
-            
-            Template t = cfg.getTemplate("t.xml");
-            assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
-            assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
+        } else {
+            cfgB.setTemplateConfigurations(null);
+            cfgB.setSettings(loadPropertiesFile("TemplateConfigurationExamples1.properties"));
         }
+        setConfiguration(cfgB.build());
+
+        addTemplate("t.xml", "");
+
+        Template t = getConfiguration().getTemplate("t.xml");
+        assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
+        assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
     }
 
     @Test
-    public void example2() throws Exception {
-        Configuration cfg = getConfiguration();
-        
+    public void example2JavaCfg() throws Exception {
+        example2(true);
+    }
+
+    @Test
+    public void example2PropertiesCfg() throws Exception {
+        example2(false);
+    }
+
+    private void example2(boolean javaCfg) throws Exception {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(this.getClass());
+        if (javaCfg) {
+            cfgB.setTemplateConfigurations(
+                    new ConditionalTemplateConfigurationFactory(
+                            new PathGlobMatcher("mail/**"),
+                            new FirstMatchTemplateConfigurationFactory(
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new FileNameGlobMatcher("*.subject.*"),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(PlainTextOutputFormat.INSTANCE)
+                                                    .build()),
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new FileNameGlobMatcher("*.body.*"),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(HTMLOutputFormat.INSTANCE)
+                                                    .build())
+                            )
+                            .noMatchErrorDetails(
+                                    "Mail template names must contain \".subject.\" or \".body.\"!")));
+        } else{
+            cfgB.setSettings(loadPropertiesFile("TemplateConfigurationExamples2.properties"));
+        }
+        setConfiguration(cfgB.build());
+
         addTemplate("t.subject.ftl", "");
         addTemplate("mail/t.subject.ftl", "");
         addTemplate("mail/t.body.ftl", "");
 
-        TemplateConfiguration.Builder tcbSubject = new TemplateConfiguration.Builder();
-        tcbSubject.setOutputFormat(PlainTextOutputFormat.INSTANCE);
-        
-        TemplateConfiguration.Builder tcbBody = new TemplateConfiguration.Builder();
-        tcbBody.setOutputFormat(HTMLOutputFormat.INSTANCE);
-        
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(
-                        new PathGlobMatcher("mail/**"),
-                        new FirstMatchTemplateConfigurationFactory(
-                                new ConditionalTemplateConfigurationFactory(
-                                        new FileNameGlobMatcher("*.subject.*"),
-                                        tcbSubject.build()),
-                                new ConditionalTemplateConfigurationFactory(
-                                        new FileNameGlobMatcher("*.body.*"),
-                                        tcbBody.build())
-                                )
-                                .noMatchErrorDetails("Mail template names must contain \".subject.\" or \".body.\"!")
-                        ));
-        
-        assertEquals(UndefinedOutputFormat.INSTANCE, cfg.getTemplate("t.subject.ftl").getOutputFormat());
-        assertEquals(PlainTextOutputFormat.INSTANCE, cfg.getTemplate("mail/t.subject.ftl").getOutputFormat());
-        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("mail/t.body.ftl").getOutputFormat());
-        
-        // From properties:
-        
-        cfg.setTemplateConfigurations(null);
-        cfg.setSettings(loadPropertiesFile("TemplateConfigurationExamples2.properties"));
-        
+        Configuration cfg = getConfiguration();
         assertEquals(UndefinedOutputFormat.INSTANCE, cfg.getTemplate("t.subject.ftl").getOutputFormat());
         assertEquals(PlainTextOutputFormat.INSTANCE, cfg.getTemplate("mail/t.subject.ftl").getOutputFormat());
         assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("mail/t.body.ftl").getOutputFormat());
     }
 
     @Test
-    public void example3() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
-        cfg.setSharedVariable("ts", new Date(1440431606011L));
-        
+    public void example3JavaCfg() throws Exception {
+        example3(true);
+    }
+
+    @Test
+    public void example3PropertiesCfg() throws Exception {
+        example3(false);
+    }
+
+    private void example3(boolean javaCfg) throws Exception {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(this.getClass())
+                .sourceEncoding(StandardCharsets.ISO_8859_1);
+        if (javaCfg) {
+            cfgB.setTemplateConfigurations(
+                    new MergingTemplateConfigurationFactory(
+                            new ConditionalTemplateConfigurationFactory(
+                                    new FileNameGlobMatcher("*.stats.*"),
+                                    new TemplateConfiguration.Builder()
+                                            .dateTimeFormat("iso")
+                                            .dateFormat("iso")
+                                            .timeFormat("iso")
+                                            .timeZone(_DateUtil.UTC)
+                                            .build()),
+                            new ConditionalTemplateConfigurationFactory(
+                                    new PathGlobMatcher("mail/**"),
+                                    new TemplateConfiguration.Builder()
+                                            .sourceEncoding(StandardCharsets.UTF_8)
+                                            .build()),
+                            new FirstMatchTemplateConfigurationFactory(
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new FileExtensionMatcher("xml"),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(XMLOutputFormat.INSTANCE)
+                                                    .build()),
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new OrMatcher(
+                                                    new FileExtensionMatcher("html"),
+                                                    new FileExtensionMatcher("htm")),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(HTMLOutputFormat.INSTANCE)
+                                                    .build())
+                            ).allowNoMatch(true)));
+        } else {
+            cfgB.setSettings(loadPropertiesFile("TemplateConfigurationExamples3.properties"));
+        }
+        setConfiguration(cfgB.build());
+
         addTemplate("t.stats.html", "${ts?datetime} ${ts?date} ${ts?time}");
         addTemplate("t.html", "");
         addTemplate("t.htm", "");
         addTemplate("t.xml", "");
         addTemplate("mail/t.html", "");
 
-        TemplateConfiguration.Builder tcbStats = new TemplateConfiguration.Builder();
-        tcbStats.setDateTimeFormat("iso");
-        tcbStats.setDateFormat("iso");
-        tcbStats.setTimeFormat("iso");
-        tcbStats.setTimeZone(_DateUtil.UTC);
-
-        TemplateConfiguration.Builder tcbMail = new TemplateConfiguration.Builder();
-        tcbMail.setSourceEncoding(StandardCharsets.UTF_8);
-        
-        TemplateConfiguration.Builder tcbHTML = new TemplateConfiguration.Builder();
-        tcbHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
-        
-        TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
-        tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
-        
-        cfg.setTemplateConfigurations(
-                new MergingTemplateConfigurationFactory(
-                        new ConditionalTemplateConfigurationFactory(
-                                new FileNameGlobMatcher("*.stats.*"),
-                                tcbStats.build()),
-                        new ConditionalTemplateConfigurationFactory(
-                                new PathGlobMatcher("mail/**"),
-                                tcbMail.build()),
-                        new FirstMatchTemplateConfigurationFactory(
-                                new ConditionalTemplateConfigurationFactory(
-                                        new FileExtensionMatcher("xml"),
-                                        tcbXML.build()),
-                                new ConditionalTemplateConfigurationFactory(
-                                        new OrMatcher(
-                                                new FileExtensionMatcher("html"),
-                                                new FileExtensionMatcher("htm")),
-                                        tcbHTML.build())
-                        ).allowNoMatch(true)
-                )
-        );
-        
-        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.html").getOutputFormat());
-        assertEquals(StandardCharsets.ISO_8859_1, cfg.getTemplate("t.html").getActualSourceEncoding());
-        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.htm").getOutputFormat());
-        assertEquals(XMLOutputFormat.INSTANCE, cfg.getTemplate("t.xml").getOutputFormat());
-        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.stats.html").getOutputFormat());
-        assertOutputForNamed("t.stats.html", "2015-08-24T15:53:26.011Z 2015-08-24 15:53:26.011Z");
-        assertEquals(StandardCharsets.UTF_8, cfg.getTemplate("mail/t.html").getActualSourceEncoding());
-        
-        // From properties:
-        
-        cfg.setTemplateConfigurations(null);
-        cfg.setSettings(loadPropertiesFile("TemplateConfigurationExamples3.properties"));
-        
+        addToDataModel("ts", new Date(1440431606011L));
+
+        Configuration cfg = getConfiguration();
         assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.html").getOutputFormat());
         assertEquals(StandardCharsets.ISO_8859_1, cfg.getTemplate("t.html").getActualSourceEncoding());
         assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.htm").getOutputFormat());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java b/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
index 85b4c13..bbe7c49 100644
--- a/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
@@ -41,6 +41,7 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
 import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
 import org.junit.Before;
@@ -415,7 +416,7 @@ public class FreemarkerServletTest {
         }
 
         MockServletConfig servletConfig = new MockServletConfig(servletContext);
-        servletConfig.addInitParameter(Configuration.SOURCE_ENCODING_KEY, "UtF-8");
+        servletConfig.addInitParameter(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY, "UtF-8");
         if (ctInitParam != null) {
             servletConfig.addInitParameter(INIT_PARAM_CONTENT_TYPE, ctInitParam);
         }
@@ -537,34 +538,35 @@ public class FreemarkerServletTest {
         private Charset lastOutputEncoding;
 
         @Override
-        protected Configuration createConfiguration() {
-            Configuration cfg = super.createConfiguration();
-            // Needed for the TemplateConfiguration that sets outputEncoding:
-            cfg.setIncompatibleImprovements(Configuration.VERSION_3_0_0);
-
-            // Set a test runner environment independent default locale:
-            cfg.setLocale(DEFAULT_LOCALE);
-            cfg.setSourceEncoding(CFG_DEFAULT_ENCODING);
-
-            {
-                TemplateConfiguration.Builder outUtf8TCB = new TemplateConfiguration.Builder();
-                outUtf8TCB.setOutputEncoding(StandardCharsets.UTF_8);
-                
-                TemplateConfiguration.Builder srcUtf8TCB = new TemplateConfiguration.Builder();
-                srcUtf8TCB.setSourceEncoding(StandardCharsets.UTF_8);
-                
-                cfg.setTemplateConfigurations(
-                        new FirstMatchTemplateConfigurationFactory(
-                                new ConditionalTemplateConfigurationFactory(
-                                        new FileNameGlobMatcher(FOO_SRC_UTF8_FTL), srcUtf8TCB.build()),
-                                new ConditionalTemplateConfigurationFactory(
-                                        new FileNameGlobMatcher(FOO_OUT_UTF8_FTL), outUtf8TCB.build())
-                        )
-                        .allowNoMatch(true)
-                );
-            }
-
-            return cfg;
+        protected Configuration.ExtendableBuilder<?> createConfigurationBuilder() {
+            return new FreemarkerServletConfigurationBuilder(TestFreemarkerServlet.this, Configuration.VERSION_3_0_0) {
+
+                @Override
+                protected Locale getDefaultLocale() {
+                    return DEFAULT_LOCALE;
+                }
+
+                @Override
+                protected Charset getDefaultSourceEncoding() {
+                    return CFG_DEFAULT_ENCODING;
+                }
+
+                @Override
+                protected TemplateConfigurationFactory getDefaultTemplateConfigurations() {
+                    TemplateConfiguration.Builder outUtf8TCB = new TemplateConfiguration.Builder();
+                    outUtf8TCB.setOutputEncoding(StandardCharsets.UTF_8);
+
+                    TemplateConfiguration.Builder srcUtf8TCB = new TemplateConfiguration.Builder();
+                    srcUtf8TCB.setSourceEncoding(StandardCharsets.UTF_8);
+
+                    return new FirstMatchTemplateConfigurationFactory(
+                            new ConditionalTemplateConfigurationFactory(
+                                    new FileNameGlobMatcher(FOO_SRC_UTF8_FTL), srcUtf8TCB.build()),
+                            new ConditionalTemplateConfigurationFactory(
+                                    new FileNameGlobMatcher(FOO_OUT_UTF8_FTL), outUtf8TCB.build())
+                    ).allowNoMatch(true);
+                }
+            };
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/servlet/InitParamParserTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/servlet/InitParamParserTest.java b/src/test/java/org/apache/freemarker/servlet/InitParamParserTest.java
index 84b30af..b0ab8e3 100644
--- a/src/test/java/org/apache/freemarker/servlet/InitParamParserTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/InitParamParserTest.java
@@ -24,7 +24,6 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.util.Collections;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.MockServletContext;
 import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
@@ -62,12 +61,10 @@ public class InitParamParserTest {
 
     @Test
     public void testCreateTemplateLoader() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-
         {
             ClassTemplateLoader ctl = (ClassTemplateLoader) InitParamParser.createTemplateLoader(
                     "classpath:templates",
-                    cfg, getClass(), null);
+                    getClass(), null);
             assertEquals("templates/", ctl.getBasePackagePath());
             assertEquals(Boolean.FALSE, ctl.getURLConnectionUsesCaches());
         }
@@ -75,7 +72,7 @@ public class InitParamParserTest {
         {
             ClassTemplateLoader ctl = (ClassTemplateLoader) InitParamParser.createTemplateLoader(
                     "classpath:templates?settings(URLConnectionUsesCaches=true)",
-                    cfg, getClass(), null);
+                    getClass(), null);
             assertEquals("templates/", ctl.getBasePackagePath());
             assertEquals(Boolean.TRUE, ctl.getURLConnectionUsesCaches());
         }
@@ -88,7 +85,7 @@ public class InitParamParserTest {
                     + "classpath:templates, "
                     + "classpath:foo/templates?settings(URLConnectionUsesCaches=true)"
                     + "]",
-                    cfg, getClass(), new MockServletContext());
+                    getClass(), new MockServletContext());
 
             assertEquals(4, mtl.getTemplateLoaderCount());
             

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java b/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
index 34d00ec..d383c1d 100644
--- a/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
@@ -40,12 +40,12 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateExceptionHandler;
 import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.apache.freemarker.servlet.FreemarkerServlet;
+import org.apache.freemarker.servlet.FreemarkerServletConfigurationBuilder;
 import org.apache.freemarker.servlet.WebAppTemplateLoader;
 import org.apache.freemarker.test.servlet.DefaultModel2TesterAction;
 import org.apache.freemarker.test.servlet.WebAppTestCase;
@@ -406,15 +406,31 @@ public class RealServletContainertTest extends WebAppTestCase {
     public static class AssertCustomizedDefaultsFreemarkerServlet extends AssertingFreemarkerServlet {
 
         @Override
-        protected Configuration createConfiguration() {
-            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
-            cfg.setLogTemplateExceptions(true);
-            DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
-            bwb.setUseModelCache(true);
-            cfg.setObjectWrapper(bwb.build());
-            cfg.setTemplateLoader(new WebAppTemplateLoader(getServletContext()));
-            return cfg;
+        protected Configuration.ExtendableBuilder createConfigurationBuilder() {
+            return new FreemarkerServletConfigurationBuilder(
+                    AssertCustomizedDefaultsFreemarkerServlet.this, Configuration.VERSION_3_0_0) {
+                @Override
+                protected TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
+                    return TemplateExceptionHandler.RETHROW_HANDLER;
+                }
+
+                @Override
+                protected boolean getDefaultLogTemplateExceptions() {
+                    return true;
+                }
+
+                @Override
+                protected ObjectWrapper getDefaultObjectWrapper() {
+                    DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+                    bwb.setUseModelCache(true);
+                    return bwb.build();
+                }
+
+                @Override
+                protected TemplateLoader getDefaultTemplateLoader() {
+                    return new WebAppTemplateLoader(getServletContext());
+                }
+            };
         }
 
         @Override
@@ -451,23 +467,30 @@ public class RealServletContainertTest extends WebAppTestCase {
         }
 
         @Override
-        protected ObjectWrapperAndUnwrapper createDefaultObjectWrapper() {
-            DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
-            bwb.setUseModelCache(true);
-            assertEquals(Configuration.VERSION_3_0_0, bwb.getIncompatibleImprovements());
-            return bwb.build();
+        protected Configuration.ExtendableBuilder createConfigurationBuilder() {
+            return new FreemarkerServletConfigurationBuilder(
+                    AssertObjectWrapperDefaults1FreemarkerServlet.this, Configuration.VERSION_3_0_0) {
+                @Override
+                protected ObjectWrapper getDefaultObjectWrapper() {
+                    DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+                    bwb.setUseModelCache(true);
+                    assertEquals(Configuration.VERSION_3_0_0, bwb.getIncompatibleImprovements());
+                    return bwb.build();
+                }
+            };
         }
-        
+
     }
 
     public static class AssertObjectWrapperDefaults2FreemarkerServlet extends
             AssertObjectWrapperDefaults1FreemarkerServlet {
 
         @Override
-        protected Configuration createConfiguration() {
-            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            cfg.setObjectWrapper(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
-            return cfg;
+        protected Configuration.ExtendableBuilder createConfigurationBuilder() {
+            Configuration.ExtendableBuilder cfgB = super.createConfigurationBuilder();
+            // This is not a proper way of doing this, but consistent behavior still needs to be tested.
+            cfgB.setObjectWrapper(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
+            return cfgB;
         }
         
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/TemplateTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/TemplateTest.java b/src/test/java/org/apache/freemarker/test/TemplateTest.java
index d42984f..ac4de52 100644
--- a/src/test/java/org/apache/freemarker/test/TemplateTest.java
+++ b/src/test/java/org/apache/freemarker/test/TemplateTest.java
@@ -28,9 +28,8 @@ import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Locale;
 import java.util.Map;
-import java.util.TimeZone;
+import java.util.Properties;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.freemarker.core.Configuration;
@@ -40,6 +39,7 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
+import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.test.templatesuite.TemplateTestSuite;
 import org.apache.freemarker.test.util.TestUtil;
@@ -56,43 +56,35 @@ public abstract class TemplateTest {
     private Configuration configuration;
     private boolean dataModelCreated;
     private Object dataModel;
+    private Map<String, String> addedTemplates = new HashMap<>();
 
+    /**
+     * Gets the {@link Configuration} used, automaticlly creating and setting if it wasn't yet.
+     */
     protected final Configuration getConfiguration() {
         if (configuration == null) {
             try {
-                configuration = createConfiguration();
-                addCommonTemplates();
-                applyEnvironmentIndependentDefaults();
+                setConfiguration(createDefaultConfiguration());
             } catch (Exception e) {
-                throw new RuntimeException("Failed to set up configuration for the test", e);
+                throw new RuntimeException("Failed to create configuration", e);
             }
         }
         return configuration;
     }
 
     /**
-     * Ensure that the configuration settings don't depend on the machine that runs the test.
+     * @param configuration Usually should be built using {@link TestConfigurationBuilder}; not {@code null}.
      */
-    private void applyEnvironmentIndependentDefaults() {
-        if (!configuration.isLocaleExplicitlySet()) {
-            configuration.setLocale(Locale.US);
-        }
-        if (!configuration.isSourceEncodingExplicitlySet()) {
-            configuration.setSourceEncoding(StandardCharsets.UTF_8);
-        }
-        if (!configuration.isTimeZoneExplicitlySet()) {
-            configuration.setTimeZone(TimeZone.getTimeZone("GMT+1"));
+    protected final void setConfiguration(Configuration configuration) {
+        _NullArgumentException.check("configuration", configuration);
+        if (this.configuration == configuration) {
+            return;
         }
-    }
 
-    protected final void setConfiguration(Configuration configuration) {
         this.configuration = configuration;
+        afterConfigurationSet();
     }
 
-    protected final void dropConfiguration() {
-        configuration = null;
-    }
-    
     protected void assertOutput(String ftl, String expectedOut) throws IOException, TemplateException {
         assertOutput(createTemplate(ftl), expectedOut, false);
     }
@@ -148,10 +140,21 @@ public abstract class TemplateTest {
         return out.toString();
     }
     
-    protected Configuration createConfiguration() throws Exception {
-        return new Configuration(Configuration.VERSION_3_0_0);
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return new TestConfigurationBuilder().build();
     }
-    
+
+    private void afterConfigurationSet() {
+        ensureAddedTemplatesPresent();
+        addCommonTemplates();
+    }
+
+    private void ensureAddedTemplatesPresent() {
+        for (Map.Entry<String, String> ent : addedTemplates.entrySet()) {
+            addTemplate(ent.getKey(), ent.getValue());
+        }
+    }
+
     protected void addCommonTemplates() {
         //
     }
@@ -179,20 +182,16 @@ public abstract class TemplateTest {
         dataModel.put("bean", new TestBean());
         return dataModel;
     }
-    
+
     protected void addTemplate(String name, String content) {
         Configuration cfg = getConfiguration();
         TemplateLoader tl = cfg.getTemplateLoader();
         ByteArrayTemplateLoader btl;
-        if (tl != null) {
-            btl = extractByteArrayTemplateLoader(tl);
-        } else {
-            btl = new ByteArrayTemplateLoader();
-            cfg.setTemplateLoader(btl);
-        }
+        btl = extractByteArrayTemplateLoader(tl);
         btl.putTemplate(name, content.getBytes(StandardCharsets.UTF_8));
+        addedTemplates.put(name, content);
     }
-    
+
     private ByteArrayTemplateLoader extractByteArrayTemplateLoader(TemplateLoader tl) {
         if (tl instanceof MultiTemplateLoader) {
             MultiTemplateLoader mtl = (MultiTemplateLoader) tl;
@@ -207,6 +206,8 @@ public abstract class TemplateTest {
                             + tl);
         } else if (tl instanceof ByteArrayTemplateLoader) {
             return (ByteArrayTemplateLoader) tl;
+        } else if (tl == null) {
+            throw new IllegalStateException("The templateLoader was null in the configuration");
         } else {
             throw new IllegalStateException(
                     "The template loader was already set to a non-ByteArrayTemplateLoader non-MultiTemplateLoader: "
@@ -226,7 +227,18 @@ public abstract class TemplateTest {
             throw new IllegalStateException("Can't add to non-Map data-model: " + dm);
         }
     }
-    
+
+    protected Properties loadPropertiesFile(String name) throws IOException {
+        Properties props = new Properties();
+        InputStream in = getClass().getResourceAsStream(name);
+        try {
+            props.load(in);
+        } finally {
+            in.close();
+        }
+        return props;
+    }
+
     protected Throwable assertErrorContains(String ftl, String... expectedSubstrings) {
         return assertErrorContains(null, ftl, null, expectedSubstrings);
     }
@@ -326,5 +338,5 @@ public abstract class TemplateTest {
         }
         
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/TestConfigurationBuilder.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/TestConfigurationBuilder.java b/src/test/java/org/apache/freemarker/test/TestConfigurationBuilder.java
new file mode 100644
index 0000000..7f869db
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/test/TestConfigurationBuilder.java
@@ -0,0 +1,92 @@
+/*
+ * 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.test;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Configuration.Builder;
+import org.apache.freemarker.core.Version;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
+
+/**
+ * Configuration builder you should use instead of {@link Builder} in unit tests.
+ * It tries to make the behavior of the tests independent of the environment where we run them. For convenience, it
+ * has a {@link ByteArrayTemplateLoader} as the default template loader.
+ */
+public class TestConfigurationBuilder extends Configuration.ExtendableBuilder<TestConfigurationBuilder> {
+
+    private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT+1");
+    private final Class<?> classTemplateLoaderBase;
+    private TemplateLoader defaultTemplateLoader;
+
+    public TestConfigurationBuilder() {
+        this((Version) null);
+    }
+
+    public TestConfigurationBuilder(Class<?> classTemplateLoaderBase) {
+        this(null, classTemplateLoaderBase);
+    }
+
+    public TestConfigurationBuilder(Version incompatibleImprovements) {
+        this(incompatibleImprovements, null);
+    }
+
+    public TestConfigurationBuilder(Version incompatibleImprovements, Class<?> classTemplateLoaderBase) {
+        super(incompatibleImprovements != null ? incompatibleImprovements : Configuration.VERSION_3_0_0);
+        this.classTemplateLoaderBase = classTemplateLoaderBase;
+    }
+
+    @Override
+    protected Locale getDefaultLocale() {
+        return Locale.US;
+    }
+
+    @Override
+    protected Charset getDefaultSourceEncoding() {
+        return StandardCharsets.UTF_8;
+    }
+
+    @Override
+    protected TimeZone getDefaultTimeZone() {
+        return DEFAULT_TIME_ZONE;
+    }
+
+    @Override
+    protected TemplateLoader getDefaultTemplateLoader() {
+        if (defaultTemplateLoader == null) {
+            if (classTemplateLoaderBase == null) {
+                defaultTemplateLoader = new ByteArrayTemplateLoader();
+            } else {
+                defaultTemplateLoader = new MultiTemplateLoader(
+                        new ByteArrayTemplateLoader(),
+                        new ClassTemplateLoader(classTemplateLoaderBase, ""));
+            }
+        }
+        return defaultTemplateLoader;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java b/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
index 5d68588..530ed4d 100644
--- a/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
+++ b/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
@@ -276,6 +276,7 @@ public class WebAppTestCase {
         return temporaryDir.toURI().toString();
     }
 
+    @SuppressFBWarnings("UI_INHERITANCE_UNSAFE_GETRESOURCE")
     private ClassPathResource findWebAppDirectoryResource(String webAppName) throws IOException {
         final String appRelResPath = "webapps/" + webAppName + "/";
         final String relResPath = appRelResPath + "WEB-INF/web.xml";

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
index 3e92a10..9dbccc7 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
@@ -68,6 +68,7 @@ import org.apache.freemarker.core.util._NullWriter;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.dom.NodeModel;
 import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.apache.freemarker.test.templatesuite.models.BooleanAndStringTemplateModel;
 import org.apache.freemarker.test.templatesuite.models.BooleanHash1;
 import org.apache.freemarker.test.templatesuite.models.BooleanHash2;
@@ -115,7 +116,7 @@ public class TemplateTestCase extends FileTestCase {
     private final String expectedFileName;
     private final boolean noOutput;
     
-    private final Configuration conf;
+    private final Configuration.ExtendableBuilder confB;
     private final HashMap<String, Object> dataModel = new HashMap<>();
     
     public TemplateTestCase(String testName, String simpleTestName, String templateName, String expectedFileName, boolean noOutput,
@@ -133,8 +134,8 @@ public class TemplateTestCase extends FileTestCase {
         this.expectedFileName = expectedFileName;
         
         this.noOutput = noOutput;
-        
-        conf = new Configuration(incompatibleImprovements);
+
+        confB = new TestConfigurationBuilder(incompatibleImprovements);
     }
     
     public void setSetting(String param, String value) throws IOException {
@@ -147,13 +148,13 @@ public class TemplateTestCase extends FileTestCase {
             if (!as.equals("as")) fail("Expecting 'as <alias>' in autoimport");
             if (!st.hasMoreTokens()) fail("Expecting alias after 'as' in autoimport");
             String alias = st.nextToken();
-            conf.addAutoImport(alias, libname);
+            confB.addAutoImport(alias, libname);
         } else if ("source_encoding".equals(param)) {
-            conf.setSourceEncoding(Charset.forName(value));
+            confB.setSourceEncoding(Charset.forName(value));
         // INCOMPATIBLE_IMPROVEMENTS is a list here, and was already set in the constructor.
-        } else if (!Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY.equals(param)) {
+        } else if (!Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY.equals(param)) {
             try {
-                conf.setSetting(param, value);
+                confB.setSetting(param, value);
             } catch (ConfigurationException e) {
                 throw new RuntimeException(
                         "Failed to set setting " +
@@ -173,7 +174,7 @@ public class TemplateTestCase extends FileTestCase {
     @Override
     @SuppressWarnings("boxing")
     public void setUp() throws Exception {
-        conf.setTemplateLoader(new CopyrightCommentRemoverTemplateLoader(
+        confB.setTemplateLoader(new CopyrightCommentRemoverTemplateLoader(
                 new FileTemplateLoader(new File(getTestClassDirectory(), "templates"))));
         
         DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
@@ -185,7 +186,7 @@ public class TemplateTestCase extends FileTestCase {
 
         dataModel.put(JAVA_OBJECT_INFO_VAR_NAME, JavaObjectInfo.INSTANCE);
         dataModel.put(TEST_NAME_VAR_NAME, simpleTestName);
-        dataModel.put(ICI_INT_VALUE_VAR_NAME, conf.getIncompatibleImprovements().intValue());
+        dataModel.put(ICI_INT_VALUE_VAR_NAME, confB.getIncompatibleImprovements().intValue());
         
         dataModel.put("message", "Hello, world!");
 
@@ -300,7 +301,7 @@ public class TemplateTestCase extends FileTestCase {
         } else if (simpleTestName.equals("var-layers")) {
             dataModel.put("x", Integer.valueOf(4));
             dataModel.put("z", Integer.valueOf(4));
-            conf.setSharedVariable("y", Integer.valueOf(7));
+            confB.setSharedVariable("y", Integer.valueOf(7));
         } else if (simpleTestName.equals("xml-fragment")) {
             DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
             f.setNamespaceAware(true);
@@ -392,10 +393,10 @@ public class TemplateTestCase extends FileTestCase {
     }
     
     @Override
-    public void runTest() throws IOException {
+    public void runTest() throws IOException, ConfigurationException {
         Template template;
         try {
-            template = conf.getTemplate(templateName);
+            template = confB.build().getTemplate(templateName);
         } catch (IOException e) {
             throw new AssertionFailedError(
                     "Could not load template " + _StringUtil.jQuote(templateName) + ":\n" + getStackTrace(e));
@@ -428,7 +429,7 @@ public class TemplateTestCase extends FileTestCase {
 
     @Override
     protected Charset getFileCharset() {
-        return conf.getOutputEncoding() != null ? conf.getOutputEncoding() : StandardCharsets.UTF_8;
+        return confB.getOutputEncoding() != null ? confB.getOutputEncoding() : StandardCharsets.UTF_8;
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
index f1ca289..0edae99 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
@@ -218,7 +218,7 @@ public class TemplateTestSuite extends TestSuite {
         
         final List<Version> icisToTest;
         {
-            final String testCaseIcis = testCaseSettings.get(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY);
+            final String testCaseIcis = testCaseSettings.get(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY);
                     
             icisToTest = testCaseIcis != null ? parseVersionList(testCaseIcis) : testSuiteIcis;
             if (icisToTest.isEmpty()) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/util/EntirelyCustomObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/util/EntirelyCustomObjectWrapper.java b/src/test/java/org/apache/freemarker/test/util/EntirelyCustomObjectWrapper.java
new file mode 100644
index 0000000..cdcde1f
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/test/util/EntirelyCustomObjectWrapper.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.test.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.SimpleDate;
+import org.apache.freemarker.core.model.impl.SimpleHash;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.model.impl.SimpleSequence;
+
+/**
+ * An object wrapper that doesn't extend {@link DefaultObjectWrapper}.
+ */
+public class EntirelyCustomObjectWrapper implements ObjectWrapper {
+
+    @Override
+    public TemplateModel wrap(Object obj) throws TemplateModelException {
+        if (obj == null) {
+            return null;
+        }
+
+        if (obj instanceof TemplateModel) {
+            return (TemplateModel) obj;
+        }
+        if (obj instanceof TemplateModelAdapter) {
+            return ((TemplateModelAdapter) obj).getTemplateModel();
+        }
+
+        if (obj instanceof String) {
+            return new SimpleScalar((String) obj);
+        }
+        if (obj instanceof Number) {
+            return new SimpleNumber((Number) obj);
+        }
+        if (obj instanceof Boolean) {
+            return obj.equals(Boolean.TRUE) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+        }
+        if (obj instanceof java.util.Date) {
+            if (obj instanceof java.sql.Date) {
+                return new SimpleDate((java.sql.Date) obj);
+            }
+            if (obj instanceof java.sql.Time) {
+                return new SimpleDate((java.sql.Time) obj);
+            }
+            if (obj instanceof java.sql.Timestamp) {
+                return new SimpleDate((java.sql.Timestamp) obj);
+            }
+            return new SimpleDate((java.util.Date) obj, TemplateDateModel.UNKNOWN);
+        }
+
+        if (obj.getClass().isArray()) {
+            obj = Arrays.asList((Object[]) obj);
+        }
+        if (obj instanceof Collection) {
+            return new SimpleSequence((Collection<?>) obj, this);
+        }
+        if (obj instanceof Map) {
+            return new SimpleHash((Map<?, ?>) obj, this);
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/test/util/FullyCustomObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/util/FullyCustomObjectWrapper.java b/src/test/java/org/apache/freemarker/test/util/FullyCustomObjectWrapper.java
deleted file mode 100644
index 31a1110..0000000
--- a/src/test/java/org/apache/freemarker/test/util/FullyCustomObjectWrapper.java
+++ /dev/null
@@ -1,91 +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.test.util;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.SimpleDate;
-import org.apache.freemarker.core.model.impl.SimpleHash;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.freemarker.core.model.impl.SimpleSequence;
-
-/**
- * An object wrapper that doesn't extend {@link DefaultObjectWrapper}.
- */
-public class FullyCustomObjectWrapper implements ObjectWrapper {
-
-    @Override
-    public TemplateModel wrap(Object obj) throws TemplateModelException {
-        if (obj == null) {
-            return null;
-        }
-
-        if (obj instanceof TemplateModel) {
-            return (TemplateModel) obj;
-        }
-        if (obj instanceof TemplateModelAdapter) {
-            return ((TemplateModelAdapter) obj).getTemplateModel();
-        }
-
-        if (obj instanceof String) {
-            return new SimpleScalar((String) obj);
-        }
-        if (obj instanceof Number) {
-            return new SimpleNumber((Number) obj);
-        }
-        if (obj instanceof Boolean) {
-            return obj.equals(Boolean.TRUE) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-        }
-        if (obj instanceof java.util.Date) {
-            if (obj instanceof java.sql.Date) {
-                return new SimpleDate((java.sql.Date) obj);
-            }
-            if (obj instanceof java.sql.Time) {
-                return new SimpleDate((java.sql.Time) obj);
-            }
-            if (obj instanceof java.sql.Timestamp) {
-                return new SimpleDate((java.sql.Timestamp) obj);
-            }
-            return new SimpleDate((java.util.Date) obj, TemplateDateModel.UNKNOWN);
-        }
-
-        if (obj.getClass().isArray()) {
-            obj = Arrays.asList((Object[]) obj);
-        }
-        if (obj instanceof Collection) {
-            return new SimpleSequence((Collection<?>) obj, this);
-        }
-        if (obj instanceof Map) {
-            return new SimpleHash((Map<?, ?>) obj, this);
-        }
-
-        return null;
-    }
-}


[11/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its public and protected API-s) and the test suite was reworked accordingly. There are still many rough edges, such as the setSetting(Strin,String) API should appear in MutableParsingAndProcessingConfiguration too, it's not settled how we will handle Map/List setting values (when are they copied to avoid aliasing effects, when they are remain mutable), etc.

Some cleanup of random small things I have ran into during the above refactoring.


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

Branch: refs/heads/3
Commit: 7d61a45d9ff129138c0eccc5a30112d7b419f22a
Parents: 7c85a56
Author: ddekany <dd...@apache.org>
Authored: Fri May 5 22:51:25 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri May 5 23:00:07 2017 +0200

----------------------------------------------------------------------
 README.md                                       |    1 -
 .../apache/freemarker/core/ASTDirSetting.java   |   14 +-
 .../apache/freemarker/core/ASTExpBuiltIn.java   |   10 +-
 .../freemarker/core/ASTExpBuiltInVariable.java  |   10 +-
 .../freemarker/core/ASTExpHashLiteral.java      |    5 +-
 .../apache/freemarker/core/Configuration.java   | 3924 +++++++++---------
 .../freemarker/core/ConfigurationException.java |    2 +-
 .../ConfigurationSettingValueException.java     |   61 +-
 .../org/apache/freemarker/core/Environment.java |   69 +-
 ...utableParsingAndProcessingConfiguration.java |  225 +-
 .../core/MutableProcessingConfiguration.java    |  677 +--
 .../NonStringOrTemplateOutputException.java     |    2 +-
 .../freemarker/core/ParsingConfiguration.java   |  175 +-
 .../core/ProcessingConfiguration.java           |    9 +-
 .../org/apache/freemarker/core/Template.java    |   34 +-
 .../freemarker/core/TemplateConfiguration.java  |   77 +-
 .../freemarker/core/TopLevelConfiguration.java  |  194 +
 .../UnknownConfigurationSettingException.java   |    3 +-
 .../core/_ErrorDescriptionBuilder.java          |    2 +-
 .../core/arithmetic/ArithmeticEngine.java       |    2 +-
 .../core/debug/RmiDebuggedEnvironmentImpl.java  |    4 +-
 .../freemarker/core/model/ObjectWrapper.java    |   10 +-
 .../freemarker/core/model/TemplateModel.java    |    2 +-
 .../core/model/impl/DefaultObjectWrapper.java   |   31 +-
 .../core/outputformat/MarkupOutputFormat.java   |    6 +-
 .../core/outputformat/OutputFormat.java         |    4 +-
 .../impl/UndefinedOutputFormat.java             |    5 +-
 .../core/templateresolver/CacheStorage.java     |    4 +-
 .../core/templateresolver/TemplateLoader.java   |    3 +-
 .../templateresolver/TemplateLoadingResult.java |    3 +-
 .../TemplateLookupStrategy.java                 |    2 +-
 .../templateresolver/TemplateNameFormat.java    |    6 +-
 .../core/templateresolver/TemplateResolver.java |   12 +-
 .../impl/DefaultTemplateNameFormat.java         |    7 +-
 .../impl/DefaultTemplateNameFormatFM2.java      |    5 +-
 .../impl/DefaultTemplateResolver.java           |   75 +-
 .../templateresolver/impl/MruCacheStorage.java  |    4 +-
 .../templateresolver/impl/NullCacheStorage.java |    3 +-
 .../templateresolver/impl/SoftCacheStorage.java |    3 +-
 .../impl/StrongCacheStorage.java                |    3 +-
 .../impl/URLTemplateLoader.java                 |    2 +-
 .../freemarker/core/util/CommonBuilder.java     |    8 +-
 .../freemarker/core/util/_StringUtil.java       |   12 +-
 .../core/valueformat/TemplateValueFormat.java   |   11 +-
 .../apache/freemarker/dom/NodeListModel.java    |    5 +-
 .../org/apache/freemarker/dom/NodeModel.java    |   10 +-
 .../freemarker/servlet/FreemarkerServlet.java   |  291 +-
 .../FreemarkerServletConfigurationBuilder.java  |   79 +
 .../freemarker/servlet/InitParamParser.java     |    5 +-
 .../servlet/WebAppTemplateLoader.java           |    2 +-
 src/manual/en_US/FM3-CHANGE-LOG.txt             |   13 +-
 .../org/apache/freemarker/core/ASTPrinter.java  |    6 +-
 .../core/ActualNamingConvetionTest.java         |   21 +-
 .../freemarker/core/ActualTagSyntaxTest.java    |   36 +-
 .../freemarker/core/BreakPlacementTest.java     |    8 +-
 .../apache/freemarker/core/CamelCaseTest.java   |  110 +-
 .../freemarker/core/CanonicalFormTest.java      |   11 +-
 .../freemarker/core/CoercionToTextualTest.java  |   34 +-
 .../freemarker/core/ConfigurableTest.java       |    4 +-
 .../freemarker/core/ConfigurationTest.java      | 1008 +++--
 .../freemarker/core/CustomAttributeTest.java    |    6 +-
 .../apache/freemarker/core/DateFormatTest.java  |  204 +-
 .../freemarker/core/DirectiveCallPlaceTest.java |    7 +-
 .../freemarker/core/EncodingOverrideTest.java   |    9 +-
 .../EnvironmentGetTemplateVariantsTest.java     |    9 +-
 .../apache/freemarker/core/ExceptionTest.java   |   24 +-
 .../apache/freemarker/core/GetSourceTest.java   |   14 +-
 .../freemarker/core/HeaderParsingTest.java      |   10 +-
 .../IncludeAndImportConfigurableLayersTest.java |  187 +-
 .../freemarker/core/IncludeAndImportTest.java   |   86 +-
 .../freemarker/core/IncudeFromNamelessTest.java |   15 +-
 .../core/InterpretAndEvalTemplateNameTest.java  |    5 +-
 .../core/InterpretSettingInheritanceTest.java   |   43 +-
 .../core/JavaCCExceptionAsEOFFixTest.java       |    9 +-
 .../freemarker/core/MiscErrorMessagesTest.java  |    5 +-
 .../core/MistakenlyPublicImportAPIsTest.java    |    5 +-
 .../core/MistakenlyPublicMacroAPIsTest.java     |    3 +-
 .../core/NewBiObjectWrapperRestrictionTest.java |    9 +-
 .../core/ObjectBuilderSettingsTest.java         |   62 +-
 .../core/OptInTemplateClassResolverTest.java    |  182 +-
 .../freemarker/core/OutputFormatTest.java       |  280 +-
 .../core/ParsingErrorMessagesTest.java          |   12 +-
 .../apache/freemarker/core/SQLTimeZoneTest.java |  111 +-
 .../freemarker/core/SpecialVariableTest.java    |   38 +-
 .../core/StringLiteralInterpolationTest.java    |   10 +-
 .../org/apache/freemarker/core/TabSizeTest.java |   15 +-
 .../core/TagSyntaxVariationsTest.java           |  127 +-
 .../core/TemplateConfigurationTest.java         |   87 +-
 ...gurationWithDefaultTemplateResolverTest.java |  153 +-
 .../core/TemplateConstructorsTest.java          |    4 +-
 .../core/TemplateGetEncodingTest.java           |   12 +-
 .../core/TemplateLookupStrategyTest.java        |  145 +-
 .../core/TemplateNameSpecialVariablesTest.java  |  115 +-
 .../core/TemplateNotFoundMessageTest.java       |   84 +-
 .../core/TheadInterruptingSupportTest.java      |    3 +-
 .../freemarker/core/UnclosedCommentTest.java    |    1 -
 .../core/WhitespaceStrippingTest.java           |   12 +-
 .../model/impl/DefaultObjectWrapperTest.java    |   13 +-
 .../DefaultTemplateResolverTest.java            |  112 +-
 .../FileTemplateLoaderTest.java                 |    7 +-
 .../TemplateConfigurationFactoryTest.java       |    5 +-
 .../TemplateNameFormatTest.java                 |   13 +-
 .../core/valueformat/NumberFormatTest.java      |  154 +-
 .../impl/ExtendedDecimalFormatTest.java         |   15 +-
 .../manualtest/AutoEscapingExample.java         |   11 +-
 .../ConfigureOutputFormatExamples.java          |  129 +-
 .../manualtest/CustomFormatsExample.java        |   57 +-
 .../freemarker/manualtest/ExamplesTest.java     |   59 -
 .../manualtest/GettingStartedExample.java       |   16 +-
 .../TemplateConfigurationExamples.java          |  225 +-
 .../servlet/FreemarkerServletTest.java          |   60 +-
 .../freemarker/servlet/InitParamParserTest.java |    9 +-
 .../servlet/jsp/RealServletContainertTest.java  |   63 +-
 .../apache/freemarker/test/TemplateTest.java    |   82 +-
 .../test/TestConfigurationBuilder.java          |   92 +
 .../freemarker/test/servlet/WebAppTestCase.java |    1 +
 .../test/templatesuite/TemplateTestCase.java    |   27 +-
 .../test/templatesuite/TemplateTestSuite.java   |    2 +-
 .../test/util/EntirelyCustomObjectWrapper.java  |   91 +
 .../test/util/FullyCustomObjectWrapper.java     |   91 -
 120 files changed, 5787 insertions(+), 5014 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index a571483..6393f28 100644
--- a/README.md
+++ b/README.md
@@ -187,7 +187,6 @@ apply it to your development environment:
         dom4j-*.jar, which casues compilation errors if it wins over
         jaxen-*.jar).
    - Press "Finish"
-- Eclipse will indicate many errors at this point; it's expected, read on.
 - Project -> Properties -> Java Compiler -> Errors/Warnings:
   Check in "Enable project specific settings", then set "Forbidden reference
   (access rules)" from "Error" to "Warning".

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirSetting.java b/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
index 396d269..68a0672 100644
--- a/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
+++ b/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
@@ -63,8 +63,8 @@ final class ASTDirSetting extends ASTDirective {
         String key = keyTk.image;
         if (Arrays.binarySearch(SETTING_NAMES, key) < 0) {
             StringBuilder sb = new StringBuilder();
-            if (cfg.getSettingNames(true).contains(key)
-                    || cfg.getSettingNames(false).contains(key)) {
+            if (Configuration.ExtendableBuilder.getSettingNames(true).contains(key)
+                    || Configuration.ExtendableBuilder.getSettingNames(false).contains(key)) {
                 sb.append("The setting name is recognized, but changing this setting from inside a template isn't "
                         + "supported.");                
             } else {
@@ -75,16 +75,16 @@ final class ASTDirSetting extends ASTDirective {
                 int shownNamingConvention;
                 {
                     int namingConvention = tokenManager.namingConvention;
-                    shownNamingConvention = namingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION
-                            ? namingConvention : Configuration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */; 
+                    shownNamingConvention = namingConvention != ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION
+                            ? namingConvention : ParsingConfiguration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */;
                 }
                 
                 boolean first = true;
                 for (String correctName : SETTING_NAMES) {
                     int correctNameNamingConvention = _StringUtil.getIdentifierNamingConvention(correctName);
-                    if (shownNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION
-                            ? correctNameNamingConvention != Configuration.LEGACY_NAMING_CONVENTION
-                            : correctNameNamingConvention != Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+                    if (shownNamingConvention == ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION
+                            ? correctNameNamingConvention != ParsingConfiguration.LEGACY_NAMING_CONVENTION
+                            : correctNameNamingConvention != ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) {
                         if (first) {
                             first = false;
                         } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java b/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
index 5774812..be559f6 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
@@ -331,16 +331,16 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
             int shownNamingConvention;
             {
                 int namingConvention = tokenManager.namingConvention;
-                shownNamingConvention = namingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION
-                        ? namingConvention : Configuration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */; 
+                shownNamingConvention = namingConvention != ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION
+                        ? namingConvention : ParsingConfiguration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */;
             }
             
             boolean first = true;
             for (String correctName : names) {
                 int correctNameNamingConvetion = _StringUtil.getIdentifierNamingConvention(correctName);
-                if (shownNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION
-                        ? correctNameNamingConvetion != Configuration.LEGACY_NAMING_CONVENTION
-                        : correctNameNamingConvetion != Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+                if (shownNamingConvention == ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION
+                        ? correctNameNamingConvetion != ParsingConfiguration.LEGACY_NAMING_CONVENTION
+                        : correctNameNamingConvetion != ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) {
                     if (first) {
                         first = false;
                     } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java b/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
index ca34a7d..ece2099 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
@@ -123,8 +123,8 @@ final class ASTExpBuiltInVariable extends ASTExpression {
             int shownNamingConvention;
             {
                 int namingConvention = tokenManager.namingConvention;
-                shownNamingConvention = namingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION
-                        ? namingConvention : Configuration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */; 
+                shownNamingConvention = namingConvention != ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION
+                        ? namingConvention : ParsingConfiguration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */;
             }
             
             {
@@ -146,9 +146,9 @@ final class ASTExpBuiltInVariable extends ASTExpression {
             boolean first = true;
             for (final String correctName : SPEC_VAR_NAMES) {
                 int correctNameNamingConvention = _StringUtil.getIdentifierNamingConvention(correctName);
-                if (shownNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION
-                        ? correctNameNamingConvention != Configuration.LEGACY_NAMING_CONVENTION
-                        : correctNameNamingConvention != Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+                if (shownNamingConvention == ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION
+                        ? correctNameNamingConvention != ParsingConfiguration.LEGACY_NAMING_CONVENTION
+                        : correctNameNamingConvention != ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) {
                     if (first) {
                         first = false;
                     } else {

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


[09/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ConfigurationException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ConfigurationException.java b/src/main/java/org/apache/freemarker/core/ConfigurationException.java
index 42ab6e1..5b61cca 100644
--- a/src/main/java/org/apache/freemarker/core/ConfigurationException.java
+++ b/src/main/java/org/apache/freemarker/core/ConfigurationException.java
@@ -23,7 +23,7 @@ package org.apache.freemarker.core;
  * Error while configuring FreeMarker.
  */
 @SuppressWarnings("serial")
-public class ConfigurationException extends Exception {
+public class ConfigurationException extends RuntimeException {
 
     public ConfigurationException(String message, Throwable cause) {
         super(message, cause);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java b/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java
index c480418..3ed6512 100644
--- a/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java
+++ b/src/main/java/org/apache/freemarker/core/ConfigurationSettingValueException.java
@@ -18,30 +18,69 @@
  */
 package org.apache.freemarker.core;
 
-import org.apache.freemarker.core.util._NullArgumentException;
+import java.util.Date;
+
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
 import org.apache.freemarker.core.util._StringUtil;
 
 /**
- * Thrown by {@link Configuration#setSetting(String, String)}; The setting name was recognized, but its value
+ * Thrown by {@link ExtendableBuilder#setSetting(String, String)}; The setting name was recognized, but its value
  * couldn't be parsed or the setting couldn't be set for some other reason. This exception should always have a
  * cause exception.
  */
 @SuppressWarnings("serial")
 public class ConfigurationSettingValueException extends ConfigurationException {
 
-    ConfigurationSettingValueException(String name, String value, Throwable cause) {
-        super(createMessage(name, value, "; see cause exception.", ""), cause);
-        _NullArgumentException.check("cause", cause);
+    public ConfigurationSettingValueException(String name, String value, Throwable cause) {
+        this(name, value, true, null, cause);
+    }
+
+    public ConfigurationSettingValueException(String name, String value, String reason) {
+        this(name, value, true, reason, null);
     }
 
-    ConfigurationSettingValueException(String name, String value, String reason) {
-        super(createMessage(name, value, ", because: ", reason));
-        _NullArgumentException.check("reason", reason);
+    /**
+     * @param name
+     *         The name of the setting
+     * @param value
+     *         The value of the setting
+     * @param showValue
+     *         Whether the value of the setting should be shown in the error message. Set to {@code false} if you want
+     *         to avoid {@link #toString()}-ing the {@code value}.
+     * @param reason
+     *         The explanation of why setting the setting has failed; maybe {@code null}, especially if you have a cause
+     *         exception anyway.
+     * @param cause
+     *         The cause exception of this exception (why setting the setting was failed)
+     */
+    public ConfigurationSettingValueException(String name, Object value, boolean showValue, String reason,
+            Throwable cause) {
+        super(
+                createMessage(
+                    name, value, true,
+                    reason != null ? ", because: " : (cause != null ? "; see cause exception." : null),
+                    reason),
+                cause);
     }
 
-    private static String createMessage(String name, String value, String detail1, String detail2) {
-        return "Failed to set FreeMarker configuration setting " + _StringUtil.jQuote(name)
-                + " to value " + _StringUtil.jQuote(value) + detail1 + detail2;
+    private static String createMessage(String name, Object value, boolean showValue, String detail1, String detail2) {
+        StringBuilder sb = new StringBuilder(64);
+        sb.append("Failed to set FreeMarker configuration setting ").append(_StringUtil.jQuote(name));
+        if (showValue) {
+            sb.append(" to value ")
+                    .append(
+                            value instanceof Number || value instanceof Boolean || value instanceof Date ? value
+                            : _StringUtil.jQuote(value));
+        } else {
+            sb.append(" to the specified value");
+        }
+        if (detail1 != null) {
+            sb.append(detail1);
+        }
+        if (detail2 != null) {
+            sb.append(detail2);
+        }
+        return sb.toString();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 220f305..6713200 100644
--- a/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/src/main/java/org/apache/freemarker/core/Environment.java
@@ -922,17 +922,17 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
+    protected TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
         return getMainTemplate().getTemplateExceptionHandler();
     }
 
     @Override
-    protected ArithmeticEngine getInheritedArithmeticEngine() {
+    protected ArithmeticEngine getDefaultArithmeticEngine() {
         return getMainTemplate().getArithmeticEngine();
     }
 
     @Override
-    protected ObjectWrapper getInheritedObjectWrapper() {
+    protected ObjectWrapper getDefaultObjectWrapper() {
         return getMainTemplate().getObjectWrapper();
     }
 
@@ -962,7 +962,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected Locale getInheritedLocale() {
+    protected Locale getDefaultLocale() {
         return getMainTemplate().getLocale();
     }
 
@@ -991,7 +991,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected TimeZone getInheritedTimeZone() {
+    protected TimeZone getDefaultTimeZone() {
         return getMainTemplate().getTimeZone();
     }
 
@@ -1020,7 +1020,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
+    protected TimeZone getDefaultSQLDateAndTimeTimeZone() {
         return getMainTemplate().getSQLDateAndTimeTimeZone();
     }
 
@@ -1051,60 +1051,65 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected Charset getInheritedURLEscapingCharset() {
+    protected Charset getDefaultURLEscapingCharset() {
         return getMainTemplate().getURLEscapingCharset();
     }
 
     @Override
-    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
+    protected TemplateClassResolver getDefaultNewBuiltinClassResolver() {
         return getMainTemplate().getNewBuiltinClassResolver();
     }
 
     @Override
-    protected boolean getInheritedAutoFlush() {
+    protected boolean getDefaultAutoFlush() {
         return getMainTemplate().getAutoFlush();
     }
 
     @Override
-    protected boolean getInheritedShowErrorTips() {
+    protected boolean getDefaultShowErrorTips() {
         return getMainTemplate().getShowErrorTips();
     }
 
     @Override
-    protected boolean getInheritedAPIBuiltinEnabled() {
+    protected boolean getDefaultAPIBuiltinEnabled() {
         return getMainTemplate().getAPIBuiltinEnabled();
     }
 
     @Override
-    protected boolean getInheritedLogTemplateExceptions() {
+    protected boolean getDefaultLogTemplateExceptions() {
         return getMainTemplate().getLogTemplateExceptions();
     }
 
     @Override
-    protected boolean getInheritedLazyImports() {
+    protected boolean getDefaultLazyImports() {
         return getMainTemplate().getLazyImports();
     }
 
     @Override
-    protected Boolean getInheritedLazyAutoImports() {
+    protected Boolean getDefaultLazyAutoImports() {
         return getMainTemplate().getLazyAutoImports();
     }
 
     @Override
-    protected Map<String, String> getInheritedAutoImports() {
+    protected Map<String, String> getDefaultAutoImports() {
         return getMainTemplate().getAutoImports();
     }
 
     @Override
-    protected List<String> getInheritedAutoIncludes() {
+    protected List<String> getDefaultAutoIncludes() {
         return getMainTemplate().getAutoIncludes();
     }
 
     @Override
-    protected Object getInheritedCustomAttribute(Object name) {
+    protected Object getDefaultCustomAttribute(Object name) {
         return getMainTemplate().getCustomAttribute(name);
     }
 
+    @Override
+    protected Map<Object, Object> getDefaultCustomAttributes() {
+        return getMainTemplate().getCustomAttributes();
+    }
+
     /*
      * Note that altough it's not allowed to set this setting with the <tt>setting</tt> directive, it still must be
      * allowed to set it from Java code while the template executes, since some frameworks allow templates to actually
@@ -1117,7 +1122,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected Charset getInheritedOutputEncoding() {
+    protected Charset getDefaultOutputEncoding() {
         return getMainTemplate().getOutputEncoding();
     }
 
@@ -1220,22 +1225,22 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected String getInheritedNumberFormat() {
+    protected String getDefaultNumberFormat() {
         return getMainTemplate().getNumberFormat();
     }
 
     @Override
-    protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
+    protected Map<String, TemplateNumberFormatFactory> getDefaultCustomNumberFormats() {
         return getMainTemplate().getCustomNumberFormats();
     }
 
     @Override
-    protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
+    protected TemplateNumberFormatFactory getDefaultCustomNumberFormat(String name) {
         return getMainTemplate().getCustomNumberFormat(name);
     }
 
     @Override
-    protected String getInheritedBooleanFormat() {
+    protected String getDefaultBooleanFormat() {
         return getMainTemplate().getBooleanFormat();
     }
 
@@ -1530,7 +1535,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected String getInheritedTimeFormat() {
+    protected String getDefaultTimeFormat() {
         return getMainTemplate().getTimeFormat();
     }
 
@@ -1548,7 +1553,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected String getInheritedDateFormat() {
+    protected String getDefaultDateFormat() {
         return getMainTemplate().getDateFormat();
     }
 
@@ -1566,17 +1571,17 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected String getInheritedDateTimeFormat() {
+    protected String getDefaultDateTimeFormat() {
         return getMainTemplate().getDateTimeFormat();
     }
 
     @Override
-    protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
+    protected Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats() {
         return getMainTemplate().getCustomDateFormats();
     }
 
     @Override
-    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
+    protected TemplateDateFormatFactory getDefaultCustomDateFormat(String name) {
         return getMainTemplate().getCustomDateFormat(name);
     }
 
@@ -2083,7 +2088,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         if (tm instanceof TemplateTransformModel) {
             ttm = (TemplateTransformModel) tm;
         } else if (exp instanceof ASTExpVariable) {
-            tm = configuration.getSharedVariable(exp.toString());
+            tm = configuration.getWrappedSharedVariable(exp.toString());
             if (tm instanceof TemplateTransformModel) {
                 ttm = (TemplateTransformModel) tm;
             }
@@ -2148,7 +2153,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             result = rootDataModel.get(name);
         }
         if (result == null) {
-            result = configuration.getSharedVariable(name);
+            result = configuration.getWrappedSharedVariable(name);
         }
         return result;
     }
@@ -2200,7 +2205,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
      */
     public Set getKnownVariableNames() throws TemplateModelException {
         // shared vars.
-        Set set = configuration.getSharedVariableNames();
+        Set set = configuration.getSharedVariables().keySet();
 
         // root hash
         if (rootDataModel instanceof TemplateHashModelEx) {
@@ -2454,7 +2459,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             public TemplateModel get(String key) throws TemplateModelException {
                 TemplateModel value = rootDataModel.get(key);
                 if (value == null) {
-                    value = configuration.getSharedVariable(key);
+                    value = configuration.getWrappedSharedVariable(key);
                 }
                 return value;
             }
@@ -2515,7 +2520,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
                     result = rootDataModel.get(key);
                 }
                 if (result == null) {
-                    result = configuration.getSharedVariable(key);
+                    result = configuration.getWrappedSharedVariable(key);
                 }
                 return result;
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
index 2df4796..00a387d 100644
--- a/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
@@ -23,10 +23,10 @@ import java.io.InputStream;
 import java.nio.charset.Charset;
 
 import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.util._NullArgumentException;
 
-// TODO This will be the superclass of TemplateConfiguration.Builder and Configuration.Builder
 public abstract class MutableParsingAndProcessingConfiguration<
         SelfT extends MutableParsingAndProcessingConfiguration<SelfT>>
         extends MutableProcessingConfiguration<SelfT>
@@ -42,22 +42,30 @@ public abstract class MutableParsingAndProcessingConfiguration<
     private Charset sourceEncoding;
     private Integer tabSize;
 
-    protected MutableParsingAndProcessingConfiguration(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
-    }
-
     protected MutableParsingAndProcessingConfiguration() {
         super();
     }
 
     /**
-     * See {@link Configuration#setTagSyntax(int)}.
+     * Setter pair of {@link #getTagSyntax()}.
      */
     public void setTagSyntax(int tagSyntax) {
-        Configuration.valideTagSyntaxValue(tagSyntax);
+        valideTagSyntaxValue(tagSyntax);
         this.tagSyntax = tagSyntax;
     }
 
+    // [FM3] Use enum; won't be needed
+    static void valideTagSyntaxValue(int tagSyntax) {
+        if (tagSyntax != ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX
+                && tagSyntax != ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX
+                && tagSyntax != ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX) {
+            throw new IllegalArgumentException(
+                    "\"tagSyntax\" can only be set to one of these: "
+                    + "Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, "
+                    + "or Configuration.SQUARE_BRACKET_SYNTAX");
+        }
+    }
+
     /**
      * Fluent API equivalent of {@link #tagSyntax(int)}
      */
@@ -67,32 +75,42 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
-     * The getter pair of {@link #setTagSyntax(int)}.
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
      */
+    public void unsetTagSyntax() {
+        this.tagSyntax = null;
+    }
+
     @Override
     public int getTagSyntax() {
-        return tagSyntax != null ? tagSyntax : getInheritedTagSyntax();
+        return isTagSyntaxSet() ? tagSyntax : getDefaultTagSyntax();
     }
 
-    protected abstract int getInheritedTagSyntax();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultTagSyntax();
 
     @Override
     public boolean isTagSyntaxSet() {
         return tagSyntax != null;
     }
 
-    /**
-     * See {@link Configuration#getTemplateLanguage()}
-     */
     @Override
     public TemplateLanguage getTemplateLanguage() {
-         return isTemplateLanguageSet() ? templateLanguage : getInheritedTemplateLanguage();
+         return isTemplateLanguageSet() ? templateLanguage : getDefaultTemplateLanguage();
     }
 
-    protected abstract TemplateLanguage getInheritedTemplateLanguage();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract TemplateLanguage getDefaultTemplateLanguage();
 
     /**
-     * See {@link Configuration#setTemplateLanguage(TemplateLanguage)}
+     * Setter pair of {@link #getTemplateLanguage()}.
      */
     public void setTemplateLanguage(TemplateLanguage templateLanguage) {
         _NullArgumentException.check("templateLanguage", templateLanguage);
@@ -107,12 +125,21 @@ public abstract class MutableParsingAndProcessingConfiguration<
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetTemplateLanguage() {
+        this.templateLanguage = null;
+    }
+
+    @Override
     public boolean isTemplateLanguageSet() {
         return templateLanguage != null;
     }
 
     /**
-     * See {@link Configuration#setNamingConvention(int)}.
+     * Setter pair of {@link #getNamingConvention()}.
      */
     public void setNamingConvention(int namingConvention) {
         Configuration.validateNamingConventionValue(namingConvention);
@@ -128,15 +155,27 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetNamingConvention() {
+        this.namingConvention = null;
+    }
+
+    /**
      * The getter pair of {@link #setNamingConvention(int)}.
      */
     @Override
     public int getNamingConvention() {
          return isNamingConventionSet() ? namingConvention
-                : getInheritedNamingConvention();
+                : getDefaultNamingConvention();
     }
 
-    protected abstract int getInheritedNamingConvention();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultNamingConvention();
 
     /**
      * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
@@ -147,10 +186,10 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
-     * See {@link Configuration#setWhitespaceStripping(boolean)}.
+     * Setter pair of {@link ParsingConfiguration#getWhitespaceStripping()}.
      */
     public void setWhitespaceStripping(boolean whitespaceStripping) {
-        this.whitespaceStripping = Boolean.valueOf(whitespaceStripping);
+        this.whitespaceStripping = whitespaceStripping;
     }
 
     /**
@@ -162,15 +201,26 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetWhitespaceStripping() {
+        this.whitespaceStripping = null;
+    }
+
+    /**
      * The getter pair of {@link #getWhitespaceStripping()}.
      */
     @Override
     public boolean getWhitespaceStripping() {
-         return isWhitespaceStrippingSet() ? whitespaceStripping.booleanValue()
-                : getInheritedWhitespaceStripping();
+         return isWhitespaceStrippingSet() ? whitespaceStripping : getDefaultWhitespaceStripping();
     }
 
-    protected abstract boolean getInheritedWhitespaceStripping();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract boolean getDefaultWhitespaceStripping();
 
     /**
      * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
@@ -181,12 +231,24 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
-     * Sets the output format of the template; see {@link Configuration#setAutoEscapingPolicy(int)} for more.
+     * * Setter pair of {@link #getAutoEscapingPolicy()}.
      */
     public void setAutoEscapingPolicy(int autoEscapingPolicy) {
-        Configuration.validateAutoEscapingPolicyValue(autoEscapingPolicy);
+        validateAutoEscapingPolicyValue(autoEscapingPolicy);
+        this.autoEscapingPolicy = autoEscapingPolicy;
+    }
 
-        this.autoEscapingPolicy = Integer.valueOf(autoEscapingPolicy);
+    // [FM3] Use enum; won't be needed
+    static void validateAutoEscapingPolicyValue(int autoEscapingPolicy) {
+        if (autoEscapingPolicy != ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY
+                && autoEscapingPolicy != ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY
+                && autoEscapingPolicy != ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY) {
+            throw new IllegalArgumentException(
+                    "\"tagSyntax\" can only be set to one of these: "
+                            + "Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,"
+                            + "Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, "
+                            + "or Configuration.DISABLE_AUTO_ESCAPING_POLICY");
+        }
     }
 
     /**
@@ -198,15 +260,26 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetAutoEscapingPolicy() {
+        this.autoEscapingPolicy = null;
+    }
+
+    /**
      * The getter pair of {@link #setAutoEscapingPolicy(int)}.
      */
     @Override
     public int getAutoEscapingPolicy() {
-         return isAutoEscapingPolicySet() ? autoEscapingPolicy.intValue()
-                : getInheritedAutoEscapingPolicy();
+         return isAutoEscapingPolicySet() ? autoEscapingPolicy : getDefaultAutoEscapingPolicy();
     }
 
-    protected abstract int getInheritedAutoEscapingPolicy();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultAutoEscapingPolicy();
 
     /**
      * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
@@ -217,14 +290,25 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
-     * Sets the output format of the template; see {@link Configuration#setOutputFormat(OutputFormat)} for more.
+     * Setter pair of {@link #getOutputFormat()}.
      */
     public void setOutputFormat(OutputFormat outputFormat) {
-        _NullArgumentException.check("outputFormat", outputFormat);
+        if (outputFormat == null) {
+            throw new _NullArgumentException(
+                    "outputFormat",
+                    "You may meant: " + UndefinedOutputFormat.class.getSimpleName() + ".INSTANCE");
+        }
         this.outputFormat = outputFormat;
     }
 
     /**
+     * Resets this setting to its initial state, as if it was never set.
+     */
+    public void unsetOutputFormat() {
+        this.outputFormat = null;
+    }
+
+    /**
      * Fluent API equivalent of {@link #setOutputFormat(OutputFormat)}
      */
     public SelfT outputFormat(OutputFormat outputFormat) {
@@ -232,15 +316,16 @@ public abstract class MutableParsingAndProcessingConfiguration<
         return self();
     }
 
-    /**
-     * The getter pair of {@link #setOutputFormat(OutputFormat)}.
-     */
     @Override
     public OutputFormat getOutputFormat() {
-         return isOutputFormatSet() ? outputFormat : getInheritedOutputFormat();
+         return isOutputFormatSet() ? outputFormat : getDefaultOutputFormat();
     }
 
-    protected abstract OutputFormat getInheritedOutputFormat();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract OutputFormat getDefaultOutputFormat();
 
     /**
      * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
@@ -251,10 +336,10 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
-     * See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}.
+     * Setter pair of {@link ParsingConfiguration#getRecognizeStandardFileExtensions()}.
      */
     public void setRecognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
-        this.recognizeStandardFileExtensions = Boolean.valueOf(recognizeStandardFileExtensions);
+        this.recognizeStandardFileExtensions = recognizeStandardFileExtensions;
     }
 
     /**
@@ -266,15 +351,26 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
+     * Resets this setting to its initial state, as if it was never set.
+     */
+    public void unsetRecognizeStandardFileExtensions() {
+        recognizeStandardFileExtensions = null;
+    }
+
+    /**
      * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
      */
     @Override
     public boolean getRecognizeStandardFileExtensions() {
-         return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions.booleanValue()
-                : getInheritedRecognizeStandardFileExtensions();
+         return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions
+                : getDefaultRecognizeStandardFileExtensions();
     }
 
-    protected abstract boolean getInheritedRecognizeStandardFileExtensions();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract boolean getDefaultRecognizeStandardFileExtensions();
 
     /**
      * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
@@ -286,10 +382,14 @@ public abstract class MutableParsingAndProcessingConfiguration<
 
     @Override
     public Charset getSourceEncoding() {
-         return isSourceEncodingSet() ? sourceEncoding : getInheritedSourceEncoding();
+         return isSourceEncodingSet() ? sourceEncoding : getDefaultSourceEncoding();
     }
 
-    protected abstract Charset getInheritedSourceEncoding();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract Charset getDefaultSourceEncoding();
 
     /**
      * The charset to be used when reading the template "file" that the {@link TemplateLoader} returns as binary
@@ -308,16 +408,30 @@ public abstract class MutableParsingAndProcessingConfiguration<
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetSourceEncoding() {
+        this.sourceEncoding = null;
+    }
+
+    @Override
     public boolean isSourceEncodingSet() {
         return sourceEncoding != null;
     }
 
     /**
-     * See {@link Configuration#setTabSize(int)}.
-     *
-     * @since 2.3.25
+     * Setter pair of {@link #getTabSize()}.
      */
     public void setTabSize(int tabSize) {
+        if (tabSize < 1) {
+            throw new IllegalArgumentException("\"tabSize\" must be at least 1, but was " + tabSize);
+        }
+        // To avoid integer overflows:
+        if (tabSize > 256) {
+            throw new IllegalArgumentException("\"tabSize\" can't be more than 256, but was " + tabSize);
+        }
         this.tabSize = Integer.valueOf(tabSize);
     }
 
@@ -330,16 +444,23 @@ public abstract class MutableParsingAndProcessingConfiguration<
     }
 
     /**
-     * Getter pair of {@link #setTabSize(int)}.
-     *
-     * @since 2.3.25
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
      */
+    public void unsetTabSize() {
+        this.tabSize = null;
+    }
+
     @Override
     public int getTabSize() {
-         return isTabSizeSet() ? tabSize.intValue() : getInheritedTabSize();
+         return isTabSizeSet() ? tabSize.intValue() : getDefaultTabSize();
     }
 
-    protected abstract int getInheritedTabSize();
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultTabSize();
 
     /**
      * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 a7daf31..c5c4c82 100644
--- a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -22,7 +22,6 @@ package org.apache.freemarker.core;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -56,7 +55,6 @@ import org.apache.freemarker.core.templateresolver.NotMatcher;
 import org.apache.freemarker.core.templateresolver.OrMatcher;
 import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
 import org.apache.freemarker.core.templateresolver.PathRegexMatcher;
-import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
 import org.apache.freemarker.core.util.FTLUtil;
@@ -73,7 +71,7 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
 /**
  * Extended by FreeMarker core classes (not by you) that support specifying {@link ProcessingConfiguration} setting
- * values. <b>New abstract methods may be added anytime in future FreeMarker versions, so don't try to implement this
+ * values. <b>New abstract methods may be added any time in future FreeMarker versions, so don't try to implement this
  * interface yourself!</b>
  */
 public abstract class MutableProcessingConfiguration<SelfT extends MutableProcessingConfiguration<SelfT>>
@@ -337,48 +335,27 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     private Map<Object, Object> customAttributes;
 
     /**
-     * Called by the {@link Configuration} constructor, initializes the fields to their {@link Configuration}-level
-     * default without marking them as set.
+     * 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.
      */
-    // TODO Move to Configuration(Builder) constructor
-    MutableProcessingConfiguration(Version iciForDefaults) {
-        _CoreAPI.checkVersionNotNullAndSupported(iciForDefaults);
-        locale = Configuration.getDefaultLocale();
-        timeZone = Configuration.getDefaultTimeZone();
-        sqlDateAndTimeTimeZone = null;
-        sqlDateAndTimeTimeZoneSet = true;
-        numberFormat = "number";
-        timeFormat = "";
-        dateFormat = "";
-        dateTimeFormat = "";
-        templateExceptionHandler = Configuration.getDefaultTemplateExceptionHandler();
-        arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE;
-        objectWrapper = Configuration.getDefaultObjectWrapper(iciForDefaults);
-        autoFlush = Boolean.TRUE;
-        newBuiltinClassResolver = TemplateClassResolver.UNRESTRICTED_RESOLVER;
-        showErrorTips = Boolean.TRUE;
-        apiBuiltinEnabled = Boolean.FALSE;
-        logTemplateExceptions = Boolean.FALSE;
-        // outputEncoding and urlEscapingCharset defaults to null,
-        // which means "not specified"
-        setBooleanFormat(TemplateBooleanFormat.C_TRUE_FALSE);
-        customDateFormats = Collections.emptyMap();
-        customNumberFormats = Collections.emptyMap();
-        outputEncodingSet = true;
-        urlEscapingCharsetSet = true;
+    protected MutableProcessingConfiguration() {
+        // Empty
+    }
 
-        lazyImports = false;
-        lazyAutoImportsSet = true;
-        initAutoImportsMap();
-        initAutoIncludesList();
+    @Override
+    public Locale getLocale() {
+         return isLocaleSet() ? locale : getDefaultLocale();
     }
 
     /**
-     * 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.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
-    protected MutableProcessingConfiguration() {
-        // Empty
+    protected abstract Locale getDefaultLocale();
+
+    @Override
+    public boolean isLocaleSet() {
+        return locale != null;
     }
 
     /**
@@ -397,21 +374,12 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
-    @Override
-    public Locale getLocale() {
-         return isLocaleSet() ? locale : getInheritedLocale();
-    }
-
-    protected abstract Locale getInheritedLocale();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
      */
-    @Override
-    public boolean isLocaleSet() {
-        return locale != null;
+    public void unsetLocale() {
+        locale = null;
     }
 
     /**
@@ -429,19 +397,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         setTimeZone(value);
         return self();
     }
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetTimeZone() {
+        this.timeZone = null;
+    }
 
     @Override
     public TimeZone getTimeZone() {
-         return isTimeZoneSet() ? timeZone : getInheritedTimeZone();
+         return isTimeZoneSet() ? timeZone : getDefaultTimeZone();
     }
 
-    protected abstract TimeZone getInheritedTimeZone();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract TimeZone getDefaultTimeZone();
+
     @Override
     public boolean isTimeZoneSet() {
         return timeZone != null;
@@ -463,20 +437,28 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetSQLDateAndTimeTimeZone() {
+        sqlDateAndTimeTimeZone = null;
+        sqlDateAndTimeTimeZoneSet = false;
+    }
+
     @Override
     public TimeZone getSQLDateAndTimeTimeZone() {
         return sqlDateAndTimeTimeZoneSet
                 ? sqlDateAndTimeTimeZone
-                : getInheritedSQLDateAndTimeTimeZone();
+                : getDefaultSQLDateAndTimeTimeZone();
     }
 
-    protected abstract TimeZone getInheritedSQLDateAndTimeTimeZone();
-
     /**
-     * Tells if this setting is set directly in this object or its value is inherited from a parent processing configuration.
-     *  
-     * @since 2.3.24
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract TimeZone getDefaultSQLDateAndTimeTimeZone();
+
     @Override
     public boolean isSQLDateAndTimeTimeZoneSet() {
         return sqlDateAndTimeTimeZoneSet;
@@ -498,18 +480,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetNumberFormat() {
+        numberFormat = null;
+    }
+
     @Override
     public String getNumberFormat() {
-         return isNumberFormatSet() ? numberFormat : getInheritedNumberFormat();
+         return isNumberFormatSet() ? numberFormat : getDefaultNumberFormat();
     }
 
-    protected abstract String getInheritedNumberFormat();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract String getDefaultNumberFormat();
+
     @Override
     public boolean isNumberFormatSet() {
         return numberFormat != null;
@@ -517,10 +506,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     
     @Override
     public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
-         return isCustomNumberFormatsSet() ? customNumberFormats : getInheritedCustomNumberFormats();
+         return isCustomNumberFormatsSet() ? customNumberFormats : getDefaultCustomNumberFormats();
     }
 
-    protected abstract Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats();
+    protected abstract Map<String, TemplateNumberFormatFactory> getDefaultCustomNumberFormats();
 
     /**
      * Setter pair of {@link #getCustomNumberFormats()}. Note that custom number formats are get through
@@ -545,6 +534,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetCustomNumberFormats() {
+        customNumberFormats = null;
+    }
+
     private void validateFormatNames(Set<String> keySet) {
         for (String name : keySet) {
             if (name.length() == 0) {
@@ -568,13 +565,6 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         }
     }
 
-    /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
-     *  
-     * @since 2.3.24
-     */
     @Override
     public boolean isCustomNumberFormatsSet() {
         return customNumberFormats != null;
@@ -589,10 +579,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
                 return r;
             }
         }
-        return getInheritedCustomNumberFormat(name);
+        return getDefaultCustomNumberFormat(name);
     }
 
-    protected abstract TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name);
+    protected abstract TemplateNumberFormatFactory getDefaultCustomNumberFormat(String name);
 
     /**
      * Setter pair of {@link #getBooleanFormat()}.
@@ -617,21 +607,26 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         setBooleanFormat(value);
         return self();
     }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetBooleanFormat() {
+        booleanFormat = null;
+    }
     
     @Override
     public String getBooleanFormat() {
-         return isBooleanFormatSet() ? booleanFormat : getInheritedBooleanFormat();
+         return isBooleanFormatSet() ? booleanFormat : getDefaultBooleanFormat();
     }
 
-    protected abstract String getInheritedBooleanFormat();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
-     *  
-     * @since 2.3.24
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract String getDefaultBooleanFormat();
+
     @Override
     public boolean isBooleanFormatSet() {
         return booleanFormat != null;
@@ -653,18 +648,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetTimeFormat() {
+        timeFormat = null;
+    }
+
     @Override
     public String getTimeFormat() {
-         return isTimeFormatSet() ? timeFormat : getInheritedTimeFormat();
+         return isTimeFormatSet() ? timeFormat : getDefaultTimeFormat();
     }
 
-    protected abstract String getInheritedTimeFormat();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract String getDefaultTimeFormat();
+
     @Override
     public boolean isTimeFormatSet() {
         return timeFormat != null;
@@ -686,18 +688,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetDateFormat() {
+        dateFormat = null;
+    }
+
     @Override
     public String getDateFormat() {
-         return isDateFormatSet() ? dateFormat : getInheritedDateFormat();
+         return isDateFormatSet() ? dateFormat : getDefaultDateFormat();
     }
 
-    protected abstract String getInheritedDateFormat();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract String getDefaultDateFormat();
+
     @Override
     public boolean isDateFormatSet() {
         return dateFormat != null;
@@ -719,18 +728,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetDateTimeFormat() {
+        this.dateTimeFormat = null;
+    }
+
     @Override
     public String getDateTimeFormat() {
-         return isDateTimeFormatSet() ? dateTimeFormat : getInheritedDateTimeFormat();
+         return isDateTimeFormatSet() ? dateTimeFormat : getDefaultDateTimeFormat();
     }
 
-    protected abstract String getInheritedDateTimeFormat();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract String getDefaultDateTimeFormat();
+
     @Override
     public boolean isDateTimeFormatSet() {
         return dateTimeFormat != null;
@@ -738,10 +754,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
 
     @Override
     public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
-         return isCustomDateFormatsSet() ? customDateFormats : getInheritedCustomDateFormats();
+         return isCustomDateFormatsSet() ? customDateFormats : getDefaultCustomDateFormats();
     }
 
-    protected abstract Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats();
+    protected abstract Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats();
 
     /**
      * Setter pair of {@link #getCustomDateFormat(String)}.
@@ -761,10 +777,13 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
      */
+    public void unsetCustomDateFormats() {
+        this.customDateFormats = null;
+    }
+
     @Override
     public boolean isCustomDateFormatsSet() {
         return customDateFormats != null;
@@ -779,10 +798,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
                 return r;
             }
         }
-        return getInheritedCustomDateFormat(name);
+        return getDefaultCustomDateFormat(name);
     }
 
-    protected abstract TemplateDateFormatFactory getInheritedCustomDateFormat(String name);
+    protected abstract TemplateDateFormatFactory getDefaultCustomDateFormat(String name);
 
     /**
      * Setter pair of {@link #getTemplateExceptionHandler()}
@@ -800,19 +819,26 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetTemplateExceptionHandler() {
+        templateExceptionHandler = null;
+    }
+
     @Override
     public TemplateExceptionHandler getTemplateExceptionHandler() {
          return isTemplateExceptionHandlerSet()
-                ? templateExceptionHandler : getInheritedTemplateExceptionHandler();
+                ? templateExceptionHandler : getDefaultTemplateExceptionHandler();
     }
 
-    protected abstract TemplateExceptionHandler getInheritedTemplateExceptionHandler();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract TemplateExceptionHandler getDefaultTemplateExceptionHandler();
+
     @Override
     public boolean isTemplateExceptionHandlerSet() {
         return templateExceptionHandler != null;
@@ -834,18 +860,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetArithmeticEngine() {
+        this.arithmeticEngine = null;
+    }
+
     @Override
     public ArithmeticEngine getArithmeticEngine() {
-         return isArithmeticEngineSet() ? arithmeticEngine : getInheritedArithmeticEngine();
+         return isArithmeticEngineSet() ? arithmeticEngine : getDefaultArithmeticEngine();
     }
 
-    protected abstract ArithmeticEngine getInheritedArithmeticEngine();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract ArithmeticEngine getDefaultArithmeticEngine();
+
     @Override
     public boolean isArithmeticEngineSet() {
         return arithmeticEngine != null;
@@ -860,6 +893,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetObjectWrapper() {
+        objectWrapper = null;
+    }
+
+    /**
      * Fluent API equivalent of {@link #setObjectWrapper(ObjectWrapper)}
      */
     public SelfT objectWrapper(ObjectWrapper value) {
@@ -870,16 +911,15 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     @Override
     public ObjectWrapper getObjectWrapper() {
          return isObjectWrapperSet()
-                ? objectWrapper : getInheritedObjectWrapper();
+                ? objectWrapper : getDefaultObjectWrapper();
     }
 
-    protected abstract ObjectWrapper getInheritedObjectWrapper();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract ObjectWrapper getDefaultObjectWrapper();
+
     @Override
     public boolean isObjectWrapperSet() {
         return objectWrapper != null;
@@ -901,20 +941,28 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetOutputEncoding() {
+        this.outputEncoding = null;
+        outputEncodingSet = false;
+    }
+
     @Override
     public Charset getOutputEncoding() {
         return isOutputEncodingSet()
                 ? outputEncoding
-                : getInheritedOutputEncoding();
+                : getDefaultOutputEncoding();
     }
 
-    protected abstract Charset getInheritedOutputEncoding();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract Charset getDefaultOutputEncoding();
+
     @Override
     public boolean isOutputEncodingSet() {
         return outputEncodingSet;
@@ -936,18 +984,26 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetURLEscapingCharset() {
+        this.urlEscapingCharset = null;
+        urlEscapingCharsetSet = false;
+    }
+
     @Override
     public Charset getURLEscapingCharset() {
-        return isURLEscapingCharsetSet() ? urlEscapingCharset : getInheritedURLEscapingCharset();
+        return isURLEscapingCharsetSet() ? urlEscapingCharset : getDefaultURLEscapingCharset();
     }
 
-    protected abstract Charset getInheritedURLEscapingCharset();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract Charset getDefaultURLEscapingCharset();
+
     @Override
     public boolean isURLEscapingCharsetSet() {
         return urlEscapingCharsetSet;
@@ -969,21 +1025,26 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetNewBuiltinClassResolver() {
+        this.newBuiltinClassResolver = null;
+    }
+
     @Override
     public TemplateClassResolver getNewBuiltinClassResolver() {
          return isNewBuiltinClassResolverSet()
-                ? newBuiltinClassResolver : getInheritedNewBuiltinClassResolver();
+                ? newBuiltinClassResolver : getDefaultNewBuiltinClassResolver();
     }
 
-    protected abstract TemplateClassResolver getInheritedNewBuiltinClassResolver();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
-     *  
-     * @since 2.3.24
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract TemplateClassResolver getDefaultNewBuiltinClassResolver();
+
     @Override
     public boolean isNewBuiltinClassResolverSet() {
         return newBuiltinClassResolver != null;
@@ -993,7 +1054,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * Setter pair of {@link #getAutoFlush()}
      */
     public void setAutoFlush(boolean autoFlush) {
-        this.autoFlush = Boolean.valueOf(autoFlush);
+        this.autoFlush = autoFlush;
     }
 
     /**
@@ -1004,20 +1065,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetAutoFlush() {
+        this.autoFlush = null;
+    }
+
     @Override
     public boolean getAutoFlush() {
-         return isAutoFlushSet() ? autoFlush.booleanValue() : getInheritedAutoFlush();
+         return isAutoFlushSet() ? autoFlush : getDefaultAutoFlush();
     }
 
-    protected abstract boolean getInheritedAutoFlush();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
-     *  
-     * @since 2.3.24
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract boolean getDefaultAutoFlush();
+
     @Override
     public boolean isAutoFlushSet() {
         return autoFlush != null;
@@ -1027,7 +1093,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * Setter pair of {@link #getShowErrorTips()}
      */
     public void setShowErrorTips(boolean showTips) {
-        showErrorTips = Boolean.valueOf(showTips);
+        showErrorTips = showTips;
     }
 
     /**
@@ -1038,18 +1104,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetShowErrorTips() {
+        showErrorTips = null;
+    }
+
     @Override
     public boolean getShowErrorTips() {
-         return isShowErrorTipsSet() ? showErrorTips : getInheritedShowErrorTips();
+         return isShowErrorTipsSet() ? showErrorTips : getDefaultShowErrorTips();
     }
 
-    protected abstract boolean getInheritedShowErrorTips();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract boolean getDefaultShowErrorTips();
+
     @Override
     public boolean isShowErrorTipsSet() {
         return showErrorTips != null;
@@ -1070,25 +1143,46 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         return self();
     }
 
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetAPIBuiltinEnabled() {
+        apiBuiltinEnabled = null;
+    }
+
     @Override
     public boolean getAPIBuiltinEnabled() {
-         return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getInheritedAPIBuiltinEnabled();
+         return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getDefaultAPIBuiltinEnabled();
     }
 
-    protected abstract boolean getInheritedAPIBuiltinEnabled();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
-     *  
-     * @since 2.3.24
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract boolean getDefaultAPIBuiltinEnabled();
+
     @Override
     public boolean isAPIBuiltinEnabledSet() {
         return apiBuiltinEnabled != null;
     }
 
+    @Override
+    public boolean getLogTemplateExceptions() {
+         return isLogTemplateExceptionsSet() ? logTemplateExceptions : getDefaultLogTemplateExceptions();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract boolean getDefaultLogTemplateExceptions();
+
+    @Override
+    public boolean isLogTemplateExceptionsSet() {
+        return logTemplateExceptions != null;
+    }
+
     /**
      * Setter pair of {@link #getLogTemplateExceptions()}
      */
@@ -1096,29 +1190,32 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         logTemplateExceptions = value;
     }
 
-    @Override
-    public boolean getLogTemplateExceptions() {
-         return isLogTemplateExceptionsSet() ? logTemplateExceptions : getInheritedLogTemplateExceptions();
+    /**
+     * Fluent API equivalent of {@link #setLogTemplateExceptions(boolean)}
+     */
+    public SelfT logTemplateExceptions(boolean value) {
+        setLogTemplateExceptions(value);
+        return self();
     }
 
-    protected abstract boolean getInheritedLogTemplateExceptions();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
      */
-    @Override
-    public boolean isLogTemplateExceptionsSet() {
-        return logTemplateExceptions != null;
+    public void unsetLogTemplateExceptions() {
+        logTemplateExceptions = null;
     }
-    
+
     @Override
     public boolean getLazyImports() {
-         return isLazyImportsSet() ? lazyImports : getInheritedLazyImports();
+         return isLazyImportsSet() ? lazyImports : getDefaultLazyImports();
     }
 
-    protected abstract boolean getInheritedLazyImports();
+    /**
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract boolean getDefaultLazyImports();
 
     /**
      * Setter pair of {@link #getLazyImports()}
@@ -1128,21 +1225,36 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Fluent API equivalent of {@link #setLazyImports(boolean)}
      */
+    public SelfT lazyImports(boolean lazyImports) {
+        setLazyImports(lazyImports);
+        return  self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetLazyImports() {
+        this.lazyImports = null;
+    }
+
     @Override
     public boolean isLazyImportsSet() {
         return lazyImports != null;
     }
-    
+
     @Override
     public Boolean getLazyAutoImports() {
-        return isLazyAutoImportsSet() ? lazyAutoImports : getInheritedLazyAutoImports();
+        return isLazyAutoImportsSet() ? lazyAutoImports : getDefaultLazyAutoImports();
     }
 
-    protected abstract Boolean getInheritedLazyAutoImports();
+    /**
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract Boolean getDefaultLazyAutoImports();
 
     /**
      * Setter pair of {@link #getLazyAutoImports()}
@@ -1151,12 +1263,24 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         this.lazyAutoImports = lazyAutoImports;
         lazyAutoImportsSet = true;
     }
-    
+
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Fluent API equivalent of {@link #setLazyAutoImports(Boolean)}
      */
+    public SelfT lazyAutoImports(Boolean lazyAutoImports) {
+        setLazyAutoImports(lazyAutoImports);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetLazyAutoImports() {
+        lazyAutoImports = null;
+        lazyAutoImportsSet = false;
+    }
+
     @Override
     public boolean isLazyAutoImportsSet() {
         return lazyAutoImportsSet;
@@ -1219,21 +1343,34 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
             addAutoImport((String) key, (String) value);
         }
     }
-    
+
+    /**
+     * Fluent API equivalent of {@link #setAutoImports(Map)}
+     */
+    public SelfT autoImports(Map map) {
+        setAutoImports(map);
+        return self();
+    }
+
+     /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ProcessingConfiguration}).
+     */
+    public void unsetAutoImports() {
+        autoImports = null;
+    }
+
     @Override
     public Map<String, String> getAutoImports() {
-         return isAutoImportsSet() ? autoImports : getInheritedAutoImports();
+         return isAutoImportsSet() ? autoImports : getDefaultAutoImports();
     }
 
-    protected abstract Map<String,String> getInheritedAutoImports();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
-     * 
-     * @since 2.3.25
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract Map<String,String> getDefaultAutoImports();
+
     @Override
     public boolean isAutoImportsSet() {
         return autoImports != null;
@@ -1274,18 +1411,25 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         }
     }
 
+    /**
+     * Fluent API equivalent of {@link #setAutoIncludes(List)}
+     */
+    public SelfT autoIncludes(List templateNames) {
+        setAutoIncludes(templateNames);
+        return self();
+    }
+
     @Override
     public List<String> getAutoIncludes() {
-         return isAutoIncludesSet() ? autoIncludes : getInheritedAutoIncludes();
+         return isAutoIncludesSet() ? autoIncludes : getDefaultAutoIncludes();
     }
 
-    protected abstract List<String> getInheritedAutoIncludes();
-
     /**
-     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
-     * the setting might returns a default value, or returns the value of the setting from a parent processing
-     * configuration or throws a {@link SettingValueNotSetException}.
+     * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+     * from another {@link ProcessingConfiguration}), or throws {@link SettingValueNotSetException}.
      */
+    protected abstract List<String> getDefaultAutoIncludes();
+
     @Override
     public boolean isAutoIncludesSet() {
         return autoIncludes != null;
@@ -1402,22 +1546,22 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      *       <br>String value: {@code "true"}, {@code "false"}, {@code "y"},  etc.
      *       
      *   <li><p>{@code "auto_import"}:
-     *       See {@link Configuration#setAutoImports(Map)}
+     *       See {@link Configuration#getAutoImports()}
      *       <br>String value is something like:
      *       <br>{@code /lib/form.ftl as f, /lib/widget as w, "/lib/odd name.ftl" as odd}
      *       
      *   <li><p>{@code "auto_include"}: Sets the list of auto-includes.
-     *       See {@link Configuration#setAutoIncludes(List)}
+     *       See {@link Configuration#getAutoIncludes()}
      *       <br>String value is something like:
      *       <br>{@code /include/common.ftl, "/include/evil name.ftl"}
      *       
      *   <li><p>{@code "lazy_auto_imports"}:
-     *       See {@link Configuration#setLazyAutoImports(Boolean)}.
+     *       See {@link Configuration#getLazyAutoImports()}.
      *       <br>String value: {@code "true"}, {@code "false"} (also the equivalents: {@code "yes"}, {@code "no"},
      *       {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}), case insensitive. Also can be {@code "null"}.
 
      *   <li><p>{@code "lazy_imports"}:
-     *       See {@link Configuration#setLazyImports(boolean)}.
+     *       See {@link Configuration#getLazyImports()}.
      *       <br>String value: {@code "true"}, {@code "false"} (also the equivalents: {@code "yes"}, {@code "no"},
      *       {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}), case insensitive.
      *       
@@ -1490,43 +1634,43 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * <p>{@link Configuration} (a subclass of {@link MutableProcessingConfiguration}) also understands these:</p>
      * <ul>
      *   <li><p>{@code "auto_escaping"}:
-     *       See {@link Configuration#setAutoEscapingPolicy(int)}
+     *       See {@link Configuration#getAutoEscapingPolicy()}
      *       <br>String value: {@code "enable_if_default"} or {@code "enableIfDefault"} for
-     *       {@link Configuration#ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
+     *       {@link ParsingConfiguration#ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
      *       {@code "enable_if_supported"} or {@code "enableIfSupported"} for
-     *       {@link Configuration#ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}
-     *       {@code "disable"} for {@link Configuration#DISABLE_AUTO_ESCAPING_POLICY}.
+     *       {@link ParsingConfiguration#ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}
+     *       {@code "disable"} for {@link ParsingConfiguration#DISABLE_AUTO_ESCAPING_POLICY}.
      *       
      *   <li><p>{@code "sourceEncoding"}:
-     *       See {@link Configuration#setSourceEncoding(Charset)}; since 2.3.26 also accepts value "JVM default"
+     *       See {@link Configuration#getSourceEncoding()}; since 2.3.26 also accepts value "JVM default"
      *       (not case sensitive) to set the Java environment default value.
      *       <br>As the default value is the system default, which can change
      *       from one server to another, <b>you should always set this!</b>
      *       
      *   <li><p>{@code "localized_lookup"}:
-     *       See {@link Configuration#setLocalizedLookup}.
+     *       See {@link Configuration#getLocalizedLookup()}.
      *       <br>String value: {@code "true"}, {@code "false"} (also the equivalents: {@code "yes"}, {@code "no"},
      *       {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}).
      *       ASTDirCase insensitive.
      *       
      *   <li><p>{@code "output_format"}:
-     *       See {@link Configuration#setOutputFormat(OutputFormat)}.
+     *       See {@link ParsingConfiguration#getOutputFormat()}.
      *       <br>String value: {@code "default"} (case insensitive) for the default, or an
      *       <a href="#fm_obe">object builder expression</a> that gives an {@link OutputFormat}, for example
      *       {@code HTMLOutputFormat} or {@code XMLOutputFormat}.
      *       
      *   <li><p>{@code "registered_custom_output_formats"}:
-     *       See {@link Configuration#setRegisteredCustomOutputFormats(Collection)}.
+     *       See {@link Configuration#getRegisteredCustomOutputFormats()}.
      *       <br>String value: an <a href="#fm_obe">object builder expression</a> that gives a {@link List} of
      *       {@link OutputFormat}-s.
      *       Example: {@code [com.example.MyOutputFormat(), com.example.MyOtherOutputFormat()]}
      *       
      *   <li><p>{@code "whitespace_stripping"}:
-     *       See {@link Configuration#setWhitespaceStripping}.
+     *       See {@link ParsingConfiguration#getWhitespaceStripping()}.
      *       <br>String value: {@code "true"}, {@code "false"}, {@code yes}, etc.
      *       
      *   <li><p>{@code "cache_storage"}:
-     *       See {@link Configuration#setCacheStorage}.
+     *       See {@link Configuration#getCacheStorage()}.
      *       <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
      *       expression</a>.
      *       If the value does not contain dot,
@@ -1549,48 +1693,48 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      *       
      *   <li><p>{@code "template_update_delay"}:
      *       Template update delay in <b>seconds</b> (not in milliseconds) if no unit is specified; see
-     *       {@link Configuration#setTemplateUpdateDelayMilliseconds(long)} for more.
+     *       {@link Configuration#getTemplateUpdateDelayMilliseconds()} for more.
      *       <br>String value: Valid positive integer, optionally followed by a time unit (recommended). The default
      *       unit is seconds. It's strongly recommended to specify the unit for clarity, like in "500 ms" or "30 s".
      *       Supported units are: "s" (seconds), "ms" (milliseconds), "m" (minutes), "h" (hours). The whitespace between
      *       the unit and the number is optional. Units are only supported since 2.3.23.
      *       
      *   <li><p>{@code "tag_syntax"}:
-     *       See {@link Configuration#setTagSyntax(int)}.
+     *       See {@link ParsingConfiguration#getTagSyntax()}.
      *       <br>String value: Must be one of
      *       {@code "auto_detect"}, {@code "angle_bracket"}, and {@code "square_bracket"}. 
      *       
      *   <li><p>{@code "naming_convention"}:
-     *       See {@link Configuration#setNamingConvention(int)}.
+     *       See {@link ParsingConfiguration#getNamingConvention()}.
      *       <br>String value: Must be one of
      *       {@code "auto_detect"}, {@code "legacy"}, and {@code "camel_case"}.
      *       
      *   <li><p>{@code "incompatible_improvements"}:
-     *       See {@link Configuration#setIncompatibleImprovements(Version)}.
+     *       See {@link Configuration#getIncompatibleImprovements()}.
      *       <br>String value: version number like {@code 2.3.20}.
      *       
      *   <li><p>{@code "recognize_standard_file_extensions"}:
-     *       See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}.
+     *       See {@link Configuration#getRecognizeStandardFileExtensions()}.
      *       <br>String value: {@code "default"} (case insensitive) for the default, or {@code "true"}, {@code "false"},
      *       {@code yes}, etc.
      *       
      *   <li><p>{@code "template_configurations"}:
-     *       See: {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)}.
+     *       See: {@link Configuration#getTemplateConfigurations()}.
      *       <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>,
      *       can be {@code null}.
      *       
      *   <li><p>{@code "template_loader"}:
-     *       See: {@link Configuration#setTemplateLoader(TemplateLoader)}.
+     *       See: {@link Configuration#getTemplateLoader()}.
      *       <br>String value: {@code "default"} (case insensitive) for the default, or else interpreted as an
      *       <a href="#fm_obe">object builder expression</a>. {@code "null"} is also allowed.
      *       
      *   <li><p>{@code "template_lookup_strategy"}:
-     *       See: {@link Configuration#setTemplateLookupStrategy(org.apache.freemarker.core.templateresolver.TemplateLookupStrategy)}.
+     *       See: {@link Configuration#getTemplateLookupStrategy()}.
      *       <br>String value: {@code "default"} (case insensitive) for the default, or else interpreted as an
      *       <a href="#fm_obe">object builder expression</a>.
      *       
      *   <li><p>{@code "template_name_format"}:
-     *       See: {@link Configuration#setTemplateNameFormat(org.apache.freemarker.core.templateresolver.TemplateNameFormat)}.
+     *       See: {@link Configuration#getTemplateNameFormat()}.
      *       <br>String value: {@code "default"} (case insensitive) for the default, {@code "default_2_3_0"}
      *       for {@link DefaultTemplateNameFormatFM2#INSTANCE}, {@code "default_2_4_0"} for
      *       {@link DefaultTemplateNameFormat#INSTANCE}.
@@ -1743,8 +1887,9 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
                     } else if ("rethrow".equalsIgnoreCase(value)) {
                         setTemplateExceptionHandler(
                                 TemplateExceptionHandler.RETHROW_HANDLER);
-                    } else if (DEFAULT_VALUE.equalsIgnoreCase(value) && this instanceof Configuration) {
-                        ((Configuration) this).unsetTemplateExceptionHandler();
+                    } else if (DEFAULT_VALUE.equalsIgnoreCase(value)
+                            && this instanceof Configuration.ExtendableBuilder) {
+                        unsetTemplateExceptionHandler();
                     } else {
                         throw new ConfigurationSettingValueException(
                                 name, value,
@@ -1770,12 +1915,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
                 }
             } else if (OBJECT_WRAPPER_KEY_SNAKE_CASE.equals(name) || OBJECT_WRAPPER_KEY_CAMEL_CASE.equals(name)) {
                 if (DEFAULT_VALUE.equalsIgnoreCase(value)) {
-                    if (this instanceof Configuration) {
-                        ((Configuration) this).unsetObjectWrapper();
+                    if (this instanceof Configuration.ExtendableBuilder) {
+                        this.unsetObjectWrapper();
                     } else {
-                        setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0));
+                        // FM3 TODO should depend on IcI
+                        setObjectWrapper(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
                     }
                 } else if ("restricted".equalsIgnoreCase(value)) {
+                    // FM3 TODO should depend on IcI
                     setObjectWrapper(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
                 } else {
                     setObjectWrapper((ObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
@@ -1857,6 +2004,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
+     * Fluent API equivalent of {@link #setSetting(String, String)}.
+     */
+    public SelfT setting(String name, String value) throws ConfigurationException {
+        setSetting(name, value);
+        return self();
+    }
+
+    /**
      * @throws IllegalArgumentException
      *             if the type of the some of the values isn't as expected
      */
@@ -1879,11 +2034,11 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      *            If we want the setting names with camel case naming convention, or with snake case (legacy) naming
      *            convention.
      * 
-     * @see Configuration#getSettingNames(boolean)
+     * @see Configuration.ExtendableBuilder#getSettingNames(boolean)
      * 
      * @since 2.3.24
      */
-    public Set<String> getSettingNames(boolean camelCase) {
+    public static Set<String> getSettingNames(boolean camelCase) {
         return new _SortedArraySet<>(camelCase ? SETTING_NAMES_CAMEL_CASE : SETTING_NAMES_SNAKE_CASE);
     }
 
@@ -1945,11 +2100,21 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         }
     }
 
+    /**
+     * Fluent API equivalent of {@link #setSettings(Properties)}.
+     */
+    public SelfT settings(Properties props) {
+        setSettings(props);
+        return self();
+    }
+
     @Override
     public Map<Object, Object> getCustomAttributes() {
-        return customAttributes;
+        return isCustomAttributesSet() ? customAttributes : getDefaultCustomAttributes();
     }
 
+    protected abstract Map<Object,Object> getDefaultCustomAttributes();
+
     /**
      * Setter pair of {@link #getCustomAttributes()}
      *
@@ -1960,6 +2125,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
+     * Fluent API equivalent of {@link #setCustomAttributes(Map)}
+     */
+    public SelfT customAttributes(Map<Object, Object> customAttributes) {
+        setCustomAttributes(customAttributes);
+        return self();
+    }
+
+    /**
      * Used internally instead of {@link #setCustomAttributes(Map)} to speed up use cases where we know that there
      * won't be aliasing problems.
      */
@@ -1995,6 +2168,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
+     * Fluent API equivalent of {@link #setCustomAttribute(Object, Object)}
+     */
+    public SelfT customAttribute(Object name, Object value) {
+        setCustomAttribute(name, value);
+        return self();
+    }
+
+    /**
      * Returns an array with names of all custom attributes defined directly on this {@link ProcessingConfiguration}.
      * (That is, it doesn't contain the names of custom attributes inherited from other {@link
      * ProcessingConfiguration}-s.) The returned array is never {@code null}, but can be zero-length.
@@ -2043,10 +2224,10 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
         } else {
             value = null;
         }
-        return value != null ? value : getInheritedCustomAttribute(key);
+        return value != null ? value : getDefaultCustomAttribute(key);
     }
 
-    protected abstract Object getInheritedCustomAttribute(Object name);
+    protected abstract Object getDefaultCustomAttribute(Object name);
 
     protected final List<String> parseAsList(String text) throws GenericParseException {
         return new SettingStringParser(text).parseAsList();



[07/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
index 8f348de..dcb9222 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
@@ -61,7 +61,7 @@ public abstract class URLTemplateLoader implements TemplateLoader {
     /**
      * Sets if {@link URLConnection#setUseCaches(boolean)} will be called, and with what value. By default this is
      * {@code false}, because FreeMarker has its own template cache with its own update delay setting
-     * ({@link Configuration#setTemplateUpdateDelayMilliseconds(long)}). If this is set to {@code null},
+     * ({@link Configuration#getTemplateUpdateDelayMilliseconds()}). If this is set to {@code null},
      * {@link URLConnection#setUseCaches(boolean)} won't be called.
      */
     public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java b/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
index 8939121..11aab33 100644
--- a/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
+++ b/src/main/java/org/apache/freemarker/core/util/CommonBuilder.java
@@ -19,11 +19,17 @@
 
 package org.apache.freemarker.core.util;
 
+import org.apache.freemarker.core.ConfigurationException;
+
 /**
  * Interface of builders (used for implementing the builder pattern).
  */
 public interface CommonBuilder<ProductT> {
 
-    ProductT build();
+    /**
+     * Creates an instance of the product class. This is usually a new instance, though if the product is stateless,
+     * it's possibly a shared object instead of a new one.
+     */
+    ProductT build() throws ConfigurationException;
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_StringUtil.java b/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
index 2827c84..50c1874 100644
--- a/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
@@ -29,8 +29,8 @@ import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.regex.Pattern;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.ParsingConfiguration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.Version;
 
@@ -1604,21 +1604,21 @@ public class _StringUtil {
     }
 
     /**
-     * @return {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}, or {@link Configuration#LEGACY_NAMING_CONVENTION}
-     *         or, {@link Configuration#AUTO_DETECT_NAMING_CONVENTION} when undecidable.
+     * @return {@link ParsingConfiguration#CAMEL_CASE_NAMING_CONVENTION}, or {@link ParsingConfiguration#LEGACY_NAMING_CONVENTION}
+     *         or, {@link ParsingConfiguration#AUTO_DETECT_NAMING_CONVENTION} when undecidable.
      */
     public static int getIdentifierNamingConvention(String name) {
         final int ln = name.length();
         for (int i = 0; i < ln; i++) {
             final char c = name.charAt(i);
             if (c == '_') {
-                return Configuration.LEGACY_NAMING_CONVENTION;
+                return ParsingConfiguration.LEGACY_NAMING_CONVENTION;
             }
             if (_StringUtil.isUpperUSASCII(c)) {
-                return Configuration.CAMEL_CASE_NAMING_CONVENTION;
+                return ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION;
             }
         }
-        return Configuration.AUTO_DETECT_NAMING_CONVENTION;
+        return ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION;
     }
 
     // [2.4] Won't be needed anymore

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java b/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
index 8fb0a0d..9203e5a 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
@@ -29,5 +29,14 @@ public abstract class TemplateValueFormat {
      * Meant to be used in error messages to tell what format the parsed string didn't fit.
      */
     public abstract String getDescription();
-    
+
+    /**
+     * The implementation in {@link TemplateValueFormat} returns {@code package.className(description)}, where
+     * description comes from {@link #getDescription()}.
+     */
+    @Override
+    public String toString() {
+        return getClass().getName() + "(" + getDescription() + ")";
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/dom/NodeListModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/NodeListModel.java b/src/main/java/org/apache/freemarker/dom/NodeListModel.java
index 75b9e13..333bb5c 100644
--- a/src/main/java/org/apache/freemarker/dom/NodeListModel.java
+++ b/src/main/java/org/apache/freemarker/dom/NodeListModel.java
@@ -44,10 +44,9 @@ import org.w3c.dom.NodeList;
  * Used when the result set contains 0 or multiple nodes; shouldn't be used when you have exactly 1 node. For exactly 1
  * node, use {@link NodeModel#wrap(Node)}, because {@link NodeModel} subclasses can have extra features building on that
  * restriction, like single elements with text content can be used as FTL string-s.
- * 
  * <p>
- * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as shared variable (
- * {@link Configuration#setSharedVariable(String, Object)}).
+ * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as
+ * {@linkplain Configuration#getSharedVariables() shared variable}.
  */
 class NodeListModel extends SimpleSequence implements TemplateHashModel, _UnexpectedTypeErrorExplainerTemplateModel {
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/dom/NodeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/NodeModel.java b/src/main/java/org/apache/freemarker/dom/NodeModel.java
index b1d3cc6..37a5c7d 100644
--- a/src/main/java/org/apache/freemarker/dom/NodeModel.java
+++ b/src/main/java/org/apache/freemarker/dom/NodeModel.java
@@ -55,23 +55,19 @@ import org.w3c.dom.Text;
 
 /**
  * A base class for wrapping a single W3C DOM_WRAPPER Node as a FreeMarker template model.
- * 
  * <p>
  * Note that {@link DefaultObjectWrapper} automatically wraps W3C DOM_WRAPPER {@link Node}-s into this, so you may need do that
  * with this class manually. However, before dropping the {@link Node}-s into the data-model, you certainly want to
  * apply {@link NodeModel#simplify(Node)} on them.
- * 
  * <p>
- * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as shared variable (
- * {@link Configuration#setSharedVariable(String, Object)}).
- * 
+ * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as
+ * {@linkplain Configuration#getSharedVariables() shared variable}.
  * <p>
  * To represent a node sequence (such as a query result) of exactly 1 nodes, this class should be used instead of
  * {@link NodeListModel}, as it adds extra capabilities by utilizing that we have exactly 1 node. If you need to wrap a
  * node sequence of 0 or multiple nodes, you must use {@link NodeListModel}.
  */
-abstract public class NodeModel
-implements TemplateNodeModelEx, TemplateHashModel, TemplateSequenceModel,
+abstract public class NodeModel implements TemplateNodeModelEx, TemplateHashModel, TemplateSequenceModel,
     AdapterTemplateModel, WrapperTemplateModel, _UnexpectedTypeErrorExplainerTemplateModel {
 
     static private final Logger LOG = DomLog.LOG;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
index 3cdc108..b4a2602 100644
--- a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -41,21 +41,20 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.apache.freemarker.core.MutableProcessingConfiguration;
 import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
 import org.apache.freemarker.core.ConfigurationException;
 import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.TemplateExceptionHandler;
 import org.apache.freemarker.core.TemplateNotFoundException;
-import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core._CoreLogs;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
@@ -267,12 +266,13 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  * 
  * <li>The following init-params are supported only for backward compatibility, and their usage is discouraged:
  * {@code TemplateUpdateInterval}, {@code DefaultEncoding}, {@code ObjectWrapper}, {@code TemplateExceptionHandler}.
- * Instead, use init-params with the setting names documented at {@link Configuration#setSetting(String, String)}, such
- * as {@code object_wrapper}.
+ * Instead, use init-params with the setting names documented at
+ * {@link ExtendableBuilder#setSetting(String, String)}, such as {@code object_wrapper}.
  * 
  * <li><strong>Any other init-params</strong> will be interpreted as {@link Configuration}-level FreeMarker setting. See
- * the possible names and values at {@link Configuration#setSetting(String, String)}. Note that these init-param names
- * are starting with lower-case letter (upper-case init-params are used for FreemarkerSerlvet settings).</li>
+ * the possible names and values at {@link ExtendableBuilder#setSetting(String, String)}. Note that
+ * these init-param names are starting with lower-case letter (upper-case init-params are used for FreemarkerSerlvet
+ * settings).</li>
  * 
  * </ul>
  * 
@@ -412,7 +412,6 @@ public class FreemarkerServlet extends HttpServlet {
     private static final String DEPR_INITPARAM_TEMPLATE_DELAY = "TemplateDelay";
     private static final String DEPR_INITPARAM_ENCODING = "DefaultEncoding";
     private static final String DEPR_INITPARAM_OBJECT_WRAPPER = "ObjectWrapper";
-    private static final String DEPR_INITPARAM_WRAPPER_RESTRICTED = "restricted";
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER = "TemplateExceptionHandler";
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW = "rethrow";
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG = "debug";
@@ -508,7 +507,6 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     // Init-param values:
-    private String templatePath;
     private boolean noCache;
     private Integer bufferSize;
     private boolean exceptionOnMissingTemplate;
@@ -522,13 +520,10 @@ public class FreemarkerServlet extends HttpServlet {
     
     @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private Configuration config;
-    @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
-    private ObjectWrapperAndUnwrapper wrapper;
     private ContentType contentType;
     private OverrideResponseContentType overrideResponseContentType = initParamValueToEnum(
             getDefaultOverrideResponseContentType(), OverrideResponseContentType.values());
     private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
-    @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private Charset forcedResponseCharacterEncoding;
     private OverrideResponseLocale overrideResponseLocale = OverrideResponseLocale.ALWAYS;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
@@ -544,7 +539,7 @@ public class FreemarkerServlet extends HttpServlet {
 
     /**
      * Don't override this method to adjust FreeMarker settings! Override the protected methods for that, such as
-     * {@link #createConfiguration()}, {@link #createTemplateLoader(String)}, {@link #createDefaultObjectWrapper()},
+     * {@link #createConfigurationBuilder()}, {@link #createTemplateLoader(String)},
      * etc. Also note that lot of things can be changed with init-params instead of overriding methods, so if you
      * override settings, usually you should only override their defaults.
      */
@@ -560,42 +555,34 @@ public class FreemarkerServlet extends HttpServlet {
         }
     }
     
-    private void initialize() throws InitParamValueException, MalformedWebXmlException, ConflictingInitParamsException {
-        config = createConfiguration();
+    private void initialize() throws InitParamValueException, MalformedWebXmlException, ConflictingInitParamsException,
+            ConfigurationException {
+        Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder();
         
         // Only override what's coming from the config if it was explicitly specified: 
-        final String iciInitParamValue = getInitParameter(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY);
+        final String iciInitParamValue = getInitParameter(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY);
         if (iciInitParamValue != null) {
             try {
-                config.setSetting(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY, iciInitParamValue);
+                cfgB.setSetting(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY, iciInitParamValue);
             } catch (Exception e) {
-                throw new InitParamValueException(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY, iciInitParamValue, e);
+                throw new InitParamValueException(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY, iciInitParamValue, e);
             }
         }
-        
-        // Set FreemarkerServlet-specific defaults, except where createConfiguration() has already set them:
-        if (!config.isTemplateExceptionHandlerExplicitlySet()) {
-            config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
-        }
-        if (!config.isLogTemplateExceptionsExplicitlySet()) {
-            config.setLogTemplateExceptions(false);
-        }
-        
+
         contentType = DEFAULT_CONTENT_TYPE;
         
-        // Process object_wrapper init-param out of order: 
-        wrapper = createObjectWrapper();
-        LOG.debug("Using object wrapper {}", wrapper);
-        config.setObjectWrapper(wrapper);
-        
-        // Process TemplatePath init-param out of order:
-        templatePath = getInitParameter(INIT_PARAM_TEMPLATE_PATH);
-        if (templatePath == null && !config.isTemplateLoaderExplicitlySet()) {
-            templatePath = InitParamParser.TEMPLATE_PATH_PREFIX_CLASS;
+        // Process object_wrapper init-param out of order:
+        String objectWrapperInitParamValue = getInitParameter(
+                MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, DEPR_INITPARAM_OBJECT_WRAPPER);
+        if (objectWrapperInitParamValue != null) {
+            setObjectWrapperFromInitParam(cfgB, objectWrapperInitParamValue);
         }
+
+        // Process TemplatePath init-param out of order:
+        String templatePath = getInitParameter(INIT_PARAM_TEMPLATE_PATH);
         if (templatePath != null) {
             try {
-                config.setTemplateLoader(createTemplateLoader(templatePath));
+                cfgB.setTemplateLoader(createTemplateLoader(templatePath));
             } catch (Exception e) {
                 throw new InitParamValueException(INIT_PARAM_TEMPLATE_PATH, templatePath, e);
             }
@@ -605,9 +592,8 @@ public class FreemarkerServlet extends HttpServlet {
         classpathTlds = createDefaultClassPathTlds();
 
         // Process all other init-params:
-        Enumeration initpnames = getServletConfig().getInitParameterNames();
-        while (initpnames.hasMoreElements()) {
-            final String name = (String) initpnames.nextElement();
+        for (Enumeration initPNames = getServletConfig().getInitParameterNames(); initPNames.hasMoreElements();) {
+            final String name = (String) initPNames.nextElement();
             final String value = getInitParameter(name);
             if (name == null) {
                 throw new MalformedWebXmlException(
@@ -624,21 +610,21 @@ public class FreemarkerServlet extends HttpServlet {
                 if (name.equals(DEPR_INITPARAM_OBJECT_WRAPPER)
                         || name.equals(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY)
                         || name.equals(INIT_PARAM_TEMPLATE_PATH)
-                        || name.equals(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY)) {
+                        || name.equals(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY)) {
                     // ignore: we have already processed these
                 } else if (name.equals(DEPR_INITPARAM_ENCODING)) { // BC
-                    if (getInitParameter(Configuration.SOURCE_ENCODING_KEY) != null) {
+                    if (getInitParameter(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY) != null) {
                         throw new ConflictingInitParamsException(
-                                Configuration.SOURCE_ENCODING_KEY, DEPR_INITPARAM_ENCODING);
+                                Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY, DEPR_INITPARAM_ENCODING);
                     }
-                    config.setSourceEncoding(Charset.forName(value));
+                    cfgB.setSourceEncoding(Charset.forName(value));
                 } else if (name.equals(DEPR_INITPARAM_TEMPLATE_DELAY)) { // BC
-                    if (getInitParameter(Configuration.TEMPLATE_UPDATE_DELAY_KEY) != null) {
+                    if (getInitParameter(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY) != null) {
                         throw new ConflictingInitParamsException(
-                                Configuration.TEMPLATE_UPDATE_DELAY_KEY, DEPR_INITPARAM_TEMPLATE_DELAY);
+                                Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, DEPR_INITPARAM_TEMPLATE_DELAY);
                     }
                     try {
-                        config.setTemplateUpdateDelayMilliseconds(Integer.parseInt(value) * 1000L);
+                        cfgB.setTemplateUpdateDelayMilliseconds(Integer.parseInt(value) * 1000L);
                     } catch (NumberFormatException e) {
                         // Intentionally ignored
                     }
@@ -649,13 +635,13 @@ public class FreemarkerServlet extends HttpServlet {
                     }
     
                     if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW.equals(value)) {
-                        config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+                        cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
                     } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG.equals(value)) {
-                        config.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
+                        cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
                     } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_HTML_DEBUG.equals(value)) {
-                        config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
+                        cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
                     } else if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE.equals(value)) {
-                        config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
+                        cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
                     } else {
                         throw new InitParamValueException(DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER, value,
                                 "Not one of the supported values.");
@@ -695,21 +681,32 @@ public class FreemarkerServlet extends HttpServlet {
                     newClasspathTlds.addAll(InitParamParser.parseCommaSeparatedList(value));
                     classpathTlds = newClasspathTlds;
                 } else {
-                    config.setSetting(name, value);
+                    cfgB.setSetting(name, value);
                 }
             } catch (ConflictingInitParamsException e) {
                 throw e;
             } catch (Exception e) {
                 throw new InitParamValueException(name, value, e);
             }
-        } // while initpnames
+        } // for initPNames
         
         if (contentType.containsCharset && responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
             throw new InitParamValueException(INIT_PARAM_CONTENT_TYPE, contentType.httpHeaderValue,
                     new IllegalStateException("You can't specify the charset in the content type, because the \"" +
                             INIT_PARAM_RESPONSE_CHARACTER_ENCODING + "\" init-param isn't set to "
                             + "\"" + INIT_PARAM_VALUE_LEGACY + "\"."));
-        }        
+        }
+
+        beforeConfigurationBuilt(cfgB);
+        config = cfgB.build();
+        afterConfigurationBuilt(config);
+
+        if (!(config.getObjectWrapper() instanceof ObjectWrapperAndUnwrapper)) {
+            throw new RuntimeException(FreemarkerServlet.class.getSimpleName() + " requires an ObjectWrapper that " +
+                    "implements " + ObjectWrapperAndUnwrapper.class.getName() + ", but this class doesn't do that: "
+                    + config.getObjectWrapper().getClass().getName());
+        }
+        LOG.debug("Using object wrapper {}", config.getObjectWrapper());
     }
     
     private List/*<MetaInfTldSource>*/ parseAsMetaInfTldLocations(String value) throws ParseException {
@@ -751,18 +748,18 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Create the template loader. The default implementation will invoke a {@link ClassTemplateLoader} if the template
+     * Create the template loader. The default implementation will create a {@link ClassTemplateLoader} if the template
      * path starts with {@code "class://"}, a {@link FileTemplateLoader} if the template path starts with
      * {@code "file://"}, and a {@link WebAppTemplateLoader} otherwise. Also, if
-     * {@link Configuration#Configuration(org.apache.freemarker.core.Version) incompatible_improvements} is 2.3.22 or higher,
-     * it will invoke a {@link MultiTemplateLoader} if the template path starts with {@code "["}.
+     * {@link Configuration#getIncompatibleImprovements()}  incompatibleImprovements} is 2.3.22 or higher,
+     * it will create a {@link MultiTemplateLoader} if the template path starts with {@code "["}.
      * 
      * @param templatePath
-     *            the template path to invoke a loader for
+     *            the template path to create a loader for
      * @return a newly created template loader
      */
     protected TemplateLoader createTemplateLoader(String templatePath) throws IOException {
-        return InitParamParser.createTemplateLoader(templatePath, getConfiguration(), getClass(), getServletContext());
+        return InitParamParser.createTemplateLoader(templatePath, getClass(), getServletContext());
     }
     
     @Override
@@ -868,9 +865,9 @@ public class FreemarkerServlet extends HttpServlet {
 
         ServletContext servletContext = getServletContext();
         try {
-            logWarnOnObjectWrapperMismatch();
-            
-            TemplateModel model = createModel(wrapper, servletContext, request, response);
+            TemplateModel model = createModel(
+                    (ObjectWrapperAndUnwrapper) config.getObjectWrapper(), // This is checked in initialize()
+                    servletContext, request, response);
 
             // Give subclasses a chance to hook into preprocessing
             if (preTemplateProcess(request, response, template, model)) {
@@ -978,26 +975,6 @@ public class FreemarkerServlet extends HttpServlet {
         throw e;
     }
     
-    @SuppressFBWarnings(value={ "MSF_MUTABLE_SERVLET_FIELD", "DC_DOUBLECHECK" }, justification="Performance trick")
-    private void logWarnOnObjectWrapperMismatch() {
-        // Deliberately uses double check locking.
-        if (wrapper != config.getObjectWrapper() && !objectWrapperMismatchWarnLogged && LOG.isWarnEnabled()) {
-            final boolean logWarn;
-            synchronized (this) {
-                logWarn = !objectWrapperMismatchWarnLogged;
-                if (logWarn) {
-                    objectWrapperMismatchWarnLogged = true;
-                }
-            }
-            if (logWarn) {
-                LOG.warn(
-                        getClass().getName()
-                        + ".wrapper != config.getObjectWrapper(); possibly the result of incorrect extension of "
-                        + FreemarkerServlet.class.getName() + ".");
-            }
-        }
-    }
-    
     /**
      * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call (as far as the
      * {@value #INIT_PARAM_OVERRIDE_RESPONSE_LOCALE} Servlet init-param allows that). The base implementation in
@@ -1091,7 +1068,7 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Called to invoke the {@link TaglibFactory} once per servlet context.
+     * Called to create the {@link TaglibFactory} once per servlet context.
      * The default implementation configures it based on the servlet-init parameters and various other environmental
      * settings, so if you override this method, you should call super, then adjust the result.
      * 
@@ -1258,128 +1235,60 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Creates the FreeMarker {@link Configuration} singleton and (when overidden) maybe sets its defaults. Servlet
-     * init-params will be applied later, and thus can overwrite the settings specified here.
-     * 
+     * Creates a new FreeMarker {@link Configuration} builder; by providing a custom builder, the configuration
+     * setting defaults can be specific to the {@link FreemarkerServlet} subclass.
      * <p>
-     * By overriding this method you can set your preferred {@link Configuration} setting defaults, as only the settings
-     * for which an init-param was specified will be overwritten later. (Note that {@link FreemarkerServlet} also has
-     * its own defaults for a few settings, but since 2.3.22, the servlet detects if those settings were already set
-     * here and then it won't overwrite them.)
-     * 
+     * The default implementation creates a new {@link FreemarkerServletConfigurationBuilder} instance (note that it's
+     * not the standard {@link Configuration} builder, as some setting defaults differ) with
+     * {@link Configuration#getIncompatibleImprovements() incompatibleImprovements}
+     * {@link Configuration#VERSION_3_0_0}.
      * <p>
-     * The default implementation simply creates a new instance with {@link Configuration#Configuration()} and returns
-     * it.
+     * By overriding this method you can use your own {@link FreemarkerServletConfigurationBuilder} subclass
+     * (or actually any {@link ExtendableBuilder} subclass) and hence specify what the defaults are.
      */
-    protected Configuration createConfiguration() {
-        // We can only set incompatible_improvements later, so ignore the deprecation warning here.
-        return new Configuration();
+    protected Configuration.ExtendableBuilder<?> createConfigurationBuilder() {
+        return new FreemarkerServletConfigurationBuilder(this, Configuration.VERSION_3_0_0);
     }
     
     /**
-     * Sets the defaults of the configuration that are specific to the {@link FreemarkerServlet} subclass.
-     * This is called after the common (wired in) {@link FreemarkerServlet} setting defaults was set, also the 
-     */
-    protected void setConfigurationDefaults() {
-        // do nothing
-    }
-    
-    /**
-     * Called from {@link #init()} to invoke the {@link ObjectWrapper}; to customzie this aspect, in most cases you
-     * should override {@link #createDefaultObjectWrapper()} instead. Overriding this method is necessary when you want
-     * to customize how the {@link ObjectWrapper} is created <em>from the init-param values</em>, or you want to do some
-     * post-processing (like checking) on the created {@link ObjectWrapper}. To customize init-param interpretation,
-     * call {@link #getInitParameter(String)} with {@link MutableProcessingConfiguration#OBJECT_WRAPPER_KEY} as argument, and see if it
-     * returns a value that you want to interpret yourself. If was {@code null} or you don't want to interpret the
-     * value, fall back to the super method.
-     * 
+     * Hook for {@link FreemarkerServlet} subclasses to modify the configuration builder just before the
+     * {@link Configuration} is created. Note that to change the defaults of some setting, you meant to use
+     * {@link #createConfigurationBuilder()} instead.
      * <p>
-     * The default implementation interprets the {@code object_wrapper} servlet init-param with
-     * calling {@link MutableProcessingConfiguration#setSetting(String, String)} (see valid values there), or if there's no such servlet
-     * init-param, then it calls {@link #createDefaultObjectWrapper()}.
-     * 
-     * @return The {@link ObjectWrapper} that will be used for adapting request, session, and servlet context attributes
-     *         to {@link TemplateModel}-s, and also as the object wrapper setting of {@link Configuration}.
+     * The implementation in {@link FreemarkerServlet} does nothing here.
      */
-    protected ObjectWrapperAndUnwrapper createObjectWrapper() {
-        String wrapper = getServletConfig().getInitParameter(DEPR_INITPARAM_OBJECT_WRAPPER);
-        if (wrapper != null) { // BC
-            if (getInitParameter(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY) != null) {
-                throw new RuntimeException("Conflicting init-params: "
-                        + MutableProcessingConfiguration.OBJECT_WRAPPER_KEY + " and "
-                        + DEPR_INITPARAM_OBJECT_WRAPPER);
-            }
-            if (DEPR_INITPARAM_WRAPPER_RESTRICTED.equals(wrapper)) {
-                return new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
-            }
-            return createDefaultObjectWrapper();
-        } else {
-            wrapper = getInitParameter(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY);
-            if (wrapper == null) {
-                if (!config.isObjectWrapperExplicitlySet()) {
-                    return createDefaultObjectWrapper();
-                } else {
-                    return asObjectWrapperAndUnwrapper(config.getObjectWrapper());
-                }
-            } else {
-                try {
-                    config.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, wrapper);
-                } catch (ConfigurationException e) {
-                    throw new RuntimeException("Failed to set " + MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, e);
-                }
-                return asObjectWrapperAndUnwrapper(config.getObjectWrapper());
-            }
-        }
-    }
-
-    private ObjectWrapperAndUnwrapper asObjectWrapperAndUnwrapper(ObjectWrapper objectWrapper) {
-        if (!(objectWrapper instanceof ObjectWrapperAndUnwrapper)) {
-            throw new RuntimeException(FreemarkerServlet.class.getSimpleName() + " requires an ObjectWrapper that " +
-                    "implements " + ObjectWrapperAndUnwrapper.class.getName() + ", but this class doesn't do that: "
-                    + objectWrapper.getClass().getName());
-        }
-        return (ObjectWrapperAndUnwrapper) objectWrapper;
+    protected void beforeConfigurationBuilt(Configuration.ExtendableBuilder<?> cfgB) {
+        // do nothing
     }
 
     /**
-     * Override this to specify what the default {@link ObjectWrapper} will be when the
-     * {@code object_wrapper} Servlet init-param wasn't specified. Note that this is called by
-     * {@link #createConfiguration()}, and so if that was also overidden but improperly then this method might won't be
-     * ever called. Also note that if you set the {@code object_wrapper} in {@link #createConfiguration()}, then this
-     * won't be called, since then that has already specified the default.
-     * 
+     * Hook for {@link FreemarkerServlet} subclasses to examine {@link Configuration} just after it was created.
      * <p>
-     * The default implementation calls {@link Configuration#getDefaultObjectWrapper(Version)}. You
-     * should also pass in the version paramter when creating an {@link ObjectWrapper} that supports that. You can get
-     * the version by calling {@link #getConfiguration()} and then {@link Configuration#getIncompatibleImprovements()}.
-     * 
-     * @since 2.3.22
-     */
-    protected ObjectWrapperAndUnwrapper createDefaultObjectWrapper() {
-        return Configuration.getDefaultObjectWrapper(config.getIncompatibleImprovements());
-    }
-    
-    /**
-     * Should be final; don't override it. Override {@link #createObjectWrapper()} instead.
+     * The implementation in {@link FreemarkerServlet} does nothing here.
      */
-    // [2.4] Make it final
-    protected ObjectWrapper getObjectWrapper() {
-        return wrapper;
+    protected void afterConfigurationBuilt(Configuration cfg) {
+        // do nothing
     }
     
     /**
-     * The value of the {@code TemplatePath} init-param. {@code null} if the {@code template_loader} setting was set in
-     * a custom {@link #createConfiguration()}.
-     * 
-     * @deprecated Not called by FreeMarker code, and there's no point to override this (unless to cause confusion).
+     * Called from {@link #init()} to set the {@link ObjectWrapper} in the {@link ExtendableBuilder}
+     * from the init-param value.
+     * To customize init-param interpretation, see if the init-param value argument is something that you want to
+     * interpret yourself, otherwise fall back to the super method. This method won't be called if there's not
+     * init-param that specifies the object wrapper.
+     * <p>
+     * The default implementation interprets the {@code object_wrapper} servlet init-param with
+     * calling {@link MutableProcessingConfiguration#setSetting(String, String)}.
+     *
+     * @param initParamValue Not {@code null}
      */
-    @Deprecated
-    protected final String getTemplatePath() {
-        return templatePath;
+    protected void setObjectWrapperFromInitParam(Configuration.ExtendableBuilder<?> cb, String initParamValue)
+            throws ConfigurationException {
+        cb.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, initParamValue);
     }
-    
+
     protected HttpRequestParametersHashModel createRequestParametersHashModel(HttpServletRequest request) {
-        return new HttpRequestParametersHashModel(request, getObjectWrapper());
+        return new HttpRequestParametersHashModel(request, config.getObjectWrapper());
     }
 
     /**
@@ -1628,6 +1537,15 @@ public class FreemarkerServlet extends HttpServlet {
         throw new IllegalArgumentException(sb.toString());
     }
 
+    private String getInitParameter(String name1, String name2) {
+        String r1 = getServletConfig().getInitParameter(name1);
+        String r2 = getInitParameter(name2);
+        if (r1 != null && r2 != null) {
+            throw new RuntimeException("Conflicting init-params: " + name1 + " and " + name2);
+        }
+        return r2 != null ? r2 : r1;
+    }
+
     /**
      * Superclass of all (future) init-param value enums.
      * 
@@ -1689,4 +1607,5 @@ public class FreemarkerServlet extends HttpServlet {
         }
     }
 
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/servlet/FreemarkerServletConfigurationBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/FreemarkerServletConfigurationBuilder.java b/src/main/java/org/apache/freemarker/servlet/FreemarkerServletConfigurationBuilder.java
new file mode 100644
index 0000000..c3eee4d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/servlet/FreemarkerServletConfigurationBuilder.java
@@ -0,0 +1,79 @@
+/*
+ * 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.servlet;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
+import org.apache.freemarker.core.TemplateExceptionHandler;
+import org.apache.freemarker.core.Version;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+
+/**
+ * Changes some defaults compared to {@link ExtendableBuilder}, to values that makes more sense for the
+ * {@link FreemarkerServlet}.
+ */
+// TODO [FM3] JavaDoc the defaults when they are stable
+public class FreemarkerServletConfigurationBuilder<SelfT extends FreemarkerServletConfigurationBuilder<SelfT>>
+        extends Configuration.ExtendableBuilder<SelfT> {
+
+    private final FreemarkerServlet freemarkerServlet;
+    private TemplateLoader cachedDefaultTemplateLoader;
+
+    public FreemarkerServletConfigurationBuilder(FreemarkerServlet freemarkerServlet, Version version) {
+        super(version);
+        this.freemarkerServlet = freemarkerServlet;
+    }
+
+    @Override
+    protected TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
+        // TODO [FM3] Not a good default. Should depend on if we are in development mode or production mode.
+        return TemplateExceptionHandler.HTML_DEBUG_HANDLER;
+    }
+
+    // TODO [FM3] Remove when this will be the ExtendableBuilder default too.
+    @Override
+    protected boolean getDefaultLogTemplateExceptions() {
+        return false;
+    }
+
+    @Override
+    public void setTemplateLoader(TemplateLoader templateLoader) {
+        super.setTemplateLoader(templateLoader);
+        if (cachedDefaultTemplateLoader != templateLoader) {
+            // Just to make it GC-able
+            cachedDefaultTemplateLoader = null;
+        }
+    }
+
+    @Override
+    protected TemplateLoader getDefaultTemplateLoader() {
+        try {
+            if (cachedDefaultTemplateLoader == null) {
+                cachedDefaultTemplateLoader = freemarkerServlet.createTemplateLoader(InitParamParser.TEMPLATE_PATH_PREFIX_CLASS);
+            }
+            return cachedDefaultTemplateLoader;
+        } catch (IOException e) {
+            // It's almost impossible that this will happen
+            throw new RuntimeException("Failed to create default template loader; see cause exception", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/InitParamParser.java b/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
index 0f20b7b..de8e780 100644
--- a/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
+++ b/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
@@ -27,7 +27,6 @@ import java.util.regex.Pattern;
 
 import javax.servlet.ServletContext;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core._ObjectBuilderSettingEvaluator;
 import org.apache.freemarker.core._SettingEvaluationEnvironment;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
@@ -54,7 +53,7 @@ final class InitParamParser {
     }
 
     static TemplateLoader createTemplateLoader(
-            String templatePath, Configuration cfg, Class classLoaderClass, ServletContext srvCtx)
+            String templatePath, Class classLoaderClass, ServletContext srvCtx)
             throws IOException {
         final int settingAssignmentsStart = findTemplatePathSettingAssignmentsStart(templatePath);
         String pureTemplatePath = (settingAssignmentsStart == -1 ? templatePath : templatePath.substring(0, settingAssignmentsStart))
@@ -91,7 +90,7 @@ final class InitParamParser {
             TemplateLoader[] templateLoaders = new TemplateLoader[listItems.size()];
             for (int i = 0; i < listItems.size(); i++) {
                 String pathItem = (String) listItems.get(i);
-                templateLoaders[i] = createTemplateLoader(pathItem, cfg, classLoaderClass, srvCtx);
+                templateLoaders[i] = createTemplateLoader(pathItem, classLoaderClass, srvCtx);
             }
             templateLoader = new MultiTemplateLoader(templateLoaders);
         } else if (pureTemplatePath.startsWith("{")) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java b/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
index be215d5..9be5834 100644
--- a/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
+++ b/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
@@ -155,7 +155,7 @@ public class WebAppTemplateLoader implements TemplateLoader {
      * workaround for the case when the servlet container doesn't show template modifications after the template was
      * already loaded earlier. But it's certainly better to counter this problem by disabling the URL connection cache
      * with {@link #setURLConnectionUsesCaches(Boolean)}, which is also the default behavior with
-     * {@link Configuration#setIncompatibleImprovements(org.apache.freemarker.core.Version) incompatible_improvements} 2.3.21
+     * {@link Configuration#getIncompatibleImprovements() incompatibleImprovements} 2.3.21
      * and later.
      * 
      * @since 2.3.23

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 9399918..e5a898e 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -212,4 +212,15 @@ the FreeMarer 3 changelog here:
   lookup and the source name, its meaning wasn't clean (but it meant the lookup name). TemplateException and ParseException 
   now also have the same properites: getTemplateSourceName(), getTemplateLookupName(), and even getSourceOrLookupName().
   Location information in error messages show getTemplateSourceOrLookupName().
-- Configuration.setSharedVaribles (not the typo in it) was renamed to setSharedVariables
\ No newline at end of file
+- Configuration.setSharedVaribles (not the typo in it) was renamed to setSharedVariables
+- Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create
+  the Configuration with the builder's build() method. Further notes:
+  - Most of the mutator methods (like setters) were moved from Configuration to Configuration.Builder. However,
+    setClassForTemplateLoader, setDirectoryForTemplateLoading and the like were removed, instead there's just
+    setTemplateLoader. So for example. instead of  setClassForTemplateLoader(Foo.class, "templates") now you have
+    to write setTemplateLoader(new ClassTemplateLoader(Foo.class, "templates")). While it's a bit longer, it shows
+    more clearly what's happening, and always supports all TemplateLoader constructor overloads.
+  - It's now possible to change the Configuration setting defaults by using a custom Configuration.ExtendableBuilder
+    subclass instead of Configuration.Builder (which is also an ExtendableBuilder subclass). FreemarkerServlet has
+    switched to this approach, using its own builder subclass to provide defaults that makes the sense in that particular
+    application. Its API-s which were used for customizing FreemarkerServlet has bean changed accordingly.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/ASTPrinter.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ASTPrinter.java b/src/test/java/org/apache/freemarker/core/ASTPrinter.java
index ddd8e9b..3518b29 100644
--- a/src/test/java/org/apache/freemarker/core/ASTPrinter.java
+++ b/src/test/java/org/apache/freemarker/core/ASTPrinter.java
@@ -43,6 +43,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.util.FTLUtil;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 
 /**
  * Static methods and command-line tool for printing the AST of a template. 
@@ -68,7 +69,7 @@ public class ASTPrinter {
     }
     
     private ASTPrinter() {
-        cfg = new Configuration(Configuration.VERSION_3_0_0);
+        cfg = new TestConfigurationBuilder(Configuration.VERSION_3_0_0).build();
     }
     
     private void mainSingleTemplate(String[] args) throws IOException, FileNotFoundException {
@@ -230,8 +231,7 @@ public class ASTPrinter {
     }
     
     public static String getASTAsString(String templateName, String ftl, Options opts) throws IOException {
-        Configuration cfg = new Configuration();
-        Template t = new Template(templateName, ftl, cfg);
+        Template t = new Template(templateName, ftl, new TestConfigurationBuilder().build());
         return getASTAsString(t, opts);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java b/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java
index ea07e1f..57e40fa 100644
--- a/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java
+++ b/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.*;
 
 import java.io.IOException;
 
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class ActualNamingConvetionTest {
@@ -31,35 +32,35 @@ public class ActualNamingConvetionTest {
     public void testUndetectable() throws IOException {
         final String ftl = "<#if true>${x?size}</#if>";
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.AUTO_DETECT_NAMING_CONVENTION), Configuration.AUTO_DETECT_NAMING_CONVENTION);
+                ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION), ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION);
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.LEGACY_NAMING_CONVENTION), Configuration.LEGACY_NAMING_CONVENTION);
+                ParsingConfiguration.LEGACY_NAMING_CONVENTION), ParsingConfiguration.LEGACY_NAMING_CONVENTION);
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.CAMEL_CASE_NAMING_CONVENTION), Configuration.CAMEL_CASE_NAMING_CONVENTION);
+                ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION), ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
     }
 
     @Test
     public void testLegacyDetected() throws IOException {
         final String ftl = "${x?upper_case}";
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.AUTO_DETECT_NAMING_CONVENTION), Configuration.LEGACY_NAMING_CONVENTION);
+                ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION), ParsingConfiguration.LEGACY_NAMING_CONVENTION);
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.LEGACY_NAMING_CONVENTION), Configuration.LEGACY_NAMING_CONVENTION);
+                ParsingConfiguration.LEGACY_NAMING_CONVENTION), ParsingConfiguration.LEGACY_NAMING_CONVENTION);
     }
 
     @Test
     public void testCamelCaseDetected() throws IOException {
         final String ftl = "${x?upperCase}";
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.AUTO_DETECT_NAMING_CONVENTION), Configuration.CAMEL_CASE_NAMING_CONVENTION);
+                ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION), ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
         assertEquals(getActualNamingConvention(ftl,
-                Configuration.CAMEL_CASE_NAMING_CONVENTION), Configuration.CAMEL_CASE_NAMING_CONVENTION);
+                ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION), ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
     }
 
     private int getActualNamingConvention(String ftl, int namingConvention) throws IOException {
-        Configuration cfg = new Configuration();
-        cfg.setNamingConvention(namingConvention);
-        return new Template(null, ftl, cfg).getActualNamingConvention();
+        return new Template(null, ftl,
+                new TestConfigurationBuilder().namingConvention(namingConvention).build())
+                .getActualNamingConvention();
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java b/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java
index 58c101f..88f0646 100644
--- a/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java
+++ b/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java
@@ -19,48 +19,50 @@
 
 package org.apache.freemarker.core;
 
+import static org.apache.freemarker.core.ParsingConfiguration.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
 
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class ActualTagSyntaxTest {
 
     @Test
     public void testWithFtlHeader() throws IOException {
-        testWithFtlHeader(Configuration.AUTO_DETECT_TAG_SYNTAX);
-        testWithFtlHeader(Configuration.ANGLE_BRACKET_TAG_SYNTAX);
-        testWithFtlHeader(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        testWithFtlHeader(AUTO_DETECT_TAG_SYNTAX);
+        testWithFtlHeader(ANGLE_BRACKET_TAG_SYNTAX);
+        testWithFtlHeader(SQUARE_BRACKET_TAG_SYNTAX);
     }
     
     private void testWithFtlHeader(int cfgTagSyntax) throws IOException {
-        assertEquals(getActualTagSyntax("[#ftl]foo", cfgTagSyntax), Configuration.SQUARE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("<#ftl>foo", cfgTagSyntax), Configuration.ANGLE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("[#ftl]foo", cfgTagSyntax), SQUARE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("<#ftl>foo", cfgTagSyntax), ANGLE_BRACKET_TAG_SYNTAX);
     }
     
     @Test
     public void testUndecidable() throws IOException {
-        assertEquals(getActualTagSyntax("foo", Configuration.AUTO_DETECT_TAG_SYNTAX), Configuration.ANGLE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("foo", Configuration.ANGLE_BRACKET_TAG_SYNTAX), Configuration.ANGLE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("foo", Configuration.SQUARE_BRACKET_TAG_SYNTAX), Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo", AUTO_DETECT_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo", ANGLE_BRACKET_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo", SQUARE_BRACKET_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX);
     }
 
     @Test
     public void testDecidableWithoutFtlHeader() throws IOException {
-        assertEquals(getActualTagSyntax("foo<#if true></#if>", Configuration.AUTO_DETECT_TAG_SYNTAX), Configuration.ANGLE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("foo<#if true></#if>", Configuration.ANGLE_BRACKET_TAG_SYNTAX), Configuration.ANGLE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("foo<#if true></#if>", Configuration.SQUARE_BRACKET_TAG_SYNTAX), Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo<#if true></#if>", AUTO_DETECT_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo<#if true></#if>", ANGLE_BRACKET_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo<#if true></#if>", SQUARE_BRACKET_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX);
         
-        assertEquals(getActualTagSyntax("foo[#if true][/#if]", Configuration.AUTO_DETECT_TAG_SYNTAX), Configuration.SQUARE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("foo[#if true][/#if]", Configuration.ANGLE_BRACKET_TAG_SYNTAX), Configuration.ANGLE_BRACKET_TAG_SYNTAX);
-        assertEquals(getActualTagSyntax("foo[#if true][/#if]", Configuration.SQUARE_BRACKET_TAG_SYNTAX), Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo[#if true][/#if]", AUTO_DETECT_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo[#if true][/#if]", ANGLE_BRACKET_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX);
+        assertEquals(getActualTagSyntax("foo[#if true][/#if]", SQUARE_BRACKET_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX);
     }
     
     private int getActualTagSyntax(String ftl, int cfgTagSyntax) throws IOException {
-        Configuration cfg = new Configuration();
-        cfg.setTagSyntax(cfgTagSyntax);
-        return new Template(null, ftl, cfg).getActualTagSyntax();
+        return new Template(
+                null, ftl,
+                new TestConfigurationBuilder().tagSyntax(cfgTagSyntax).build()).getActualTagSyntax();
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
index 05da5bc..61ba02b 100644
--- a/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
+++ b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
@@ -50,13 +50,7 @@ public class BreakPlacementTest extends TemplateTest {
         assertErrorContains("<#if false><#break></#if>", BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#list xs><#break></#list>", BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#list 1..2 as x>${x}<#else><#break></#list>", BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list 1..2 as x>${x}<#macro m><#break></#macro></#list>", BREAK_NESTING_ERROR_MESSAGE_PART);
     }
 
-    @Test
-    public void testInvalidPlacementInsideMacro() throws IOException, TemplateException {
-        final String ftl = "<#list 1..2 as x>${x}<#macro m><#break></#macro></#list>";
-        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_3_0_0);
-        assertErrorContains(ftl, BREAK_NESTING_ERROR_MESSAGE_PART);
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CamelCaseTest.java b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
index 7c07127..95572ad 100644
--- a/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
+++ b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
@@ -31,15 +31,18 @@ import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class CamelCaseTest extends TemplateTest {
 
     @Test
     public void camelCaseSpecialVars() throws IOException, TemplateException {
-        getConfiguration().setOutputEncoding(StandardCharsets.UTF_8);
-        getConfiguration().setURLEscapingCharset(StandardCharsets.ISO_8859_1);
-        getConfiguration().setLocale(Locale.GERMANY);
+        setConfiguration(new TestConfigurationBuilder()
+                .outputEncoding(StandardCharsets.UTF_8)
+                .urlEscapingCharset(StandardCharsets.ISO_8859_1)
+                .locale(Locale.GERMANY)
+                .build());
         assertOutput("${.dataModel?isHash?c}", "true");
         assertOutput("${.data_model?is_hash?c}", "true");
         assertOutput("${.localeObject.toString()}", "de_DE");
@@ -69,10 +72,11 @@ public class CamelCaseTest extends TemplateTest {
         
         assertErrorContains("<#if x><#elseIf y></#if>${.foo}", "dataModel", "\\!data_model");
         assertErrorContains("<#if x><#elseif y></#if>${.foo}", "data_model", "\\!dataModel");
-        
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+        setConfigurationToCamelCaseNamingConvention();
         assertErrorContains("${.foo}", "dataModel", "\\!data_model");
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+
+        setConfigurationToLegacyCaseNamingConvention();
         assertErrorContains("${.foo}", "data_model", "\\!dataModel");
     }
     
@@ -87,8 +91,6 @@ public class CamelCaseTest extends TemplateTest {
     
     @Test
     public void camelCaseFtlHeaderParameters() throws IOException, TemplateException {
-        getConfiguration().setOutputEncoding(StandardCharsets.UTF_8);
-        
         assertOutput(
                 "<#ftl "
                 + "stripWhitespace=false "
@@ -116,16 +118,25 @@ public class CamelCaseTest extends TemplateTest {
         assertErrorContains("<#ftl strip_whitespace=true stripText=true>", "naming convention");
         assertErrorContains("<#ftl stripWhitespace=true>${.foo_bar}", "naming convention");
         assertErrorContains("<#ftl strip_whitespace=true>${.fooBar}", "naming convention");
-        
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+        setConfiguration(new TestConfigurationBuilder()
+                .namingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION)
+                .outputEncoding(StandardCharsets.UTF_8)
+                .build());
         assertErrorContains("<#ftl strip_whitespace=true>", "naming convention");
         assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", StandardCharsets.UTF_8.name());
         
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        setConfiguration(new TestConfigurationBuilder()
+                .namingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION)
+                .outputEncoding(StandardCharsets.UTF_8)
+                .build());
         assertErrorContains("<#ftl stripWhitespace=true>", "naming convention");
         assertOutput("<#ftl strip_whitespace=true>${.output_encoding}", StandardCharsets.UTF_8.name());
         
-        getConfiguration().setNamingConvention(Configuration.AUTO_DETECT_NAMING_CONVENTION);
+        setConfiguration(new TestConfigurationBuilder()
+                .namingConvention(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION)
+                .outputEncoding(StandardCharsets.UTF_8)
+                .build());
         assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", StandardCharsets.UTF_8.name());
         assertOutput("<#ftl encoding='iso-8859-1' stripWhitespace=true>${.outputEncoding}", StandardCharsets.UTF_8.name());
         assertOutput("<#ftl stripWhitespace=true encoding='iso-8859-1'>${.outputEncoding}", StandardCharsets.UTF_8.name());
@@ -143,9 +154,10 @@ public class CamelCaseTest extends TemplateTest {
         assertErrorContains("<#if x><#elseIf y></#if><#setting foo=1>", "booleanFormat", "\\!boolean_format");
         assertErrorContains("<#if x><#elseif y></#if><#setting foo=1>", "boolean_format", "\\!booleanFormat");
 
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        setConfigurationToCamelCaseNamingConvention();
         assertErrorContains("<#setting foo=1>", "booleanFormat", "\\!boolean_format");
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+
+        setConfigurationToLegacyCaseNamingConvention();
         assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat");
     }
     
@@ -175,8 +187,8 @@ public class CamelCaseTest extends TemplateTest {
 
     @Test
     public void stringLiteralInterpolation() throws IOException, TemplateException {
-        assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention());
-        getConfiguration().setSharedVariable("x", "x");
+        assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention());
+        addToDataModel("x", "x");
         
         assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X");
         assertOutput("${x?upperCase} ${'-${x?upperCase}-'}", "X -X-");
@@ -191,13 +203,13 @@ public class CamelCaseTest extends TemplateTest {
                 "naming convention", "camel", "upper_case");
         assertErrorContains("${x?upperCase} ${'-${x?upper_case}-'}",
                 "naming convention", "camel", "upper_case");
-        
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+        setConfigurationToCamelCaseNamingConvention();
         assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X");
         assertErrorContains("${'-${x?upper_case}-'}",
                 "naming convention", "camel", "upper_case", "\\!detection");
-        
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+
+        setConfigurationToLegacyCaseNamingConvention();
         assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X");
         assertErrorContains("${'-${x?upperCase}-'}",
                 "naming convention", "legacy", "upperCase", "\\!detection");
@@ -205,7 +217,7 @@ public class CamelCaseTest extends TemplateTest {
     
     @Test
     public void evalAndInterpret() throws IOException, TemplateException {
-        assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention());
+        assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention());
         // The naming convention detected doesn't affect the enclosing template's naming convention.
         // - ?eval:
         assertOutput("${\"'x'?upperCase\"?eval}${'x'?upper_case}", "XX");
@@ -221,7 +233,7 @@ public class CamelCaseTest extends TemplateTest {
                 "naming convention", "camel", "upperCase", "is_string", "line 2", "line 3");
         
         // Will be inherited by ?eval-ed/?interpreted fragments:
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        setConfigurationToCamelCaseNamingConvention();
         // - ?eval:
         assertErrorContains("${\"'x'?upper_case\"?eval}", "naming convention", "camel", "upper_case");
         assertOutput("${\"'x'?upperCase\"?eval}", "X");
@@ -230,7 +242,7 @@ public class CamelCaseTest extends TemplateTest {
         assertOutput("<@r\"${'x'?upperCase}\"?interpret />", "X");
         
         // Again, will be inherited by ?eval-ed/?interpreted fragments:
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        setConfigurationToLegacyCaseNamingConvention();
         // - ?eval:
         assertErrorContains("${\"'x'?upperCase\"?eval}", "naming convention", "legacy", "upperCase");
         assertOutput("${\"'x'?upper_case\"?eval}", "X");
@@ -238,7 +250,13 @@ public class CamelCaseTest extends TemplateTest {
         assertErrorContains("<@r\"${'x'?upperCase}\"?interpret />", "naming convention", "legacy", "upperCase");
         assertOutput("<@r\"${'x'?upper_case}\"?interpret />", "X");
     }
-    
+
+    private void setConfigurationToLegacyCaseNamingConvention() {
+        setConfiguration(new TestConfigurationBuilder()
+                .namingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION)
+                .build());
+    }
+
     @Test
     public void camelCaseBuiltInErrorMessage() throws IOException, TemplateException {
         assertErrorContains("${'x'?upperCasw}", "upperCase", "\\!upper_case");
@@ -248,13 +266,19 @@ public class CamelCaseTest extends TemplateTest {
         
         assertErrorContains("<#if x><#elseIf y></#if> ${'x'?foo}", "upperCase", "\\!upper_case");
         assertErrorContains("<#if x><#elseif y></#if>${'x'?foo}", "upper_case", "\\!upperCase");
-        
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+
+        setConfigurationToCamelCaseNamingConvention();
         assertErrorContains("${'x'?foo}", "upperCase", "\\!upper_case");
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        setConfigurationToLegacyCaseNamingConvention();
         assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase");
     }
-    
+
+    private void setConfigurationToCamelCaseNamingConvention() {
+        setConfiguration(new TestConfigurationBuilder()
+                .namingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION)
+                .build());
+    }
+
     @Test
     public void builtInsHasBothNamingStyle() throws IOException, TemplateException {
         assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), new NamePairAssertion() {
@@ -273,7 +297,7 @@ public class CamelCaseTest extends TemplateTest {
     private void assertContainsBothNamingStyles(Set<String> names, NamePairAssertion namePairAssertion) {
         Set<String> underscoredNamesWithCamelCasePair = new HashSet<>();
         for (String name : names) {
-            if (_StringUtil.getIdentifierNamingConvention(name) == Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+            if (_StringUtil.getIdentifierNamingConvention(name) == ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) {
                 String underscoredName = correctIsoBIExceptions(_StringUtil.camelCaseToUnderscored(name)); 
                 assertTrue(
                         "Missing underscored variation \"" + underscoredName + "\" for \"" + name + "\".",
@@ -284,7 +308,7 @@ public class CamelCaseTest extends TemplateTest {
             }
         }
         for (String name : names) {
-            if (_StringUtil.getIdentifierNamingConvention(name) == Configuration.LEGACY_NAMING_CONVENTION) {
+            if (_StringUtil.getIdentifierNamingConvention(name) == ParsingConfiguration.LEGACY_NAMING_CONVENTION) {
                 assertTrue("Missing camel case variation for \"" + name + "\".",
                         underscoredNamesWithCamelCasePair.contains(name));
             }
@@ -298,7 +322,9 @@ public class CamelCaseTest extends TemplateTest {
     @Test
     public void camelCaseDirectives() throws IOException, TemplateException {
         camelCaseDirectives(false);
-        getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX)
+                .build());
         camelCaseDirectives(true);
     }
 
@@ -338,12 +364,13 @@ public class CamelCaseTest extends TemplateTest {
     }
     
     private void explicitNamingConvention(boolean squared) throws IOException, TemplateException {
-        if (squared) {
-            getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
-        }
-        
-        getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
-        
+        int tagSyntax = squared ? ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX
+                : ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX;
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(tagSyntax)
+                .namingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION)
+                .build());
+
         assertErrorContains(
                 squared("<#if true>t<#elseif false>f</#if>", squared),
                 "naming convention", "camel", "#elseif");
@@ -366,9 +393,12 @@ public class CamelCaseTest extends TemplateTest {
                 "1");
 
         // ---
-        
-        getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
-        
+
+        setConfiguration(new TestConfigurationBuilder()
+                .tagSyntax(tagSyntax)
+                .namingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION)
+                .build());
+
         assertErrorContains(
                 squared("<#if true>t<#elseIf false>f</#if>", squared),
                 "naming convention", "legacy", "#elseIf");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
index 3218cd6..c78c90e 100644
--- a/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
+++ b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
@@ -24,6 +24,7 @@ import java.io.StringWriter;
 
 import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.apache.freemarker.test.util.FileTestCase;
 
 public class CanonicalFormTest extends FileTestCase {
@@ -54,13 +55,13 @@ public class CanonicalFormTest extends FileTestCase {
     
     private void assertCanonicalFormOf(String ftlFileName)
             throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setTemplateLoader(
-                new CopyrightCommentRemoverTemplateLoader(
-                        new ClassTemplateLoader(CanonicalFormTest.class, "")));
+        Configuration cfg = new TestConfigurationBuilder()
+                .templateLoader(
+                        new CopyrightCommentRemoverTemplateLoader(
+                                new ClassTemplateLoader(CanonicalFormTest.class, "")))
+                .build();
         StringWriter sw = new StringWriter();
         cfg.getTemplate(ftlFileName).dump(sw);
-
         assertExpectedFileEqualsString(ftlFileName + ".out", sw.toString());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
index 83f4ba5..c8a3335 100644
--- a/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
+++ b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
@@ -29,6 +29,7 @@ import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
 import org.apache.freemarker.core.userpkg.HTMLISOTemplateDateFormatFactory;
 import org.apache.freemarker.core.userpkg.PrintfGTemplateNumberFormatFactory;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -52,11 +53,11 @@ public class CoercionToTextualTest extends TemplateTest {
 
     @Test
     public void testEscBuiltin() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
-        cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
-        cfg.setBooleanFormat("<y>,<n>");
-        
+        setConfiguration(createDefaultConfigurationBuilder()
+                .outputFormat(HTMLOutputFormat.INSTANCE)
+                .autoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY)
+                .booleanFormat("<y>,<n>")
+                .build());
         assertOutput("${'a<b'?esc}", "a&lt;b");
         assertOutput("${n?string?esc}", "1.50E+03");
         assertOutput("${n?esc}", "1.50*10<sup>3</sup>");
@@ -117,16 +118,23 @@ public class CoercionToTextualTest extends TemplateTest {
         assertOutput("${'&' + b}", "&y");
         assertOutput("${'&' + m}", "&amp;<p>M</p>");
     }
-    
+
+    @Override
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return createDefaultConfigurationBuilder().build();
+    }
+
+    private TestConfigurationBuilder createDefaultConfigurationBuilder() {
+        return new TestConfigurationBuilder()
+                .customNumberFormats(Collections.singletonMap("G", PrintfGTemplateNumberFormatFactory.INSTANCE))
+                .customDateFormats(Collections.singletonMap("HI", HTMLISOTemplateDateFormatFactory.INSTANCE))
+                .numberFormat("@G 3")
+                .dateTimeFormat("@HI")
+                .booleanFormat("y,n");
+    }
+
     @Before
     public void setup() throws TemplateModelException {
-        Configuration cfg = getConfiguration();
-        cfg.setCustomNumberFormats(Collections.singletonMap("G", PrintfGTemplateNumberFormatFactory.INSTANCE));
-        cfg.setCustomDateFormats(Collections.singletonMap("HI", HTMLISOTemplateDateFormatFactory.INSTANCE));
-        cfg.setNumberFormat("@G 3");
-        cfg.setDateTimeFormat("@HI");
-        cfg.setBooleanFormat("y,n");
-        
         addToDataModel("s", "abc");
         addToDataModel("n", 1500);
         addToDataModel("dt", TM);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 bff5dc0..ebcc465 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
@@ -96,7 +96,7 @@ public class ConfigurableTest {
             String fieldName = field.getName();
             if (fieldName.endsWith("_KEY")) {
                 String keyFieldValue = (String) field.get(null);
-                assertNotEquals(Configuration.CAMEL_CASE_NAMING_CONVENTION,
+                assertNotEquals(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION,
                         _StringUtil.getIdentifierNamingConvention(keyFieldValue));
                 assertEquals(fieldName.substring(0, fieldName.length() - 4).toLowerCase(), keyFieldValue);
                 
@@ -109,7 +109,7 @@ public class ConfigurableTest {
                 
                 try {
                     String keyCCFieldValue = (String) confClass.getField(fieldName + "_CAMEL_CASE").get(null);
-                    assertNotEquals(Configuration.LEGACY_NAMING_CONVENTION,
+                    assertNotEquals(ParsingConfiguration.LEGACY_NAMING_CONVENTION,
                             _StringUtil.getIdentifierNamingConvention(keyCCFieldValue));
                     assertEquals(keyFieldValue, _StringUtil.camelCaseToUnderscored(keyCCFieldValue));
                 } catch (NoSuchFieldException e) {


[06/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 eaac897..c3c88e8 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -59,6 +59,7 @@ import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
@@ -95,109 +96,119 @@ public class ConfigurationTest extends TestCase {
         super(name);
     }
 
-    public void testUnsetAndIsExplicitlySet() {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+    public void testUnsetAndIsSet() throws Exception {
+        Configuration.ExtendableBuilder<?> cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
-        assertFalse(cfg.isLogTemplateExceptionsExplicitlySet());
-        assertFalse(cfg.getLogTemplateExceptions());
+        assertFalse(cfgB.isLogTemplateExceptionsSet());
+        assertFalse(cfgB.getLogTemplateExceptions());
         //
-        cfg.setLogTemplateExceptions(true);
-        assertTrue(cfg.isLogTemplateExceptionsExplicitlySet());
-        assertTrue(cfg.getLogTemplateExceptions());
+        cfgB.setLogTemplateExceptions(true);
+        {
+            Configuration cfg = cfgB.build();
+            assertTrue(cfgB.isLogTemplateExceptionsSet());
+            assertTrue(cfg.isLogTemplateExceptionsSet());
+            assertTrue(cfgB.getLogTemplateExceptions());
+            assertTrue(cfg.getLogTemplateExceptions());
+        }
         //
         for (int i = 0; i < 2; i++) {
-            cfg.unsetLogTemplateExceptions();
-            assertFalse(cfg.isLogTemplateExceptionsExplicitlySet());
+            cfgB.unsetLogTemplateExceptions();
+            Configuration cfg = cfgB.build();
+            assertFalse(cfgB.isLogTemplateExceptionsSet());
+            assertTrue(cfg.isLogTemplateExceptionsSet());
+            assertFalse(cfgB.getLogTemplateExceptions());
             assertFalse(cfg.getLogTemplateExceptions());
         }
-        
-        assertFalse(cfg.isObjectWrapperExplicitlySet());
-        assertSame(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0), cfg.getObjectWrapper());
+
+        DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        assertFalse(cfgB.isObjectWrapperSet());
+        assertSame(dow, cfgB.getObjectWrapper());
         //
         RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
-        cfg.setObjectWrapper(ow);
-        assertTrue(cfg.isObjectWrapperExplicitlySet());
-        assertSame(ow, cfg.getObjectWrapper());
+        cfgB.setObjectWrapper(ow);
+        assertTrue(cfgB.isObjectWrapperSet());
+        assertSame(ow, cfgB.getObjectWrapper());
         //
         for (int i = 0; i < 2; i++) {
-            cfg.unsetObjectWrapper();
-            assertFalse(cfg.isObjectWrapperExplicitlySet());
-            assertSame(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0), cfg.getObjectWrapper());
+            cfgB.unsetObjectWrapper();
+            assertFalse(cfgB.isObjectWrapperSet());
+            assertSame(dow, cfgB.getObjectWrapper());
         }
         
-        assertFalse(cfg.isTemplateExceptionHandlerExplicitlySet());
-        assertSame(TemplateExceptionHandler.DEBUG_HANDLER, cfg.getTemplateExceptionHandler());
+        assertFalse(cfgB.isTemplateExceptionHandlerSet());
+        assertSame(TemplateExceptionHandler.DEBUG_HANDLER, cfgB.getTemplateExceptionHandler());
         //
-        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
-        assertTrue(cfg.isTemplateExceptionHandlerExplicitlySet());
-        assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfg.getTemplateExceptionHandler());
+        cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+        assertTrue(cfgB.isTemplateExceptionHandlerSet());
+        assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfgB.getTemplateExceptionHandler());
         //
         for (int i = 0; i < 2; i++) {
-            cfg.unsetTemplateExceptionHandler();
-            assertFalse(cfg.isTemplateExceptionHandlerExplicitlySet());
-            assertSame(TemplateExceptionHandler.DEBUG_HANDLER, cfg.getTemplateExceptionHandler());
+            cfgB.unsetTemplateExceptionHandler();
+            assertFalse(cfgB.isTemplateExceptionHandlerSet());
+            assertSame(TemplateExceptionHandler.DEBUG_HANDLER, cfgB.getTemplateExceptionHandler());
         }
         
-        assertFalse(cfg.isTemplateLoaderExplicitlySet());
-        assertNull(cfg.getTemplateLoader());
+        assertFalse(cfgB.isTemplateLoaderSet());
+        assertNull(cfgB.getTemplateLoader());
         //
-        cfg.setTemplateLoader(null);
-        assertTrue(cfg.isTemplateLoaderExplicitlySet());
-        assertNull(cfg.getTemplateLoader());
+        cfgB.setTemplateLoader(null);
+        assertTrue(cfgB.isTemplateLoaderSet());
+        assertNull(cfgB.getTemplateLoader());
         //
         for (int i = 0; i < 3; i++) {
             if (i == 2) {
-                cfg.setTemplateLoader(new StringTemplateLoader());
+                cfgB.setTemplateLoader(new StringTemplateLoader());
             }
-            cfg.unsetTemplateLoader();
-            assertFalse(cfg.isTemplateLoaderExplicitlySet());
-            assertNull(cfg.getTemplateLoader());
+            cfgB.unsetTemplateLoader();
+            assertFalse(cfgB.isTemplateLoaderSet());
+            assertNull(cfgB.getTemplateLoader());
         }
         
-        assertFalse(cfg.isTemplateLookupStrategyExplicitlySet());
-        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy());
+        assertFalse(cfgB.isTemplateLookupStrategySet());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.getTemplateLookupStrategy());
         //
-        cfg.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
-        assertTrue(cfg.isTemplateLookupStrategyExplicitlySet());
+        cfgB.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
+        assertTrue(cfgB.isTemplateLookupStrategySet());
         //
         for (int i = 0; i < 2; i++) {
-            cfg.unsetTemplateLookupStrategy();
-            assertFalse(cfg.isTemplateLookupStrategyExplicitlySet());
+            cfgB.unsetTemplateLookupStrategy();
+            assertFalse(cfgB.isTemplateLookupStrategySet());
         }
         
-        assertFalse(cfg.isTemplateNameFormatExplicitlySet());
-        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfg.getTemplateNameFormat());
+        assertFalse(cfgB.isTemplateNameFormatSet());
+        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat());
         //
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
-        assertTrue(cfg.isTemplateNameFormatExplicitlySet());
-        assertSame(DefaultTemplateNameFormat.INSTANCE, cfg.getTemplateNameFormat());
+        cfgB.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+        assertTrue(cfgB.isTemplateNameFormatSet());
+        assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat());
         //
         for (int i = 0; i < 2; i++) {
-            cfg.unsetTemplateNameFormat();
-            assertFalse(cfg.isTemplateNameFormatExplicitlySet());
-            assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfg.getTemplateNameFormat());
+            cfgB.unsetTemplateNameFormat();
+            assertFalse(cfgB.isTemplateNameFormatSet());
+            assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat());
         }
         
-        assertFalse(cfg.isCacheStorageExplicitlySet());
-        assertTrue(cfg.getCacheStorage() instanceof SoftCacheStorage);
+        assertFalse(cfgB.isCacheStorageSet());
+        assertTrue(cfgB.getCacheStorage() instanceof SoftCacheStorage);
         //
-        cfg.setCacheStorage(NullCacheStorage.INSTANCE);
-        assertTrue(cfg.isCacheStorageExplicitlySet());
-        assertSame(NullCacheStorage.INSTANCE, cfg.getCacheStorage());
+        cfgB.setCacheStorage(NullCacheStorage.INSTANCE);
+        assertTrue(cfgB.isCacheStorageSet());
+        assertSame(NullCacheStorage.INSTANCE, cfgB.getCacheStorage());
         //
         for (int i = 0; i < 3; i++) {
             if (i == 2) {
-                cfg.setCacheStorage(cfg.getCacheStorage());
+                cfgB.setCacheStorage(cfgB.getCacheStorage());
             }
-            cfg.unsetCacheStorage();
-            assertFalse(cfg.isCacheStorageExplicitlySet());
-            assertTrue(cfg.getCacheStorage() instanceof SoftCacheStorage);
+            cfgB.unsetCacheStorage();
+            assertFalse(cfgB.isCacheStorageSet());
+            assertTrue(cfgB.getCacheStorage() instanceof SoftCacheStorage);
         }
     }
     
     public void testTemplateLoadingErrors() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setClassForTemplateLoading(getClass(), "nosuchpackage");
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .templateLoader(new ClassTemplateLoader(getClass(), "nosuchpackage"))
+                .build();
         try {
             cfg.getTemplate("missing.ftl");
             fail();
@@ -206,40 +217,34 @@ public class ConfigurationTest extends TestCase {
         }
     }
     
-    private void assertUsesNewObjectWrapper(Configuration cfg) {
-        assertEquals(
-                Configuration.VERSION_3_0_0,
-                ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
-    }
-    
     public void testVersion() {
         Version v = Configuration.getVersion();
         assertTrue(v.intValue() >= _CoreAPI.VERSION_INT_3_0_0);
         
         try {
-            new Configuration(new Version(999, 1, 2));
+            new Configuration.Builder(new Version(999, 1, 2));
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("upgrade"));
         }
         
         try {
-            new Configuration(new Version(2, 3, 0));
+            new Configuration.Builder(new Version(2, 3, 0));
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("3.0.0"));
         }
     }
     
     public void testShowErrorTips() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         try {
+            Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
             new Template(null, "${x}", cfg).process(null, _NullWriter.INSTANCE);
             fail();
         } catch (TemplateException e) {
             assertThat(e.getMessage(), containsString("Tip:"));
         }
-        
-        cfg.setShowErrorTips(false);
+
         try {
+            Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).showErrorTips(false).build();
             new Template(null, "${x}", cfg).process(null, _NullWriter.INSTANCE);
             fail();
         } catch (TemplateException e) {
@@ -255,25 +260,24 @@ public class ConfigurationTest extends TestCase {
         final String tHuFtl = "t_hu.ftl";
         final String tEnFtl = "t_en.ftl";
         final String tUtf8Ftl = "utf8.ftl";
-        final Serializable custLookupCond = new Serializable() { };
-        
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.GERMAN);
-        cfg.setSourceEncoding(StandardCharsets.ISO_8859_1);
-
-        TemplateConfiguration.Builder huTCB = new TemplateConfiguration.Builder();
-        huTCB.setSourceEncoding(ISO_8859_2);
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*_hu.*"),
-                huTCB.build()));
+        final Serializable custLookupCond = 12345;
 
         ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
         tl.putTemplate(tFtl, "${1}".getBytes(StandardCharsets.UTF_8));
         tl.putTemplate(tEnFtl, "${1}".getBytes(StandardCharsets.UTF_8));
         tl.putTemplate(tHuFtl, "${1}".getBytes(StandardCharsets.UTF_8));
         tl.putTemplate(tUtf8Ftl, "<#ftl encoding='utf-8'>".getBytes(StandardCharsets.UTF_8));
-        cfg.setTemplateLoader(tl);
-        
+
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .locale(Locale.GERMAN)
+                .sourceEncoding(StandardCharsets.ISO_8859_1)
+                .templateLoader(tl)
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("*_hu.*"),
+                                new TemplateConfiguration.Builder().sourceEncoding(ISO_8859_2).build()))
+                .build();
+
         // 1 args:
         {
             Template t = cfg.getTemplate(tFtl);
@@ -377,96 +381,48 @@ public class ConfigurationTest extends TestCase {
         assertEquals(expectedContent, sw.toString());
     }
     
-    public void testSetTemplateLoaderAndCache() throws Exception {
-        Configuration cfg = new Configuration();
+    public void testTemplateResolverCache() throws Exception {
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
-        CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
+        CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfgB.getCacheStorage();
         assertEquals(0, cache.getSize());
-        cfg.setCacheStorage(new StrongCacheStorage());
-        cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
+        cfgB.setCacheStorage(new StrongCacheStorage());
+        cache = (CacheStorageWithGetSize) cfgB.getCacheStorage();
         assertEquals(0, cache.getSize());
-        
-        cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
+        cfgB.setTemplateLoader(new ClassTemplateLoader(ConfigurationTest.class, ""));
+        Configuration cfg = cfgB.build();
         assertEquals(0, cache.getSize());
         cfg.getTemplate("toCache1.ftl");
         assertEquals(1, cache.getSize());
         cfg.getTemplate("toCache2.ftl");
         assertEquals(2, cache.getSize());
-        cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
-        assertEquals(0, cache.getSize());
-        cfg.getTemplate("toCache1.ftl");
-        assertEquals(1, cache.getSize());
-        cfg.setTemplateLoader(cfg.getTemplateLoader());
-        assertEquals(1, cache.getSize());
-    }
-
-    public void testChangingLocalizedLookupClearsCache() throws Exception {
-        Configuration cfg = new Configuration();
-        cfg.setCacheStorage(new StrongCacheStorage());
-        CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
-        cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
-        
-        assertEquals(0, cache.getSize());
-        
-        cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
-        assertEquals(0, cache.getSize());
-        cfg.getTemplate("toCache1.ftl");
-        assertEquals(1, cache.getSize());
-        cfg.setLocalizedLookup(true);
-        assertEquals(1, cache.getSize());
-        cfg.setLocalizedLookup(false);
-        assertEquals(0, cache.getSize());
-        cfg.getTemplate("toCache1.ftl");
-        assertEquals(1, cache.getSize());
-        cfg.setLocalizedLookup(false);
-        assertEquals(1, cache.getSize());
-        cfg.setLocalizedLookup(true);
-        assertEquals(0, cache.getSize());
-    }
-
-    public void testChangingTemplateNameFormatClearsCache() throws Exception {
-        Configuration cfg = new Configuration();
-        cfg.setCacheStorage(new StrongCacheStorage());
-        CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
-        cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
-        
-        assertEquals(0, cache.getSize());
-        
-        cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
-        assertEquals(0, cache.getSize());
-        cfg.getTemplate("toCache1.ftl");
-        assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
-        assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+        cfg.clearTemplateCache();
         assertEquals(0, cache.getSize());
         cfg.getTemplate("toCache1.ftl");
         assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+        cfgB.setTemplateLoader(cfgB.getTemplateLoader());
         assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
-        assertEquals(0, cache.getSize());
     }
 
-    public void testChangingTemplateNameFormatHasEffect() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
+    public void testTemplateNameFormat() throws Exception {
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("a/b.ftl", "In a/b.ftl");
         tl.putTemplate("b.ftl", "In b.ftl");
-        cfg.setTemplateLoader(tl);
+
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .templateLoader(tl);
 
         {
-            cfg.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
-            final Template template = cfg.getTemplate("a/./../b.ftl");
+            cfgB.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
+            final Template template = cfgB.build().getTemplate("a/./../b.ftl");
             assertEquals("a/b.ftl", template.getLookupName());
             assertEquals("a/b.ftl", template.getSourceName());
             assertEquals("In a/b.ftl", template.toString());
         }
         
         {
-            cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
-            final Template template = cfg.getTemplate("a/./../b.ftl");
+            cfgB.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+            final Template template = cfgB.build().getTemplate("a/./../b.ftl");
             assertEquals("b.ftl", template.getLookupName());
             assertEquals("b.ftl", template.getSourceName());
             assertEquals("In b.ftl", template.toString());
@@ -474,82 +430,72 @@ public class ConfigurationTest extends TestCase {
     }
 
     public void testTemplateNameFormatSetSetting() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfg.getTemplateNameFormat());
-        cfg.setSetting(Configuration.TEMPLATE_NAME_FORMAT_KEY, "defAult_2_4_0");
-        assertSame(DefaultTemplateNameFormat.INSTANCE, cfg.getTemplateNameFormat());
-        cfg.setSetting(Configuration.TEMPLATE_NAME_FORMAT_KEY, "defaUlt_2_3_0");
-        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfg.getTemplateNameFormat());
-        assertTrue(cfg.isTemplateNameFormatExplicitlySet());
-        cfg.setSetting(Configuration.TEMPLATE_NAME_FORMAT_KEY, "defauLt");
-        assertFalse(cfg.isTemplateNameFormatExplicitlySet());
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY, "defAult_2_4_0");
+        assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY, "defaUlt_2_3_0");
+        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat());
+        assertTrue(cfgB.isTemplateNameFormatSet());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY, "defauLt");
+        assertFalse(cfgB.isTemplateNameFormatSet());
     }
 
     public void testObjectWrapperSetSetting() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         {
-            cfg.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "defAult");
-            assertSame(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0), cfg.getObjectWrapper());
-            DefaultObjectWrapper dow = (DefaultObjectWrapper) cfg.getObjectWrapper();
+            cfgB.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "defAult");
+            DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+            assertSame(dow, cfgB.getObjectWrapper());
             assertEquals(Configuration.VERSION_3_0_0, dow.getIncompatibleImprovements());
         }
         
         {
-            cfg.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "restricted");
-            assertThat(cfg.getObjectWrapper(), instanceOf(RestrictedObjectWrapper.class));
+            cfgB.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "restricted");
+            assertThat(cfgB.getObjectWrapper(), instanceOf(RestrictedObjectWrapper.class));
         }
     }
     
-    public void testTemplateLookupStrategyDefaultAndSet() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy());
-        
-        cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
+    public void testTemplateLookupStrategyDefaultAndSet() throws Exception {
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.getTemplateLookupStrategy());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.build().getTemplateLookupStrategy());
+
+        cfgB.setTemplateLoader(new ClassTemplateLoader(ConfigurationTest.class, ""));
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.getTemplateLookupStrategy());
+        Configuration cfg = cfgB.build();
         assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy());
-        
-        CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfg.getCacheStorage();
-        cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
-        assertEquals(0, cache.getSize());
         cfg.getTemplate("toCache1.ftl");
-        assertEquals(1, cache.getSize());
-        
-        cfg.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
-        assertEquals(1, cache.getSize());
-        
+
         final TemplateLookupStrategy myStrategy = new TemplateLookupStrategy() {
             @Override
             public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
-                return ctx.lookupWithAcquisitionStrategy(ctx.getTemplateName());
+                return ctx.lookupWithAcquisitionStrategy("toCache2.ftl");
             }
         };
-        cfg.setTemplateLookupStrategy(myStrategy);
-        assertEquals(0, cache.getSize());
+        cfgB.setTemplateLookupStrategy(myStrategy);
+        assertSame(myStrategy, cfgB.getTemplateLookupStrategy());
+        cfg = cfgB.build();
+        cfg.clearTemplateCache();
         assertSame(myStrategy, cfg.getTemplateLookupStrategy());
-        cfg.getTemplate("toCache1.ftl");
-        assertEquals(1, cache.getSize());
-        
-        cfg.setTemplateLookupStrategy(myStrategy);
-        assertEquals(1, cache.getSize());
-        
-        cfg.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
-        assertEquals(0, cache.getSize());
+        Template template = cfg.getTemplate("toCache1.ftl");
+        assertEquals("toCache2.ftl", template.getSourceName());
     }
     
     public void testSetTemplateConfigurations() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        assertNull(cfg.getTemplateConfigurations());
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+        assertNull(cfgB.getTemplateConfigurations());
 
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("t.de.ftlh", "");
         tl.putTemplate("t.fr.ftlx", "");
         tl.putTemplate("t.ftlx", "");
         tl.putTemplate("Stat/t.de.ftlx", "");
-        cfg.setTemplateLoader(tl);
+        cfgB.setTemplateLoader(tl);
         
-        cfg.setTimeZone(TimeZone.getTimeZone("GMT+09"));
+        cfgB.setTimeZone(TimeZone.getTimeZone("GMT+09"));
         
-        cfg.setSetting(Configuration.TEMPLATE_CONFIGURATIONS_KEY,
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY,
                 "MergingTemplateConfigurationFactory("
                     + "FirstMatchTemplateConfigurationFactory("
                         + "ConditionalTemplateConfigurationFactory("
@@ -570,7 +516,8 @@ public class ConfigurationTest extends TestCase {
                         + "TemplateConfiguration(timeZone=TimeZone('UTC'))"
                     + ")"
                 + ")");
-        
+
+        Configuration cfg = cfgB.build();
         {
             Template t = cfg.getTemplate("t.de.ftlh");
             assertEquals("TODO,HTML", t.getBooleanFormat());
@@ -592,42 +539,42 @@ public class ConfigurationTest extends TestCase {
             assertEquals(_DateUtil.UTC, t.getTimeZone());
         }
         
-        assertNotNull(cfg.getTemplateConfigurations());
-        cfg.setSetting(Configuration.TEMPLATE_CONFIGURATIONS_KEY, "null");
-        assertNull(cfg.getTemplateConfigurations());
+        assertNotNull(cfgB.getTemplateConfigurations());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY, "null");
+        assertNull(cfgB.getTemplateConfigurations());
     }
 
     public void testSetAutoEscaping() throws Exception {
-       Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+       Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
     
-       assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
 
-       cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY);
-       assertEquals(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setAutoEscapingPolicy(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY);
+       assertEquals(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
 
-       cfg.setAutoEscapingPolicy(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
-       assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setAutoEscapingPolicy(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
+       assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
 
-       cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
-       assertEquals(Configuration.DISABLE_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setAutoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY);
+       assertEquals(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
        
-       cfg.setSetting(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enableIfSupported");
-       assertEquals(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enableIfSupported");
+       assertEquals(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
 
-       cfg.setSetting(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enable_if_supported");
-       assertEquals(Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enable_if_supported");
+       assertEquals(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
        
-       cfg.setSetting(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enableIfDefault");
-       assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enableIfDefault");
+       assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
 
-       cfg.setSetting(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enable_if_default");
-       assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enable_if_default");
+       assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
        
-       cfg.setSetting(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "disable");
-       assertEquals(Configuration.DISABLE_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy());
+       cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "disable");
+       assertEquals(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
        
        try {
-           cfg.setAutoEscapingPolicy(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+           cfgB.setAutoEscapingPolicy(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
            fail();
        } catch (IllegalArgumentException e) {
            // Expected
@@ -635,37 +582,37 @@ public class ConfigurationTest extends TestCase {
     }
 
     public void testSetOutputFormat() throws Exception {
-       Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+       Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
        
-       assertEquals(UndefinedOutputFormat.INSTANCE, cfg.getOutputFormat());
-       assertFalse(cfg.isOutputFormatExplicitlySet());
+       assertEquals(UndefinedOutputFormat.INSTANCE, cfgB.getOutputFormat());
+       assertFalse(cfgB.isOutputFormatSet());
        
        try {
-           cfg.setOutputFormat(null);
+           cfgB.setOutputFormat(null);
            fail();
        } catch (_NullArgumentException e) {
            // Expected
        }
        
-       assertFalse(cfg.isOutputFormatExplicitlySet());
+       assertFalse(cfgB.isOutputFormatSet());
        
-       cfg.setSetting(Configuration.OUTPUT_FORMAT_KEY_CAMEL_CASE, XMLOutputFormat.class.getSimpleName());
-       assertEquals(XMLOutputFormat.INSTANCE, cfg.getOutputFormat());
+       cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE, XMLOutputFormat.class.getSimpleName());
+       assertEquals(XMLOutputFormat.INSTANCE, cfgB.getOutputFormat());
        
-       cfg.setSetting(Configuration.OUTPUT_FORMAT_KEY_SNAKE_CASE, HTMLOutputFormat.class.getSimpleName());
-       assertEquals(HTMLOutputFormat.INSTANCE, cfg.getOutputFormat());
+       cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_SNAKE_CASE, HTMLOutputFormat.class.getSimpleName());
+       assertEquals(HTMLOutputFormat.INSTANCE, cfgB.getOutputFormat());
        
-       cfg.unsetOutputFormat();
-       assertEquals(UndefinedOutputFormat.INSTANCE, cfg.getOutputFormat());
-       assertFalse(cfg.isOutputFormatExplicitlySet());
+       cfgB.unsetOutputFormat();
+       assertEquals(UndefinedOutputFormat.INSTANCE, cfgB.getOutputFormat());
+       assertFalse(cfgB.isOutputFormatSet());
        
-       cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE);
-       assertTrue(cfg.isOutputFormatExplicitlySet());
-       cfg.setSetting(Configuration.OUTPUT_FORMAT_KEY_CAMEL_CASE, "default");
-       assertFalse(cfg.isOutputFormatExplicitlySet());
+       cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+       assertTrue(cfgB.isOutputFormatSet());
+       cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE, "default");
+       assertFalse(cfgB.isOutputFormatSet());
        
        try {
-           cfg.setSetting(Configuration.OUTPUT_FORMAT_KEY, "null");
+           cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY, "null");
        } catch (ConfigurationSettingValueException e) {
            assertThat(e.getCause().getMessage(), containsString(UndefinedOutputFormat.class.getSimpleName()));
        }
@@ -673,7 +620,7 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testGetOutputFormatByName() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
         
         assertSame(HTMLOutputFormat.INSTANCE, cfg.getOutputFormat(HTMLOutputFormat.INSTANCE.getName()));
         
@@ -726,11 +673,11 @@ public class ConfigurationTest extends TestCase {
     }
 
     public void testSetRegisteredCustomOutputFormats() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
         assertTrue(cfg.getRegisteredCustomOutputFormats().isEmpty());
         
-        cfg.setSetting(Configuration.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
+        cfg.setSetting(Configuration.ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
                 "[org.apache.freemarker.core.userpkg.CustomHTMLOutputFormat(), "
                 + "org.apache.freemarker.core.userpkg.DummyOutputFormat()]");
         assertEquals(
@@ -738,7 +685,7 @@ public class ConfigurationTest extends TestCase {
                 new ArrayList(cfg.getRegisteredCustomOutputFormats()));
         
         try {
-            cfg.setSetting(Configuration.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE, "[TemplateConfiguration()]");
+            cfg.setSetting(Configuration.ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE, "[TemplateConfiguration()]");
             fail();
         } catch (ConfigurationSettingValueException e) {
             assertThat(e.getMessage(), containsString(OutputFormat.class.getSimpleName()));
@@ -746,30 +693,30 @@ public class ConfigurationTest extends TestCase {
     }
 
     public void testSetRecognizeStandardFileExtensions() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
      
-        assertTrue(cfg.getRecognizeStandardFileExtensions());
-        assertFalse(cfg.isRecognizeStandardFileExtensionsExplicitlySet());
+        assertTrue(cfgB.getRecognizeStandardFileExtensions());
+        assertFalse(cfgB.isRecognizeStandardFileExtensionsSet());
 
-        cfg.setRecognizeStandardFileExtensions(false);
-        assertFalse(cfg.getRecognizeStandardFileExtensions());
-        assertTrue(cfg.isRecognizeStandardFileExtensionsExplicitlySet());
+        cfgB.setRecognizeStandardFileExtensions(false);
+        assertFalse(cfgB.getRecognizeStandardFileExtensions());
+        assertTrue(cfgB.isRecognizeStandardFileExtensionsSet());
      
-        cfg.unsetRecognizeStandardFileExtensions();
-        assertTrue(cfg.getRecognizeStandardFileExtensions());
-        assertFalse(cfg.isRecognizeStandardFileExtensionsExplicitlySet());
+        cfgB.unsetRecognizeStandardFileExtensions();
+        assertTrue(cfgB.getRecognizeStandardFileExtensions());
+        assertFalse(cfgB.isRecognizeStandardFileExtensionsSet());
         
-        cfg.setRecognizeStandardFileExtensions(true);
-        assertTrue(cfg.getRecognizeStandardFileExtensions());
-        assertTrue(cfg.isRecognizeStandardFileExtensionsExplicitlySet());
+        cfgB.setRecognizeStandardFileExtensions(true);
+        assertTrue(cfgB.getRecognizeStandardFileExtensions());
+        assertTrue(cfgB.isRecognizeStandardFileExtensionsSet());
      
-        cfg.setSetting(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE, "false");
-        assertFalse(cfg.getRecognizeStandardFileExtensions());
-        assertTrue(cfg.isRecognizeStandardFileExtensionsExplicitlySet());
+        cfgB.setSetting(Configuration.ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE, "false");
+        assertFalse(cfgB.getRecognizeStandardFileExtensions());
+        assertTrue(cfgB.isRecognizeStandardFileExtensionsSet());
         
-        cfg.setSetting(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE, "default");
-        assertTrue(cfg.getRecognizeStandardFileExtensions());
-        assertFalse(cfg.isRecognizeStandardFileExtensionsExplicitlySet());
+        cfgB.setSetting(Configuration.ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE, "default");
+        assertTrue(cfgB.getRecognizeStandardFileExtensions());
+        assertFalse(cfgB.isRecognizeStandardFileExtensionsSet());
      }
     
     public void testSetTimeZone() throws ConfigurationException {
@@ -778,16 +725,16 @@ public class ConfigurationTest extends TestCase {
             TimeZone sysDefTZ = TimeZone.getTimeZone("GMT-01");
             TimeZone.setDefault(sysDefTZ);
             
-            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            assertEquals(sysDefTZ, cfg.getTimeZone());
-            cfg.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default");
-            assertEquals(sysDefTZ, cfg.getTimeZone());
+            Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+            assertEquals(sysDefTZ, cfgB.getTimeZone());
+            cfgB.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default");
+            assertEquals(sysDefTZ, cfgB.getTimeZone());
             
             TimeZone newSysDefTZ = TimeZone.getTimeZone("GMT+09");
             TimeZone.setDefault(newSysDefTZ);
-            assertEquals(sysDefTZ, cfg.getTimeZone());
-            cfg.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default");
-            assertEquals(newSysDefTZ, cfg.getTimeZone());
+            assertEquals(sysDefTZ, cfgB.getTimeZone());
+            cfgB.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default");
+            assertEquals(newSysDefTZ, cfgB.getTimeZone());
         } finally {
             TimeZone.setDefault(origSysDefTZ);
         }
@@ -799,81 +746,96 @@ public class ConfigurationTest extends TestCase {
             TimeZone sysDefTZ = TimeZone.getTimeZone("GMT-01");
             TimeZone.setDefault(sysDefTZ);
             
-            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            assertNull(cfg.getSQLDateAndTimeTimeZone());
+            Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+            assertNull(cfgB.getSQLDateAndTimeTimeZone());
             
-            cfg.setSQLDateAndTimeTimeZone(null);
-            assertNull(cfg.getSQLDateAndTimeTimeZone());
+            cfgB.setSQLDateAndTimeTimeZone(null);
+            assertNull(cfgB.getSQLDateAndTimeTimeZone());
             
-            cfg.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "JVM default");
-            assertEquals(sysDefTZ, cfg.getSQLDateAndTimeTimeZone());
+            cfgB.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "JVM default");
+            assertEquals(sysDefTZ, cfgB.getSQLDateAndTimeTimeZone());
             
-            cfg.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "null");
-            assertNull(cfg.getSQLDateAndTimeTimeZone());
+            cfgB.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "null");
+            assertNull(cfgB.getSQLDateAndTimeTimeZone());
         } finally {
             TimeZone.setDefault(origSysDefTZ);
         }
     }
 
     public void testTimeZoneLayers() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        Environment env1 = t.createProcessingEnvironment(null, new StringWriter());
-        Environment env2 = t.createProcessingEnvironment(null, new StringWriter());
-        
-        // cfg:
-        assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
-        assertNull(cfg.getSQLDateAndTimeTimeZone());
-        // env:
-        assertEquals(TimeZone.getDefault(), env1.getTimeZone());
-        assertNull(env1.getSQLDateAndTimeTimeZone());
-        // env 2:
-        assertEquals(TimeZone.getDefault(), env2.getTimeZone());
-        assertNull(env2.getSQLDateAndTimeTimeZone());
-        
-        env1.setSQLDateAndTimeTimeZone(_DateUtil.UTC);
-        // cfg:
-        assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
-        assertNull(cfg.getSQLDateAndTimeTimeZone());
-        // env:
-        assertEquals(TimeZone.getDefault(), env1.getTimeZone());
-        assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone());
-        
         TimeZone localTZ = TimeZone.getTimeZone("Europe/Brussels");
-        env1.setTimeZone(localTZ);
-        // cfg:
-        assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
-        assertNull(cfg.getSQLDateAndTimeTimeZone());
-        // env:
-        assertEquals(localTZ, env1.getTimeZone());
-        assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone());
-        // env 2:
-        assertEquals(TimeZone.getDefault(), env2.getTimeZone());
-        assertNull(env2.getSQLDateAndTimeTimeZone());
-        
-        TimeZone otherTZ1 = TimeZone.getTimeZone("GMT+05");
-        TimeZone otherTZ2 = TimeZone.getTimeZone("GMT+06");
-        cfg.setTimeZone(otherTZ1);
-        cfg.setSQLDateAndTimeTimeZone(otherTZ2);
-        // cfg:
-        assertEquals(otherTZ1, cfg.getTimeZone());
-        assertEquals(otherTZ2, cfg.getSQLDateAndTimeTimeZone());
-        // env:
-        assertEquals(localTZ, env1.getTimeZone());
-        assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone());
-        // env 2:
-        assertEquals(otherTZ1, env2.getTimeZone());
-        assertEquals(otherTZ2, env2.getSQLDateAndTimeTimeZone());
-        
-        try {
-            setTimeZoneToNull(env2);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // expected
+
+        {
+            Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
+            Template t = new Template(null, "", cfg);
+            Environment env1 = t.createProcessingEnvironment(null, new StringWriter());
+            Environment env2 = t.createProcessingEnvironment(null, new StringWriter());
+
+            // cfg:
+            assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
+            assertNull(cfg.getSQLDateAndTimeTimeZone());
+            // env:
+            assertEquals(TimeZone.getDefault(), env1.getTimeZone());
+            assertNull(env1.getSQLDateAndTimeTimeZone());
+            // env 2:
+            assertEquals(TimeZone.getDefault(), env2.getTimeZone());
+            assertNull(env2.getSQLDateAndTimeTimeZone());
+
+            env1.setSQLDateAndTimeTimeZone(_DateUtil.UTC);
+            // cfg:
+            assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
+            assertNull(cfg.getSQLDateAndTimeTimeZone());
+            // env:
+            assertEquals(TimeZone.getDefault(), env1.getTimeZone());
+            assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone());
+
+            env1.setTimeZone(localTZ);
+            // cfg:
+            assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
+            assertNull(cfg.getSQLDateAndTimeTimeZone());
+            // env:
+            assertEquals(localTZ, env1.getTimeZone());
+            assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone());
+            // env 2:
+            assertEquals(TimeZone.getDefault(), env2.getTimeZone());
+            assertNull(env2.getSQLDateAndTimeTimeZone());
+        }
+
+        {
+            TimeZone otherTZ1 = TimeZone.getTimeZone("GMT+05");
+            TimeZone otherTZ2 = TimeZone.getTimeZone("GMT+06");
+            Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                    .timeZone(otherTZ1)
+                    .sqlDateAndTimeTimeZone(otherTZ2)
+                    .build();
+
+            Template t = new Template(null, "", cfg);
+            Environment env1 = t.createProcessingEnvironment(null, new StringWriter());
+            Environment env2 = t.createProcessingEnvironment(null, new StringWriter());
+
+            env1.setTimeZone(localTZ);
+            env1.setSQLDateAndTimeTimeZone(_DateUtil.UTC);
+
+            // cfg:
+            assertEquals(otherTZ1, cfg.getTimeZone());
+            assertEquals(otherTZ2, cfg.getSQLDateAndTimeTimeZone());
+            // env:
+            assertEquals(localTZ, env1.getTimeZone());
+            assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone());
+            // env 2:
+            assertEquals(otherTZ1, env2.getTimeZone());
+            assertEquals(otherTZ2, env2.getSQLDateAndTimeTimeZone());
+
+            try {
+                setTimeZoneToNull(env2);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            env2.setSQLDateAndTimeTimeZone(null);
+            assertEquals(otherTZ1, env2.getTimeZone());
+            assertNull(env2.getSQLDateAndTimeTimeZone());
         }
-        env2.setSQLDateAndTimeTimeZone(null);
-        assertEquals(otherTZ1, env2.getTimeZone());
-        assertNull(env2.getSQLDateAndTimeTimeZone());
     }
 
     @SuppressFBWarnings(value="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS", justification="Expected to fail")
@@ -882,167 +844,169 @@ public class ConfigurationTest extends TestCase {
     }
     
     public void testSetICIViaSetSettingAPI() throws ConfigurationException {
-        Configuration cfg = new Configuration();
+        Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0);
         assertEquals(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS, cfg.getIncompatibleImprovements());
-        cfg.setSetting(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0"); // This is the only valid value ATM...
+        // This is the only valid value ATM:
+        cfg.setSetting(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0");
         assertEquals(Configuration.VERSION_3_0_0, cfg.getIncompatibleImprovements());
     }
 
     public void testSetLogTemplateExceptionsViaSetSettingAPI() throws ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0);
         assertFalse(cfg.getLogTemplateExceptions());
         cfg.setSetting(MutableProcessingConfiguration.LOG_TEMPLATE_EXCEPTIONS_KEY, "true");
         assertTrue(cfg.getLogTemplateExceptions());
     }
     
-    public void testSharedVariables() throws TemplateModelException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+    public void testSharedVariables() throws TemplateException, IOException {
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
 
-        cfg.setSharedVariable("erased", "");
-        assertNotNull(cfg.getSharedVariable("erased"));
-        
         Map<String, Object> vars = new HashMap<>();
         vars.put("a", "aa");
         vars.put("b", "bb");
         vars.put("c", new MyScalarModel());
-        cfg.setSharedVariables(vars);
+        cfgB.setSharedVariables(vars);
 
-        assertNull(cfg.getSharedVariable("erased"));
+        assertNull(cfgB.getSharedVariable("erased"));
         
         {
-            TemplateScalarModel aVal = (TemplateScalarModel) cfg.getSharedVariable("a");
+            Configuration cfg = cfgB.build();
+
+            TemplateScalarModel aVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("a");
             assertEquals("aa", aVal.getAsString());
             assertEquals(SimpleScalar.class, aVal.getClass());
-            
-            TemplateScalarModel bVal = (TemplateScalarModel) cfg.getSharedVariable("b");
+
+            TemplateScalarModel bVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("b");
             assertEquals("bb", bVal.getAsString());
             assertEquals(SimpleScalar.class, bVal.getClass());
             
-            TemplateScalarModel cVal = (TemplateScalarModel) cfg.getSharedVariable("c");
+            TemplateScalarModel cVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("c");
             assertEquals("my", cVal.getAsString());
-            assertEquals(MyScalarModel.class, cfg.getSharedVariable("c").getClass());
+            assertEquals(MyScalarModel.class, cfg.getWrappedSharedVariable("c").getClass());
+
+            // See if it actually works in templates:
+            StringWriter sw = new StringWriter();
+            new Template(null, "${a} ${b}", cfg)
+                    .process(ImmutableMap.of("a", "aaDM"), sw);
+            assertEquals("aaDM bb", sw.toString());
         }
         
-        // Legacy method: Keeps TemplateModel created on the time it was called. 
-        cfg.setSharedVariable("b", "bbLegacy");
+        cfgB.setSharedVariable("b", "bbLegacy");
         
-        // Cause re-wrapping of variables added via setSharedVariables:
-        cfg.setObjectWrapper(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
-
         {
-            TemplateScalarModel aVal = (TemplateScalarModel) cfg.getSharedVariable("a");
+            Configuration cfg = cfgB.build();
+
+            TemplateScalarModel aVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("a");
             assertEquals("aa", aVal.getAsString());
             assertEquals(SimpleScalar.class, aVal.getClass());
             
-            TemplateScalarModel bVal = (TemplateScalarModel) cfg.getSharedVariable("b");
+            TemplateScalarModel bVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("b");
             assertEquals("bbLegacy", bVal.getAsString());
             assertEquals(SimpleScalar.class, bVal.getClass());
-            
-            TemplateScalarModel cVal = (TemplateScalarModel) cfg.getSharedVariable("c");
-            assertEquals("my", cVal.getAsString());
-            assertEquals(MyScalarModel.class, cVal.getClass());
         }
     }
 
     @Test
-    public void testApiBuiltinEnabled() throws IOException, TemplateException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
+    public void testApiBuiltinEnabled() throws Exception {
         try {
-            new Template(null, "${1?api}", cfg).process(null, _NullWriter.INSTANCE);
+            new Template(
+                    null, "${1?api}",
+                    new Configuration.Builder(Configuration.VERSION_3_0_0).build())
+                    .process(null, _NullWriter.INSTANCE);
             fail();
         } catch (TemplateException e) {
             assertThat(e.getMessage(), containsString(MutableProcessingConfiguration.API_BUILTIN_ENABLED_KEY));
         }
             
-        cfg.setAPIBuiltinEnabled(true);
-        new Template(null, "${m?api.hashCode()}", cfg)
+        new Template(
+                null, "${m?api.hashCode()}",
+                new Configuration.Builder(Configuration.VERSION_3_0_0).apiBuiltinEnabled(true).build())
                 .process(Collections.singletonMap("m", new HashMap()), _NullWriter.INSTANCE);
     }
 
     @Test
     public void testTemplateUpdateDelay() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
 
-        assertEquals(DefaultTemplateResolver.DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS, cfg.getTemplateUpdateDelayMilliseconds());
+        assertEquals(DefaultTemplateResolver.DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS, cfgB.getTemplateUpdateDelayMilliseconds());
         
-        cfg.setTemplateUpdateDelayMilliseconds(4000);
-        assertEquals(4000L, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setTemplateUpdateDelayMilliseconds(4000);
+        assertEquals(4000L, cfgB.getTemplateUpdateDelayMilliseconds());
         
-        cfg.setTemplateUpdateDelayMilliseconds(100);
-        assertEquals(100L, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setTemplateUpdateDelayMilliseconds(100);
+        assertEquals(100L, cfgB.getTemplateUpdateDelayMilliseconds());
         
         try {
-            cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "5");
-            assertEquals(5000L, cfg.getTemplateUpdateDelayMilliseconds());
+            cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "5");
+            assertEquals(5000L, cfgB.getTemplateUpdateDelayMilliseconds());
         } catch (ConfigurationSettingValueException e) {
             assertThat(e.getMessage(), containsStringIgnoringCase("unit must be specified"));
         }
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "0");
-        assertEquals(0L, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "0");
+        assertEquals(0L, cfgB.getTemplateUpdateDelayMilliseconds());
         try {
-            cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "5 foo");
-            assertEquals(5000L, cfg.getTemplateUpdateDelayMilliseconds());
+            cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "5 foo");
+            assertEquals(5000L, cfgB.getTemplateUpdateDelayMilliseconds());
         } catch (ConfigurationSettingValueException e) {
             assertThat(e.getMessage(), containsStringIgnoringCase("\"foo\""));
         }
         
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "3 ms");
-        assertEquals(3L, cfg.getTemplateUpdateDelayMilliseconds());
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "4ms");
-        assertEquals(4L, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "3 ms");
+        assertEquals(3L, cfgB.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "4ms");
+        assertEquals(4L, cfgB.getTemplateUpdateDelayMilliseconds());
         
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "3 s");
-        assertEquals(3000L, cfg.getTemplateUpdateDelayMilliseconds());
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "4s");
-        assertEquals(4000L, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "3 s");
+        assertEquals(3000L, cfgB.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "4s");
+        assertEquals(4000L, cfgB.getTemplateUpdateDelayMilliseconds());
         
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "3 m");
-        assertEquals(1000L * 60 * 3, cfg.getTemplateUpdateDelayMilliseconds());
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "4m");
-        assertEquals(1000L * 60 * 4, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "3 m");
+        assertEquals(1000L * 60 * 3, cfgB.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "4m");
+        assertEquals(1000L * 60 * 4, cfgB.getTemplateUpdateDelayMilliseconds());
 
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "1 h");
-        assertEquals(1000L * 60 * 60, cfg.getTemplateUpdateDelayMilliseconds());
-        cfg.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY, "2h");
-        assertEquals(1000L * 60 * 60 * 2, cfg.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "1 h");
+        assertEquals(1000L * 60 * 60, cfgB.getTemplateUpdateDelayMilliseconds());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "2h");
+        assertEquals(1000L * 60 * 60 * 2, cfgB.getTemplateUpdateDelayMilliseconds());
     }
     
     @Test
     @SuppressFBWarnings(value = "NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS ", justification = "Testing wrong args")
     public void testSetCustomNumberFormat() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
         try {
-            cfg.setCustomNumberFormats(null);
+            cfgB.setCustomNumberFormats(null);
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("null"));
         }
 
         try {
-            cfg.setCustomNumberFormats(Collections.singletonMap("", HexTemplateNumberFormatFactory.INSTANCE));
+            cfgB.setCustomNumberFormats(Collections.singletonMap("", HexTemplateNumberFormatFactory.INSTANCE));
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("0 length"));
         }
 
         try {
-            cfg.setCustomNumberFormats(Collections.singletonMap("a_b", HexTemplateNumberFormatFactory.INSTANCE));
+            cfgB.setCustomNumberFormats(Collections.singletonMap("a_b", HexTemplateNumberFormatFactory.INSTANCE));
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("a_b"));
         }
 
         try {
-            cfg.setCustomNumberFormats(Collections.singletonMap("a b", HexTemplateNumberFormatFactory.INSTANCE));
+            cfgB.setCustomNumberFormats(Collections.singletonMap("a b", HexTemplateNumberFormatFactory.INSTANCE));
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("a b"));
         }
         
         try {
-            cfg.setCustomNumberFormats(ImmutableMap.of(
+            cfgB.setCustomNumberFormats(ImmutableMap.of(
                     "a", HexTemplateNumberFormatFactory.INSTANCE,
                     "@wrong", HexTemplateNumberFormatFactory.INSTANCE));
             fail();
@@ -1050,13 +1014,13 @@ public class ConfigurationTest extends TestCase {
             assertThat(e.getMessage(), containsString("@wrong"));
         }
         
-        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
+        cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
                 "{ 'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "() }");
         assertEquals(
                 Collections.singletonMap("base", BaseNTemplateNumberFormatFactory.INSTANCE),
-                cfg.getCustomNumberFormats());
+                cfgB.getCustomNumberFormats());
         
-        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
+        cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
                 "{ "
                 + "'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "(), "
                 + "'hex': " + HexTemplateNumberFormatFactory.class.getName() + "()"
@@ -1065,13 +1029,13 @@ public class ConfigurationTest extends TestCase {
                 ImmutableMap.of(
                         "base", BaseNTemplateNumberFormatFactory.INSTANCE,
                         "hex", HexTemplateNumberFormatFactory.INSTANCE),
-                cfg.getCustomNumberFormats());
+                cfgB.getCustomNumberFormats());
         
-        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY, "{}");
-        assertEquals(Collections.emptyMap(), cfg.getCustomNumberFormats());
+        cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY, "{}");
+        assertEquals(Collections.emptyMap(), cfgB.getCustomNumberFormats());
         
         try {
-            cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
+            cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
                     "{ 'x': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }");
             fail();
         } catch (ConfigurationException e) {
@@ -1083,34 +1047,34 @@ public class ConfigurationTest extends TestCase {
 
     @Test
     public void testSetTabSize() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
         String ftl = "${\t}";
         
         try {
-            new Template(null, ftl, cfg);
+            new Template(null, ftl, cfgB.build());
             fail();
         } catch (ParseException e) {
             assertEquals(9, e.getColumnNumber());
         }
         
-        cfg.setTabSize(1);
+        cfgB.setTabSize(1);
         try {
-            new Template(null, ftl, cfg);
+            new Template(null, ftl, cfgB.build());
             fail();
         } catch (ParseException e) {
             assertEquals(4, e.getColumnNumber());
         }
         
         try {
-            cfg.setTabSize(0);
+            cfgB.setTabSize(0);
             fail();
         } catch (IllegalArgumentException e) {
             // Expected
         }
         
         try {
-            cfg.setTabSize(257);
+            cfgB.setTabSize(257);
             fail();
         } catch (IllegalArgumentException e) {
             // Expected
@@ -1119,15 +1083,15 @@ public class ConfigurationTest extends TestCase {
 
     @Test
     public void testTabSizeSetting() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        assertEquals(8, cfg.getTabSize());
-        cfg.setSetting(Configuration.TAB_SIZE_KEY_CAMEL_CASE, "4");
-        assertEquals(4, cfg.getTabSize());
-        cfg.setSetting(Configuration.TAB_SIZE_KEY_SNAKE_CASE, "1");
-        assertEquals(1, cfg.getTabSize());
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+        assertEquals(8, cfgB.getTabSize());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TAB_SIZE_KEY_CAMEL_CASE, "4");
+        assertEquals(4, cfgB.getTabSize());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TAB_SIZE_KEY_SNAKE_CASE, "1");
+        assertEquals(1, cfgB.getTabSize());
         
         try {
-            cfg.setSetting(Configuration.TAB_SIZE_KEY_SNAKE_CASE, "x");
+            cfgB.setSetting(Configuration.ExtendableBuilder.TAB_SIZE_KEY_SNAKE_CASE, "x");
             fail();
         } catch (ConfigurationException e) {
             assertThat(e.getCause(), instanceOf(NumberFormatException.class));
@@ -1137,38 +1101,38 @@ public class ConfigurationTest extends TestCase {
     @SuppressFBWarnings(value="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS", justification="We test failures")
     @Test
     public void testSetCustomDateFormat() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
         try {
-            cfg.setCustomDateFormats(null);
+            cfgB.setCustomDateFormats(null);
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("null"));
         }
         
         try {
-            cfg.setCustomDateFormats(Collections.singletonMap("", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            cfgB.setCustomDateFormats(Collections.singletonMap("", EpochMillisTemplateDateFormatFactory.INSTANCE));
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("0 length"));
         }
 
         try {
-            cfg.setCustomDateFormats(Collections.singletonMap("a_b", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            cfgB.setCustomDateFormats(Collections.singletonMap("a_b", EpochMillisTemplateDateFormatFactory.INSTANCE));
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("a_b"));
         }
 
         try {
-            cfg.setCustomDateFormats(Collections.singletonMap("a b", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            cfgB.setCustomDateFormats(Collections.singletonMap("a b", EpochMillisTemplateDateFormatFactory.INSTANCE));
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("a b"));
         }
         
         try {
-            cfg.setCustomDateFormats(ImmutableMap.of(
+            cfgB.setCustomDateFormats(ImmutableMap.of(
                     "a", EpochMillisTemplateDateFormatFactory.INSTANCE,
                     "@wrong", EpochMillisTemplateDateFormatFactory.INSTANCE));
             fail();
@@ -1176,13 +1140,13 @@ public class ConfigurationTest extends TestCase {
             assertThat(e.getMessage(), containsString("@wrong"));
         }
         
-        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
+        cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
                 "{ 'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }");
         assertEquals(
                 Collections.singletonMap("epoch", EpochMillisTemplateDateFormatFactory.INSTANCE),
-                cfg.getCustomDateFormats());
+                cfgB.getCustomDateFormats());
         
-        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
+        cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
                 "{ "
                 + "'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "(), "
                 + "'epochDiv': " + EpochMillisDivTemplateDateFormatFactory.class.getName() + "()"
@@ -1191,13 +1155,13 @@ public class ConfigurationTest extends TestCase {
                 ImmutableMap.of(
                         "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
                         "epochDiv", EpochMillisDivTemplateDateFormatFactory.INSTANCE),
-                cfg.getCustomDateFormats());
+                cfgB.getCustomDateFormats());
         
-        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY, "{}");
-        assertEquals(Collections.emptyMap(), cfg.getCustomDateFormats());
+        cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY, "{}");
+        assertEquals(Collections.emptyMap(), cfgB.getCustomDateFormats());
         
         try {
-            cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
+            cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
                     "{ 'x': " + HexTemplateNumberFormatFactory.class.getName() + "() }");
             fail();
         } catch (ConfigurationException e) {
@@ -1208,112 +1172,116 @@ public class ConfigurationTest extends TestCase {
     }
 
     public void testNamingConventionSetSetting() throws ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0);
 
-        assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention());
+        assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention());
         
         cfg.setSetting("naming_convention", "legacy");
-        assertEquals(Configuration.LEGACY_NAMING_CONVENTION, cfg.getNamingConvention());
+        assertEquals(ParsingConfiguration.LEGACY_NAMING_CONVENTION, cfg.getNamingConvention());
         
         cfg.setSetting("naming_convention", "camel_case");
-        assertEquals(Configuration.CAMEL_CASE_NAMING_CONVENTION, cfg.getNamingConvention());
+        assertEquals(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION, cfg.getNamingConvention());
         
         cfg.setSetting("naming_convention", "auto_detect");
-        assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention());
+        assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention());
     }
 
     public void testLazyImportsSetSetting() throws ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-
-        assertFalse(cfg.getLazyImports());
-        assertTrue(cfg.isLazyImportsSet());
-        cfg.setSetting("lazy_imports", "true");
-        assertTrue(cfg.getLazyImports());
-        cfg.setSetting("lazyImports", "false");
-        assertFalse(cfg.getLazyImports());
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+
+        assertFalse(cfgB.getLazyImports());
+        assertFalse(cfgB.isLazyImportsSet());
+        cfgB.setSetting("lazy_imports", "true");
+        assertTrue(cfgB.getLazyImports());
+        cfgB.setSetting("lazyImports", "false");
+        assertFalse(cfgB.getLazyImports());
+        assertTrue(cfgB.isLazyImportsSet());
     }
     
     public void testLazyAutoImportsSetSetting() throws ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-
-        assertNull(cfg.getLazyAutoImports());
-        assertTrue(cfg.isLazyAutoImportsSet());
-        cfg.setSetting("lazy_auto_imports", "true");
-        assertEquals(Boolean.TRUE, cfg.getLazyAutoImports());
-        assertTrue(cfg.isLazyAutoImportsSet());
-        cfg.setSetting("lazyAutoImports", "false");
-        assertEquals(Boolean.FALSE, cfg.getLazyAutoImports());
-        cfg.setSetting("lazyAutoImports", "null");
-        assertNull(cfg.getLazyAutoImports());
-        assertTrue(cfg.isLazyAutoImportsSet());
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+
+        assertNull(cfgB.getLazyAutoImports());
+        assertFalse(cfgB.isLazyAutoImportsSet());
+        cfgB.setSetting("lazy_auto_imports", "true");
+        assertEquals(Boolean.TRUE, cfgB.getLazyAutoImports());
+        assertTrue(cfgB.isLazyAutoImportsSet());
+        cfgB.setSetting("lazyAutoImports", "false");
+        assertEquals(Boolean.FALSE, cfgB.getLazyAutoImports());
+        cfgB.setSetting("lazyAutoImports", "null");
+        assertNull(cfgB.getLazyAutoImports());
+        assertTrue(cfgB.isLazyAutoImportsSet());
+        cfgB.unsetLazyAutoImports();
+        assertNull(cfgB.getLazyAutoImports());
+        assertFalse(cfgB.isLazyAutoImportsSet());
     }
 
     public void testLocaleSetting() throws TemplateException, ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
 
-        assertEquals(Locale.getDefault(), cfg.getLocale());
-        assertFalse(cfg.isLocaleExplicitlySet());
+        assertEquals(Locale.getDefault(), cfgB.getLocale());
+        assertFalse(cfgB.isLocaleSet());
 
         Locale nonDefault = Locale.getDefault().equals(Locale.GERMANY) ? Locale.FRANCE : Locale.GERMANY;
-        cfg.setLocale(nonDefault);
-        assertTrue(cfg.isLocaleExplicitlySet());
-        assertEquals(nonDefault, cfg.getLocale());
+        cfgB.setLocale(nonDefault);
+        assertTrue(cfgB.isLocaleSet());
+        assertEquals(nonDefault, cfgB.getLocale());
 
-        cfg.unsetLocale();
-        assertEquals(Locale.getDefault(), cfg.getLocale());
-        assertFalse(cfg.isLocaleExplicitlySet());
+        cfgB.unsetLocale();
+        assertEquals(Locale.getDefault(), cfgB.getLocale());
+        assertFalse(cfgB.isLocaleSet());
 
-        cfg.setSetting(Configuration.LOCALE_KEY, "JVM default");
-        assertEquals(Locale.getDefault(), cfg.getLocale());
-        assertTrue(cfg.isLocaleExplicitlySet());
+        cfgB.setSetting(Configuration.ExtendableBuilder.LOCALE_KEY, "JVM default");
+        assertEquals(Locale.getDefault(), cfgB.getLocale());
+        assertTrue(cfgB.isLocaleSet());
     }
 
     public void testDefaultEncodingSetting() throws TemplateException, ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
 
-        assertEquals(Charset.defaultCharset(), cfg.getSourceEncoding());
-        assertFalse(cfg.isSourceEncodingExplicitlySet());
+        assertEquals(Charset.defaultCharset(), cfgB.getSourceEncoding());
+        assertFalse(cfgB.isSourceEncodingSet());
 
         Charset nonDefault = Charset.defaultCharset().equals(StandardCharsets.UTF_8) ? StandardCharsets.ISO_8859_1
                 : StandardCharsets.UTF_8;
-        cfg.setSourceEncoding(nonDefault);
-        assertTrue(cfg.isSourceEncodingExplicitlySet());
-        assertEquals(nonDefault, cfg.getSourceEncoding());
+        cfgB.setSourceEncoding(nonDefault);
+        assertTrue(cfgB.isSourceEncodingSet());
+        assertEquals(nonDefault, cfgB.getSourceEncoding());
 
-        cfg.unsetSourceEncoding();
-        assertEquals(Charset.defaultCharset(), cfg.getSourceEncoding());
-        assertFalse(cfg.isSourceEncodingExplicitlySet());
+        cfgB.unsetSourceEncoding();
+        assertEquals(Charset.defaultCharset(), cfgB.getSourceEncoding());
+        assertFalse(cfgB.isSourceEncodingSet());
 
-        cfg.setSetting(Configuration.SOURCE_ENCODING_KEY, "JVM default");
-        assertEquals(Charset.defaultCharset(), cfg.getSourceEncoding());
-        assertTrue(cfg.isSourceEncodingExplicitlySet());
+        cfgB.setSetting(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY, "JVM default");
+        assertEquals(Charset.defaultCharset(), cfgB.getSourceEncoding());
+        assertTrue(cfgB.isSourceEncodingSet());
     }
 
     public void testTimeZoneSetting() throws TemplateException, ConfigurationException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
 
-        assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
-        assertFalse(cfg.isTimeZoneExplicitlySet());
+        assertEquals(TimeZone.getDefault(), cfgB.getTimeZone());
+        assertFalse(cfgB.isTimeZoneSet());
 
         TimeZone nonDefault = TimeZone.getDefault().equals(_DateUtil.UTC) ? TimeZone.getTimeZone("PST") : _DateUtil.UTC;
-        cfg.setTimeZone(nonDefault);
-        assertTrue(cfg.isTimeZoneExplicitlySet());
-        assertEquals(nonDefault, cfg.getTimeZone());
+        cfgB.setTimeZone(nonDefault);
+        assertTrue(cfgB.isTimeZoneSet());
+        assertEquals(nonDefault, cfgB.getTimeZone());
 
-        cfg.unsetTimeZone();
-        assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
-        assertFalse(cfg.isTimeZoneExplicitlySet());
+        cfgB.unsetTimeZone();
+        assertEquals(TimeZone.getDefault(), cfgB.getTimeZone());
+        assertFalse(cfgB.isTimeZoneSet());
 
-        cfg.setSetting(Configuration.TIME_ZONE_KEY, "JVM default");
-        assertEquals(TimeZone.getDefault(), cfg.getTimeZone());
-        assertTrue(cfg.isTimeZoneExplicitlySet());
+        cfgB.setSetting(Configuration.ExtendableBuilder.TIME_ZONE_KEY, "JVM default");
+        assertEquals(TimeZone.getDefault(), cfgB.getTimeZone());
+        assertTrue(cfgB.isTimeZoneSet());
     }
 
     @Test
     public void testGetSettingNamesAreSorted() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
         for (boolean camelCase : new boolean[] { false, true }) {
-            List<String> names = new ArrayList<>(cfg.getSettingNames(camelCase));
+            List<String> names = new ArrayList<>(Configuration.Builder.getSettingNames(camelCase));
             List<String> procCfgNames = new ArrayList<>(new Template(null, "", cfg)
                     .createProcessingEnvironment(null, _NullWriter.INSTANCE)
                     .getSettingNames(camelCase));
@@ -1331,18 +1299,20 @@ public class ConfigurationTest extends TestCase {
     }
 
     @Test
+    @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE")
     public void testGetSettingNamesNameConventionsContainTheSame() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame(
-                new ArrayList<>(cfg.getSettingNames(false)),
-                new ArrayList<>(cfg.getSettingNames(true)));
+                new ArrayList<>(cfgB.getSettingNames(false)),
+                new ArrayList<>(cfgB.getSettingNames(true)));
     }
 
     @Test
+    @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE")
     public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        List<String> names = new ArrayList<>(cfg.getSettingNames(false));
-        List<String> cfgableNames = new ArrayList<>(cfg.getSettingNames(false));
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+        List<String> names = new ArrayList<>(cfgB.getSettingNames(false));
+        List<String> cfgableNames = new ArrayList<>(cfgB.getSettingNames(false));
         assertStartsWith(names, cfgableNames);
         
         for (int i = cfgableNames.size(); i < names.size(); i++) {
@@ -1353,8 +1323,7 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Collection<String> names = cfg.getSettingNames(false);
+        Collection<String> names = new Configuration.Builder(Configuration.VERSION_3_0_0).getSettingNames(false);
         
         for (Class<? extends MutableProcessingConfiguration> cfgableClass : new Class[] { Configuration.class, MutableProcessingConfiguration.class }) {
             for (Field f : cfgableClass.getFields()) {
@@ -1368,13 +1337,12 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException {
-        ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configuration.class);
+        ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configuration.ExtendableBuilder.class);
     }
 
     @Test
     public void testGetSettingNamesCoversAllSettingNames() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Collection<String> names = cfg.getSettingNames(false);
+        Collection<String> names = new Configuration.Builder(Configuration.VERSION_3_0_0).getSettingNames(false);
         
         for (Field f : MutableProcessingConfiguration.class.getFields()) {
             if (f.getName().endsWith("_KEY")) {
@@ -1386,18 +1354,18 @@ public class ConfigurationTest extends TestCase {
 
     @Test
     public void testSetSettingSupportsBothNamingConventions() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         
-        cfg.setSetting(Configuration.SOURCE_ENCODING_KEY_CAMEL_CASE, StandardCharsets.UTF_16LE.name());
-        assertEquals(StandardCharsets.UTF_16LE, cfg.getSourceEncoding());
-        cfg.setSetting(Configuration.SOURCE_ENCODING_KEY_SNAKE_CASE, StandardCharsets.UTF_8.name());
-        assertEquals(StandardCharsets.UTF_8, cfg.getSourceEncoding());
+        cfgB.setSetting(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY_CAMEL_CASE, StandardCharsets.UTF_16LE.name());
+        assertEquals(StandardCharsets.UTF_16LE, cfgB.getSourceEncoding());
+        cfgB.setSetting(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY_SNAKE_CASE, StandardCharsets.UTF_8.name());
+        assertEquals(StandardCharsets.UTF_8, cfgB.getSourceEncoding());
         
-        for (String nameCC : cfg.getSettingNames(true)) {
+        for (String nameCC : cfgB.getSettingNames(true)) {
             for (String value : new String[] { "1", "default", "true" }) {
                 Exception resultCC = null;
                 try {
-                    cfg.setSetting(nameCC, value);
+                    cfgB.setSetting(nameCC, value);
                 } catch (Exception e) {
                     assertThat(e, not(instanceOf(UnknownConfigurationSettingException.class)));
                     resultCC = e;
@@ -1406,7 +1374,7 @@ public class ConfigurationTest extends TestCase {
                 String nameSC = _StringUtil.camelCaseToUnderscored(nameCC);
                 Exception resultSC = null;
                 try {
-                    cfg.setSetting(nameSC, value);
+                    cfgB.setSetting(nameSC, value);
                 } catch (Exception e) {
                     assertThat(e, not(instanceOf(UnknownConfigurationSettingException.class)));
                     resultSC = e;
@@ -1424,11 +1392,11 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testGetSupportedBuiltInDirectiveNames() {
-        Configuration cfg = new Configuration();
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
         
-        Set<String> allNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.AUTO_DETECT_NAMING_CONVENTION);
-        Set<String> lNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.LEGACY_NAMING_CONVENTION);
-        Set<String> cNames = cfg.getSupportedBuiltInDirectiveNames(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        Set<String> allNames = cfg.getSupportedBuiltInDirectiveNames(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION);
+        Set<String> lNames = cfg.getSupportedBuiltInDirectiveNames(ParsingConfiguration.LEGACY_NAMING_CONVENTION);
+        Set<String> cNames = cfg.getSupportedBuiltInDirectiveNames(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
         
         checkNamingConventionNameSets(allNames, lNames, cNames);
         
@@ -1439,11 +1407,11 @@ public class ConfigurationTest extends TestCase {
 
     @Test
     public void testGetSupportedBuiltInNames() {
-        Configuration cfg = new Configuration();
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
         
-        Set<String> allNames = cfg.getSupportedBuiltInNames(Configuration.AUTO_DETECT_NAMING_CONVENTION);
-        Set<String> lNames = cfg.getSupportedBuiltInNames(Configuration.LEGACY_NAMING_CONVENTION);
-        Set<String> cNames = cfg.getSupportedBuiltInNames(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        Set<String> allNames = cfg.getSupportedBuiltInNames(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION);
+        Set<String> lNames = cfg.getSupportedBuiltInNames(ParsingConfiguration.LEGACY_NAMING_CONVENTION);
+        Set<String> cNames = cfg.getSupportedBuiltInNames(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION);
         
         checkNamingConventionNameSets(allNames, lNames, cNames);
     }
@@ -1464,15 +1432,15 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testRemovedSettings() {
-        Configuration cfg = new Configuration();
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
         try {
-            cfg.setSetting("classic_compatible", "true");
+            cfgB.setSetting("classic_compatible", "true");
             fail();
         } catch (ConfigurationException e) {
             assertThat(e.getMessage(), allOf(containsString("removed"), containsString("3.0.0")));
         }
         try {
-            cfg.setSetting("strict_syntax", "true");
+            cfgB.setSetting("strict_syntax", "true");
             fail();
         } catch (ConfigurationException e) {
             assertThat(e.getMessage(), allOf(containsString("removed"), containsString("3.0.0")));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 00771cd..726a20c 100644
--- a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
+++ b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
@@ -101,7 +101,7 @@ public class CustomAttributeTest {
                 + "'" + KEY_1 + "': [ 's', 2, true, {  'a': 'A' } ], "
                 + "'" + KEY_2 + "': " + VALUE_BIGDECIMAL + " "
                 + "}>",
-                new Configuration(Configuration.VERSION_3_0_0));
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build());
 
         assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet());
         assertEquals(VALUE_LIST, t.getCustomAttribute(KEY_1));
@@ -126,7 +126,7 @@ public class CustomAttributeTest {
                 + "'" + KEY_2 + "': 'b', "
                 + "'" + KEY_3 + "': 'c' "
                 + "}>",
-                new Configuration(Configuration.VERSION_3_0_0),
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build(),
                 tcb.build());
 
         assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet());
@@ -147,7 +147,7 @@ public class CustomAttributeTest {
         tcb.setCustomAttribute(KEY_3, VALUE_3);
         tcb.setCustomAttribute(KEY_4, VALUE_4);
         Template t = new Template(null, "",
-                new Configuration(Configuration.VERSION_3_0_0),
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build(),
                 tcb.build());
 
         assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomAttributes().keySet());



[10/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 fa5d5d4..df15c0f 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -19,18 +19,16 @@
 
 package org.apache.freemarker.core;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Serializable;
 import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -44,14 +42,11 @@ import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.outputformat.OutputFormat;
@@ -74,17 +69,15 @@ import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
-import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateResolver;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
-import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.MruCacheStorage;
-import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
-import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CaptureOutput;
+import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util.HtmlEscape;
 import org.apache.freemarker.core.util.NormalizeNewlines;
 import org.apache.freemarker.core.util.StandardCompress;
@@ -97,27 +90,35 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
 /**
  * <b>The main entry point into the FreeMarker API</b>; encapsulates the configuration settings of FreeMarker,
  * also serves as a central template-loading and caching service.
  *
- * <p>This class is meant to be used in a singleton pattern. That is, you invoke an instance of this at the beginning of
- * the application life-cycle, set its {@link #setSetting(String, String) configuration settings} there (either with the
- * setter methods like {@link #setTemplateLoader(TemplateLoader)} or by loading a {@code .properties} file), and then
+ * <p>This class is meant to be used in a singleton pattern. That is, you create an instance of this at the beginning of
+ * the application life-cycle with {@link Configuration.Builder}, set its settings
+ * (either
+ * with
+ * the
+ * setter methods like {@link Configuration.Builder#setTemplateLoader(TemplateLoader)} or by loading a
+ * {@code .properties} file and use that with {@link Configuration.Builder#setSettings(Properties)}}), and
+ * then
  * use that single instance everywhere in your application. Frequently re-creating {@link Configuration} is a typical
- * and grave mistake from performance standpoint, as the {@link Configuration} holds the template templateResolver, and often also
- * the class introspection templateResolver, which then will be lost. (Note that, naturally, having multiple long-lived instances,
+ * and grave mistake from performance standpoint, as the {@link Configuration} holds the template cache, and often also
+ * the class introspection cache, which then will be lost. (Note that, naturally, having multiple long-lived instances,
  * like one per component that internally uses FreeMarker is fine.)  
  * 
  * <p>The basic usage pattern is like:
  * 
  * <pre>
  *  // Where the application is initialized; in general you do this ONLY ONCE in the application life-cycle!
- *  Configuration cfg = new Configuration(VERSION_<i>X</i>_<i>Y</i>_<i>Z</i>));
- *  // Where X, Y, Z enables the not-100%-backward-compatible fixes introduced in
- *  // FreeMarker version X.Y.Z  and earlier (see {@link #Configuration(Version)}).
- *  cfg.set<i>SomeSetting</i>(...);
- *  cfg.set<i>OtherSetting</i>(...);
+ *  Configuration cfg = new Configuration.Builder(VERSION_<i>X</i>_<i>Y</i>_<i>Z</i>));
+ *          .<i>someSetting</i>(...)
+ *          .<i>otherSetting</i>(...)
+ *          .build()
+ *  // VERSION_<i>X</i>_<i>Y</i>_<i>Z</i> enables the not-100%-backward-compatible fixes introduced in
+ *  // FreeMarker version X.Y.Z and earlier (see {@link Configuration#getIncompatibleImprovements()}).
  *  ...
  *  
  *  // Later, whenever the application needs a template (so you may do this a lot, and from multiple threads):
@@ -126,205 +127,66 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * 
  * <p>A couple of settings that you should not leave on its default value are:
  * <ul>
- *   <li>{@link #setTemplateLoader(TemplateLoader) template_loader}: The default value is deprecated and in fact quite
- *       useless. (For the most common cases you can use the convenience methods,
- *       {@link #setDirectoryForTemplateLoading(File)} and {@link #setClassForTemplateLoading(Class, String)} and
- *       {@link #setClassLoaderForTemplateLoading(ClassLoader, String)} too.)
- *   <li>{@link #setSourceEncoding(Charset) sourceEncoding}: The default value is system dependent, which makes it
+ *   <li>{@link #getTemplateLoader templateLoader}: The default value is {@code null}, so you won't be able to load
+ *       anything.
+ *   <li>{@link #getSourceEncoding sourceEncoding}: The default value is system dependent, which makes it
  *       fragile on servers, so it should be set explicitly, like to "UTF-8" nowadays. 
- *   <li>{@link #setTemplateExceptionHandler(TemplateExceptionHandler) template_exception_handler}: For developing
+ *   <li>{@link #getTemplateExceptionHandler() templateExceptionHandler}: For developing
  *       HTML pages, the most convenient value is {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}. For production,
  *       {@link TemplateExceptionHandler#RETHROW_HANDLER} is safer to use.
- *   <!-- 2.4: recommend the new object wrapper here -->
  * </ul>
  * 
- * <p>A {@link Configuration} object is thread-safe only after you have stopped modifying the configuration settings,
- * and you have <b>safely published</b> it (see JSR 133 and related literature) to other threads. Generally, you set
- * everything directly after you have instantiated the {@link Configuration} object, then you don't change the settings
- * anymore, so then it's safe to make it accessible (again, via a "safe publication" technique) from multiple threads.
- * The methods that aren't for modifying settings, like {@link #getTemplate(String)}, are thread-safe.
+ * <p>{@link Configuration} is thread-safe and (as of 3.0.0) immutable (apart from internal caches).
  */
-public final class Configuration extends MutableProcessingConfiguration<Configuration>
-        implements Cloneable, ParsingAndProcessingConfiguration, CustomStateScope {
+public final class Configuration
+        implements TopLevelConfiguration, CustomStateScope {
     
     private static final String VERSION_PROPERTIES_PATH = "org/apache/freemarker/core/version.properties";
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String SOURCE_ENCODING_KEY_SNAKE_CASE = "source_encoding";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String SOURCE_ENCODING_KEY_CAMEL_CASE = "sourceEncoding";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String SOURCE_ENCODING_KEY = SOURCE_ENCODING_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String LOCALIZED_LOOKUP_KEY_SNAKE_CASE = "localized_lookup";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String LOCALIZED_LOOKUP_KEY_CAMEL_CASE = "localizedLookup";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String LOCALIZED_LOOKUP_KEY = LOCALIZED_LOOKUP_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String WHITESPACE_STRIPPING_KEY_SNAKE_CASE = "whitespace_stripping";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String WHITESPACE_STRIPPING_KEY_CAMEL_CASE = "whitespaceStripping";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String WHITESPACE_STRIPPING_KEY = WHITESPACE_STRIPPING_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.24 */
-    public static final String OUTPUT_FORMAT_KEY_SNAKE_CASE = "output_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.24 */
-    public static final String OUTPUT_FORMAT_KEY_CAMEL_CASE = "outputFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String OUTPUT_FORMAT_KEY = OUTPUT_FORMAT_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.24 */
-    public static final String RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE = "recognize_standard_file_extensions";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.24 */
-    public static final String RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE = "recognizeStandardFileExtensions";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY
-            = RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.24 */
-    public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE = "registered_custom_output_formats";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.24 */
-    public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE = "registeredCustomOutputFormats";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY = REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.24 */
-    public static final String AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE = "auto_escaping_policy";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.24 */
-    public static final String AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE = "autoEscapingPolicy";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String AUTO_ESCAPING_POLICY_KEY = AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String CACHE_STORAGE_KEY_SNAKE_CASE = "cache_storage";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String CACHE_STORAGE_KEY_CAMEL_CASE = "cacheStorage";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String CACHE_STORAGE_KEY = CACHE_STORAGE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE = "template_update_delay";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE = "templateUpdateDelay";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TEMPLATE_UPDATE_DELAY_KEY = TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String AUTO_INCLUDE_KEY_SNAKE_CASE = "auto_include";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String AUTO_INCLUDE_KEY_CAMEL_CASE = "autoInclude";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String AUTO_INCLUDE_KEY = AUTO_INCLUDE_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_LANGUAGE_KEY_SNAKE_CASE = "template_language";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_LANGUAGE_KEY_CAMEL_CASE = "templateLanguage";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TEMPLATE_LANGUAGE_KEY = TEMPLATE_LANGUAGE_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TAG_SYNTAX_KEY_SNAKE_CASE = "tag_syntax";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TAG_SYNTAX_KEY_CAMEL_CASE = "tagSyntax";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TAG_SYNTAX_KEY = TAG_SYNTAX_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String NAMING_CONVENTION_KEY_SNAKE_CASE = "naming_convention";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String NAMING_CONVENTION_KEY_CAMEL_CASE = "namingConvention";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String NAMING_CONVENTION_KEY = NAMING_CONVENTION_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
-    public static final String TAB_SIZE_KEY_SNAKE_CASE = "tab_size";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
-    public static final String TAB_SIZE_KEY_CAMEL_CASE = "tabSize";
-    /** Alias to the {@code ..._SNAKE_CASE} variation. @since 2.3.25 */
-    public static final String TAB_SIZE_KEY = TAB_SIZE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_LOADER_KEY_SNAKE_CASE = "template_loader";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_LOADER_KEY_CAMEL_CASE = "templateLoader";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TEMPLATE_LOADER_KEY = TEMPLATE_LOADER_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE = "template_lookup_strategy";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE = "templateLookupStrategy";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TEMPLATE_LOOKUP_STRATEGY_KEY = TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE = "template_name_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE = "templateNameFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TEMPLATE_NAME_FORMAT_KEY = TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.24 */
-    public static final String TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE = "template_configurations";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.24 */
-    public static final String TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE = "templateConfigurations";
-    /** Alias to the {@code ..._SNAKE_CASE} variation. @since 2.3.24 */
-    public static final String TEMPLATE_CONFIGURATIONS_KEY = TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE = "incompatible_improvements";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE = "incompatibleImprovements";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String INCOMPATIBLE_IMPROVEMENTS_KEY = INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE;
-    
+
     private static final String[] SETTING_NAMES_SNAKE_CASE = new String[] {
         // Must be sorted alphabetically!
-        AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE,
-        CACHE_STORAGE_KEY_SNAKE_CASE,
-        INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
-        LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
-        NAMING_CONVENTION_KEY_SNAKE_CASE,
-        OUTPUT_FORMAT_KEY_SNAKE_CASE,
-        RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE,
-        REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE,
-        SOURCE_ENCODING_KEY_SNAKE_CASE,
-        TAB_SIZE_KEY_SNAKE_CASE,
-        TAG_SYNTAX_KEY_SNAKE_CASE,
-        TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE,
-        TEMPLATE_LANGUAGE_KEY_SNAKE_CASE,
-        TEMPLATE_LOADER_KEY_SNAKE_CASE,
-        TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE,
-        TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE,
-        TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE,
-        WHITESPACE_STRIPPING_KEY_SNAKE_CASE,
+        ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE,
+        ExtendableBuilder.CACHE_STORAGE_KEY_SNAKE_CASE,
+        ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
+        ExtendableBuilder.LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
+        ExtendableBuilder.NAMING_CONVENTION_KEY_SNAKE_CASE,
+        ExtendableBuilder.OUTPUT_FORMAT_KEY_SNAKE_CASE,
+        ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE,
+        ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE,
+        ExtendableBuilder.SHARED_VARIABLES_KEY_SNAKE_CASE,
+        ExtendableBuilder.SOURCE_ENCODING_KEY_SNAKE_CASE,
+        ExtendableBuilder.TAB_SIZE_KEY_SNAKE_CASE,
+        ExtendableBuilder.TAG_SYNTAX_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_LANGUAGE_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_LOADER_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE,
+        ExtendableBuilder.WHITESPACE_STRIPPING_KEY_SNAKE_CASE,
     };
 
     private static final String[] SETTING_NAMES_CAMEL_CASE = new String[] {
         // Must be sorted alphabetically!
-        AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE,
-        CACHE_STORAGE_KEY_CAMEL_CASE,
-        INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
-        LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
-        NAMING_CONVENTION_KEY_CAMEL_CASE,
-        OUTPUT_FORMAT_KEY_CAMEL_CASE,
-        RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE,
-        REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
-        SOURCE_ENCODING_KEY_CAMEL_CASE,
-        TAB_SIZE_KEY_CAMEL_CASE,
-        TAG_SYNTAX_KEY_CAMEL_CASE,
-        TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE,
-        TEMPLATE_LANGUAGE_KEY_CAMEL_CASE,
-        TEMPLATE_LOADER_KEY_CAMEL_CASE,
-        TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE,
-        TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE,
-        TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE,
-        WHITESPACE_STRIPPING_KEY_CAMEL_CASE
+        ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE,
+        ExtendableBuilder.CACHE_STORAGE_KEY_CAMEL_CASE,
+        ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
+        ExtendableBuilder.LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
+        ExtendableBuilder.NAMING_CONVENTION_KEY_CAMEL_CASE,
+        ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE,
+        ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE,
+        ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
+        ExtendableBuilder.SHARED_VARIABLES_KEY_CAMEL_CASE,
+        ExtendableBuilder.SOURCE_ENCODING_KEY_CAMEL_CASE,
+        ExtendableBuilder.TAB_SIZE_KEY_CAMEL_CASE,
+        ExtendableBuilder.TAG_SYNTAX_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_LANGUAGE_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_LOADER_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE,
+        ExtendableBuilder.WHITESPACE_STRIPPING_KEY_CAMEL_CASE
     };
     
     private static final Map<String, OutputFormat> STANDARD_OUTPUT_FORMATS;
@@ -340,29 +202,8 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
         STANDARD_OUTPUT_FORMATS.put(JavaScriptOutputFormat.INSTANCE.getName(), JavaScriptOutputFormat.INSTANCE);
         STANDARD_OUTPUT_FORMATS.put(JSONOutputFormat.INSTANCE.getName(), JSONOutputFormat.INSTANCE);
     }
-    
-    public static final int AUTO_DETECT_TAG_SYNTAX = 0;
-    public static final int ANGLE_BRACKET_TAG_SYNTAX = 1;
-    public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
-
-    public static final int AUTO_DETECT_NAMING_CONVENTION = 10;
-    public static final int LEGACY_NAMING_CONVENTION = 11;
-    public static final int CAMEL_CASE_NAMING_CONVENTION = 12;
 
-    /**
-     * Don't enable auto-escaping, regardless of what the {@link OutputFormat} is. Note that a {@code 
-     * <#ftl auto_esc=true>} in the template will override this.
-     */
-    public static final int DISABLE_AUTO_ESCAPING_POLICY = 20;
-    /**
-     * Enable auto-escaping if the output format supports it and {@link MarkupOutputFormat#isAutoEscapedByDefault()} is
-     * {@code true}.
-     */
-    public static final int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 21;
-    /** Enable auto-escaping if the {@link OutputFormat} supports it. */
-    public static final int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 22;
-    
-    /** FreeMarker version 3.0.0 (an {@link #Configuration(Version) incompatible improvements break-point}) */
+    /** FreeMarker version 3.0.0 */
     public static final Version VERSION_3_0_0 = new Version(3, 0, 0);
     
     /** The default of {@link #getIncompatibleImprovements()}, currently {@link #VERSION_3_0_0}. */
@@ -406,402 +247,298 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
             throw new RuntimeException("Failed to load and parse " + VERSION_PROPERTIES_PATH, e);
         }
     }
-    
-    private volatile boolean localizedLookup = true;
-    private boolean whitespaceStripping = true;
-    private int autoEscapingPolicy = ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY;
-    private OutputFormat outputFormat = UndefinedOutputFormat.INSTANCE;
-    private boolean outputFormatExplicitlySet;
-    private Boolean recognizeStandardFileExtensions;
-    private Map<String, ? extends OutputFormat> registeredCustomOutputFormats = Collections.emptyMap(); 
-    private Version incompatibleImprovements;
-    private TemplateLanguage templateLanguage = TemplateLanguage.FTL;
-    private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX;
-    private int namingConvention = AUTO_DETECT_NAMING_CONVENTION;
-    private int tabSize = 8;  // Default from JavaCC 3.x 
-
-    private DefaultTemplateResolver templateResolver;
-    
-    private boolean templateLoaderExplicitlySet;
-    private boolean templateLookupStrategyExplicitlySet;
-    private boolean templateNameFormatExplicitlySet;
-    private boolean cacheStorageExplicitlySet;
-    
-    private boolean objectWrapperExplicitlySet;
-    private boolean templateExceptionHandlerExplicitlySet;
-    private boolean logTemplateExceptionsExplicitlySet;
-    private boolean localeExplicitlySet;
-    private boolean sourceEncodingExplicitlySet;
-    private boolean timeZoneExplicitlySet;
 
-    private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap();
+    // Configuration-specific settings:
+
+    private final Version incompatibleImprovements;
+    private final DefaultTemplateResolver templateResolver;
+    private final boolean localizedLookup;
+    private final List<OutputFormat> registeredCustomOutputFormats;
+    private final Map<String, OutputFormat> registeredCustomOutputFormatsByName;
+    private final Map<String, Object> sharedVariables;
+    private final Map<String, TemplateModel> wrappedSharedVariables;
+
+    // ParsingConfiguration settings:
+
+    private final TemplateLanguage templateLanguage;
+    private final int tagSyntax;
+    private final int namingConvention;
+    private final boolean whitespaceStripping;
+    private final int autoEscapingPolicy;
+    private final OutputFormat outputFormat;
+    private final Boolean recognizeStandardFileExtensions;
+    private final int tabSize;
+    private final Charset sourceEncoding;
+
+    // ProcessingConfiguration settings:
+
+    private final Locale locale;
+    private final String numberFormat;
+    private final String timeFormat;
+    private final String dateFormat;
+    private final String dateTimeFormat;
+    private final TimeZone timeZone;
+    private final TimeZone sqlDateAndTimeTimeZone;
+    private final String booleanFormat;
+    private final TemplateExceptionHandler templateExceptionHandler;
+    private final ArithmeticEngine arithmeticEngine;
+    private final ObjectWrapper objectWrapper;
+    private final Charset outputEncoding;
+    private final Charset urlEscapingCharset;
+    private final Boolean autoFlush;
+    private final TemplateClassResolver newBuiltinClassResolver;
+    private final Boolean showErrorTips;
+    private final Boolean apiBuiltinEnabled;
+    private final Boolean logTemplateExceptions;
+    private final Map<String, TemplateDateFormatFactory> customDateFormats;
+    private final Map<String, TemplateNumberFormatFactory> customNumberFormats;
+    private final Map<String, String> autoImports;
+    private final List<String> autoIncludes;
+    private final Boolean lazyImports;
+    private final Boolean lazyAutoImports;
+    private final Map<Object, Object> customAttributes;
+
+    // CustomStateScope:
 
     private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0);
     private final Object customStateMapLock = new Object();
 
-    /**
-     * Needed so that it doesn't mater in what order do you call {@link #setSharedVariables(Map)}
-     * and {@link #setObjectWrapper(ObjectWrapper)}. When the user configures FreeMarker from Spring XML, he has no
-     * control over the order, so it has to work on both ways.
-     */
-    private HashMap<String, Object> rewrappableSharedVariables = null;
-    
-    private Charset sourceEncoding = getDefaultSourceEncoding();
-
-    /**
-     * @deprecated Use {@link #Configuration(Version)} instead. Note that the version can be still modified later with
-     *     {@link Configuration#setIncompatibleImprovements(Version)} (or
-     *     {@link Configuration#setSettings(Properties)}).  
-     */
-    @Deprecated
-    public Configuration() {
-        this(DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
-    }
+    private <SelfT extends ExtendableBuilder<SelfT>> Configuration(ExtendableBuilder<SelfT> builder)
+            throws ConfigurationException {
+        // Configuration-specific settings:
 
-    /**
-     * Creates a new instance and sets which of the non-backward-compatible bugfixes/improvements should be enabled.
-     * Note that the specified versions corresponds to the {@code incompatible_improvements} configuration setting, and
-     * can be changed later, with {@link #setIncompatibleImprovements(Version)} for example. 
-     *
-     * <p><b>About the "incompatible improvements" setting</b>
-     *
-     * <p>This setting value is the FreeMarker version number where the not 100% backward compatible bug fixes and
-     * improvements that you want to enable were already implemented. In new projects you should set this to the
-     * FreeMarker version that you are actually using. In older projects it's also usually better to keep this high,
-     * however you better check the changes activated (find them below), at least if not only the 3rd version number
-     * (the micro version) of {@code incompatibleImprovements} is increased. Generally, as far as you only increase the
-     * last version number of this setting, the changes are always low risk. The default value is 2.3.0 to maximize
-     * backward compatibility, but that value isn't recommended.
-     * 
-     * <p>Bugfixes and improvements that are fully backward compatible, also those that are important security fixes,
-     * are enabled regardless of the incompatible improvements setting.
-     * 
-     * <p>An important consequence of setting this setting is that now your application will check if the stated minimum
-     * FreeMarker version requirement is met. Like if you set this setting to 2.3.22, but accidentally the application
-     * is deployed with FreeMarker 2.3.21, then FreeMarker will fail, telling that a higher version is required. After
-     * all, the fixes/improvements you have requested aren't available on a lower version.
-     * 
-     * <p>Note that as FreeMarker's minor (2nd) or major (1st) version number increments, it's possible that emulating
-     * some of the old bugs will become unsupported, that is, even if you set this setting to a low value, it silently
-     * wont bring back the old behavior anymore. Information about that will be present here.
-     * 
-     * <p>Currently the effects of this setting are:
-     * <ul>
-     *   <li><p>
-     *     3.0.0: This is the lowest supported value in FreeMarker 3.
-     *   </li>
-     * </ul>
-     * 
-     * @throws IllegalArgumentException
-     *             If {@code incompatibleImmprovements} refers to a version that wasn't released yet when the currently
-     *             used FreeMarker version was released, or is less than 3.0.0, or is {@code null}.
-     * 
-     * @since 2.3.21
-     */
-    public Configuration(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
-        
-        _NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
-        this.incompatibleImprovements = incompatibleImprovements;
-        
-        createTemplateResolver();
-        loadBuiltInSharedVariables();
-    }
+        incompatibleImprovements = builder.getIncompatibleImprovements();
 
-    private void createTemplateResolver() {
         templateResolver = new DefaultTemplateResolver(
-                null,
-                getDefaultCacheStorage(),
-                getDefaultTemplateLookupStrategy(),
-                getDefaultTemplateNameFormat(),
-                null,
+                builder.getTemplateLoader(),
+                builder.getCacheStorage(), builder.getTemplateUpdateDelayMilliseconds(),
+                builder.getTemplateLookupStrategy(), builder.getLocalizedLookup(),
+                builder.getTemplateNameFormat(),
+                builder.getTemplateConfigurations(),
                 this);
-        templateResolver.clearTemplateCache(); // for fully BC behavior
-        templateResolver.setTemplateUpdateDelayMilliseconds(5000);
-    }
-
-    @Override
-    public TemplateExceptionHandler getTemplateExceptionHandler() {
-        return super.getTemplateExceptionHandler();
-    }
 
-    @Override
-    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
-        throw new BugException("Missing property value");
-    }
+        localizedLookup = builder.getLocalizedLookup();
+
+        {
+            Collection<OutputFormat> registeredCustomOutputFormats = builder.getRegisteredCustomOutputFormats();
+
+            _NullArgumentException.check(registeredCustomOutputFormats);
+            Map<String, OutputFormat> registeredCustomOutputFormatsByName = new LinkedHashMap<>(
+                    registeredCustomOutputFormats.size() * 4 / 3, 1f);
+            for (OutputFormat outputFormat : registeredCustomOutputFormats) {
+                String name = outputFormat.getName();
+                if (name.equals(UndefinedOutputFormat.INSTANCE.getName())) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The \"" + name + "\" output format can't be redefined",
+                            null);
+                }
+                if (name.equals(PlainTextOutputFormat.INSTANCE.getName())) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The \"" + name + "\" output format can't be redefined",
+                            null);
+                }
+                if (name.length() == 0) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't be 0 long",
+                            null);
+                }
+                if (!Character.isLetterOrDigit(name.charAt(0))) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name must start with letter or digit: " + name,
+                            null);
+                }
+                if (name.indexOf('+') != -1) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't contain \"+\" character: " + name,
+                            null);
+                }
+                if (name.indexOf('{') != -1) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't contain \"{\" character: " + name,
+                            null);
+                }
+                if (name.indexOf('}') != -1) {
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't contain \"}\" character: " + name,
+                            null);
+                }
 
-    @Override
-    protected ArithmeticEngine getInheritedArithmeticEngine() {
-        throw new BugException("Missing property value");
-    }
+                OutputFormat replaced = registeredCustomOutputFormatsByName.put(outputFormat.getName(), outputFormat);
+                if (replaced != null) {
+                    if (replaced == outputFormat) {
+                        throw new IllegalArgumentException(
+                                "Duplicate output format in the collection: " + outputFormat);
+                    }
+                    throw new ConfigurationSettingValueException(
+                            ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "Clashing output format names between " + replaced + " and " + outputFormat + ".",
+                            null);
+                }
+            }
 
-    private void recreateTemplateResolverWith(
-            TemplateLoader loader, CacheStorage storage,
-            TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,
-            TemplateConfigurationFactory templateConfigurations) {
-        DefaultTemplateResolver oldCache = templateResolver;
-        templateResolver = new DefaultTemplateResolver(
-                loader, storage, templateLookupStrategy, templateNameFormat, templateConfigurations, this);
-        templateResolver.clearTemplateCache(false);
-        templateResolver.setTemplateUpdateDelayMilliseconds(oldCache.getTemplateUpdateDelayMilliseconds());
-        templateResolver.setLocalizedLookup(localizedLookup);
-    }
-    
-    private void recreateTemplateResolver() {
-        recreateTemplateResolverWith(templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(),
-                templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(),
-                getTemplateConfigurations());
-    }
-    
-    private TemplateLookupStrategy getDefaultTemplateLookupStrategy() {
-        return getDefaultTemplateLookupStrategy(getIncompatibleImprovements());
-    }
-    
-    static TemplateLookupStrategy getDefaultTemplateLookupStrategy(Version incompatibleImprovements) {
-        return DefaultTemplateLookupStrategy.INSTANCE;
-    }
-    
-    private TemplateNameFormat getDefaultTemplateNameFormat() {
-        return getDefaultTemplateNameFormat(getIncompatibleImprovements());
-    }
-    
-    static TemplateNameFormat getDefaultTemplateNameFormat(Version incompatibleImprovements) {
-        return DefaultTemplateNameFormatFM2.INSTANCE;
-    }
-    
-    private CacheStorage getDefaultCacheStorage() {
-        return createDefaultCacheStorage(getIncompatibleImprovements(), getCacheStorage()); 
-    }
-    
-    static CacheStorage createDefaultCacheStorage(Version incompatibleImprovements, CacheStorage existingCacheStorage) {
-        if (existingCacheStorage instanceof DefaultSoftCacheStorage) {
-            return existingCacheStorage;
+            this.registeredCustomOutputFormatsByName = registeredCustomOutputFormatsByName;
+            this.registeredCustomOutputFormats = Collections.unmodifiableList(new
+                    ArrayList<OutputFormat>(registeredCustomOutputFormats));
         }
-        return new DefaultSoftCacheStorage(); 
-    }
 
-    private static class DefaultSoftCacheStorage extends SoftCacheStorage {
-        // Nothing to override
-    }
+        ObjectWrapper objectWrapper = builder.getObjectWrapper();
 
-    static TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
-        return TemplateExceptionHandler.DEBUG_HANDLER; // [FM3] RETHROW
-    }
+        {
+            Map<String, Object> sharedVariables = builder.getSharedVariables();
 
-    private ObjectWrapper getDefaultObjectWrapper() {
-        return getDefaultObjectWrapper(getIncompatibleImprovements());
-    }
-    
-    @Override
-    public Object clone() {
-        try {
-            Configuration copy = (Configuration) super.clone();
-            copy.sharedVariables = new HashMap(sharedVariables);
-            copy.recreateTemplateResolverWith(
-                    templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(),
-                    templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(),
-                    templateResolver.getTemplateConfigurations());
-            return copy;
-        } catch (CloneNotSupportedException e) {
-            throw new BugException("Cloning failed", e);
+            HashMap<String, TemplateModel> wrappedSharedVariables = new HashMap<>(
+                    (sharedVariables.size() + 5 /* [FM3] 5 legacy vars */) * 4 / 3 + 1, 0.75f);
+
+            // TODO [FM3] Get rid of this
+            wrappedSharedVariables.put("capture_output", new CaptureOutput());
+            wrappedSharedVariables.put("compress", StandardCompress.INSTANCE);
+            wrappedSharedVariables.put("html_escape", new HtmlEscape());
+            wrappedSharedVariables.put("normalize_newlines", new NormalizeNewlines());
+            wrappedSharedVariables.put("xml_escape", new XmlEscape());
+
+            // In case the inherited sharedVariables aren't empty, we want to merge the two maps:
+            wrapAndPutSharedVariables(wrappedSharedVariables, builder.getDefaultSharedVariables(),
+                    objectWrapper);
+            if (builder.isSharedVariablesSet()) {
+                wrapAndPutSharedVariables(wrappedSharedVariables, sharedVariables, objectWrapper);
+            }
+            this.wrappedSharedVariables = wrappedSharedVariables;
+            this.sharedVariables = Collections.unmodifiableMap(new LinkedHashMap<>(sharedVariables));
         }
-    }
-    
-    private void loadBuiltInSharedVariables() {
-        sharedVariables.put("capture_output", new CaptureOutput());
-        sharedVariables.put("compress", StandardCompress.INSTANCE);
-        sharedVariables.put("html_escape", new HtmlEscape());
-        sharedVariables.put("normalize_newlines", new NormalizeNewlines());
-        sharedVariables.put("xml_escape", new XmlEscape());
-    }
 
-    /**
-     * Sets a {@link TemplateLoader} that is used to look up and load templates;
-     * as a side effect the template templateResolver will be emptied.
-     * By providing your own {@link TemplateLoader} implementation, you can load templates from whatever kind of
-     * storages, like from relational databases, NoSQL-storages, etc.
-     * 
-     * <p>Convenience methods exists to install commonly used loaders, instead of using this method:
-     * {@link #setClassForTemplateLoading(Class, String)}, 
-     * {@link #setClassLoaderForTemplateLoading(ClassLoader, String)}, 
-     * {@link #setDirectoryForTemplateLoading(File)}, and
-     * {@link #setServletContextForTemplateLoading(Object, String)}.
-     * 
-     * <p>You can chain several {@link TemplateLoader}-s together with {@link MultiTemplateLoader}.
-     * 
-     * <p>Default value: You should always set the template loader instead of relying on the default value.
-     * (But if you still care what it is, before "incompatible improvements" 2.3.21 it's a {@link FileTemplateLoader}
-     * that uses the current directory as its root; as it's hard tell what that directory will be, it's not very useful
-     * and dangerous. Starting with "incompatible improvements" 2.3.21 the default is {@code null}.)   
-     */
-    public void setTemplateLoader(TemplateLoader templateLoader) {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (templateResolver.getTemplateLoader() != templateLoader) {
-                recreateTemplateResolverWith(templateLoader, templateResolver.getCacheStorage(),
-                        templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(),
-                        templateResolver.getTemplateConfigurations());
+        // ParsingConfiguration settings:
+
+        templateLanguage = builder.getTemplateLanguage();
+        tagSyntax = builder.getTagSyntax();
+        namingConvention = builder.getNamingConvention();
+        whitespaceStripping = builder.getWhitespaceStripping();
+        autoEscapingPolicy = builder.getAutoEscapingPolicy();
+        outputFormat = builder.getOutputFormat();
+        recognizeStandardFileExtensions = builder.getRecognizeStandardFileExtensions();
+        tabSize = builder.getTabSize();
+        sourceEncoding = builder.getSourceEncoding();
+
+        // ProcessingConfiguration settings:
+
+        locale = builder.getLocale();
+        numberFormat = builder.getNumberFormat();
+        timeFormat = builder.getTimeFormat();
+        dateFormat = builder.getDateFormat();
+        dateTimeFormat = builder.getDateTimeFormat();
+        timeZone = builder.getTimeZone();
+        sqlDateAndTimeTimeZone = builder.getSQLDateAndTimeTimeZone();
+        booleanFormat = builder.getBooleanFormat();
+        templateExceptionHandler = builder.getTemplateExceptionHandler();
+        arithmeticEngine = builder.getArithmeticEngine();
+        this.objectWrapper = objectWrapper;
+        outputEncoding = builder.getOutputEncoding();
+        urlEscapingCharset = builder.getURLEscapingCharset();
+        autoFlush = builder.getAutoFlush();
+        newBuiltinClassResolver = builder.getNewBuiltinClassResolver();
+        showErrorTips = builder.getShowErrorTips();
+        apiBuiltinEnabled = builder.getAPIBuiltinEnabled();
+        logTemplateExceptions = builder.getLogTemplateExceptions();
+        customDateFormats = Collections.unmodifiableMap(builder.getCustomDateFormats());
+        customNumberFormats = Collections.unmodifiableMap(builder.getCustomNumberFormats());
+        autoImports = Collections.unmodifiableMap(builder.getAutoImports());
+        autoIncludes = Collections.unmodifiableList(builder.getAutoIncludes());
+        lazyImports = builder.getLazyImports();
+        lazyAutoImports = builder.getLazyAutoImports();
+        customAttributes = Collections.unmodifiableMap(builder.getCustomAttributes());
+    }
+
+    private <SelfT extends ExtendableBuilder<SelfT>> void wrapAndPutSharedVariables(
+            HashMap<String, TemplateModel> wrappedSharedVariables, Map<String, Object> rawSharedVariables,
+            ObjectWrapper objectWrapper) throws ConfigurationSettingValueException {
+        if (rawSharedVariables.isEmpty()) {
+            return;
+        }
+
+        for (Entry<String, Object> ent : rawSharedVariables.entrySet()) {
+            try {
+                wrappedSharedVariables.put(ent.getKey(), objectWrapper.wrap(ent.getValue()));
+            } catch (TemplateModelException e) {
+                throw new ConfigurationSettingValueException(
+                        ExtendableBuilder.SHARED_VARIABLES_KEY, null, false,
+                        "Failed to wrap shared variable " + _StringUtil.jQuote(ent.getKey()),
+                        e);
             }
-            templateLoaderExplicitlySet = true;
         }
     }
-    
-    /**
-     * Resets the setting to its default, as if it was never set. This means that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
-     * {@link #isTemplateLoaderExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
-     */
-    public void unsetTemplateLoader() {
-        if (templateLoaderExplicitlySet) {
-            setTemplateLoader(null);
-            templateLoaderExplicitlySet = false;
-        }
+
+    @Override
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        return templateExceptionHandler;
     }
 
     /**
-     * Tells if {@link #setTemplateLoader(TemplateLoader)} (or equivalent) was already called on this instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public boolean isTemplateLoaderExplicitlySet() {
-        return templateLoaderExplicitlySet;
+    @Override
+    public boolean isTemplateExceptionHandlerSet() {
+        return true;
     }
 
-    /**
-     * The getter pair of {@link #setTemplateLoader(TemplateLoader)}.
-     */
+    private static class DefaultSoftCacheStorage extends SoftCacheStorage {
+        // Nothing to override
+    }
+
+    @Override
     public TemplateLoader getTemplateLoader() {
         if (templateResolver == null) {
             return null;
         }
         return templateResolver.getTemplateLoader();
     }
-    
-    /**
-     * Sets a {@link TemplateLookupStrategy} that is used to look up templates based on the requested name; as a side
-     * effect the template templateResolver will be emptied. The default value is
-     * {@link DefaultTemplateLookupStrategy#INSTANCE}.
-     * 
-     * @since 2.3.22
-     */
-    public void setTemplateLookupStrategy(TemplateLookupStrategy templateLookupStrategy) {
-        if (templateResolver.getTemplateLookupStrategy() != templateLookupStrategy) {
-            recreateTemplateResolverWith(templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(),
-                    templateLookupStrategy, templateResolver.getTemplateNameFormat(),
-                    templateResolver.getTemplateConfigurations());
-        }
-        templateLookupStrategyExplicitlySet = true;
-    }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
-     * {@link #isTemplateLookupStrategyExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
-     */
-    public void unsetTemplateLookupStrategy() {
-        if (templateLookupStrategyExplicitlySet) {
-            setTemplateLookupStrategy(getDefaultTemplateLookupStrategy());
-            templateLookupStrategyExplicitlySet = false;
-        }
-    }
-    
-    /**
-     * Tells if {@link #setTemplateLookupStrategy(TemplateLookupStrategy)} (or equivalent) was already called on this
-     * instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public boolean isTemplateLookupStrategyExplicitlySet() {
-        return templateLookupStrategyExplicitlySet;
+    @Override
+    public boolean isTemplateLoaderSet() {
+        return true;
     }
-    
-    /**
-     * The getter pair of {@link #setTemplateLookupStrategy(TemplateLookupStrategy)}.
-     */
+
+    @Override
     public TemplateLookupStrategy getTemplateLookupStrategy() {
         if (templateResolver == null) {
             return null;
         }
         return templateResolver.getTemplateLookupStrategy();
     }
-    
-    /**
-     * Sets the template name format used. The default is {@link DefaultTemplateNameFormatFM2#INSTANCE}, while the
-     * recommended value for new projects is {@link DefaultTemplateNameFormat#INSTANCE}.
-     * 
-     * @since 2.3.22
-     */
-    public void setTemplateNameFormat(TemplateNameFormat templateNameFormat) {
-        if (templateResolver.getTemplateNameFormat() != templateNameFormat) {
-            recreateTemplateResolverWith(templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(),
-                    templateResolver.getTemplateLookupStrategy(), templateNameFormat,
-                    templateResolver.getTemplateConfigurations());
-        }
-        templateNameFormatExplicitlySet = true;
-    }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
-     * {@link #isTemplateNameFormatExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void unsetTemplateNameFormat() {
-        if (templateNameFormatExplicitlySet) {
-            setTemplateNameFormat(getDefaultTemplateNameFormat());
-            templateNameFormatExplicitlySet = false;
-        }
+    @Override
+    public boolean isTemplateLookupStrategySet() {
+        return true;
     }
     
-    /**
-     * Tells if {@link #setTemplateNameFormat(TemplateNameFormat)} (or equivalent) was already called on this instance.
-     * 
-     * @since 2.3.22
-     */
-    public boolean isTemplateNameFormatExplicitlySet() {
-        return templateNameFormatExplicitlySet;
-    }
-
-    /**
-     * The getter pair of {@link #setTemplateNameFormat(TemplateNameFormat)}.
-     */
+    @Override
     public TemplateNameFormat getTemplateNameFormat() {
         if (templateResolver == null) {
             return null;
         }
         return templateResolver.getTemplateNameFormat();
     }
-    
+
     /**
-     * Sets a {@link TemplateConfigurationFactory} that will configure individual templates where their settings differ
-     * from those coming from the common {@link Configuration} object. A typical use case for that is specifying the
-     * {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat) outputFormat} for templates based on their
-     * file extension or parent directory.
-     * 
-     * <p>
-     * Note that the settings suggested by standard file extensions are stronger than that you set here. See
-     * {@link #setRecognizeStandardFileExtensions(boolean)} for more information about standard file extensions.
-     * 
-     * <p>See "Template configurations" in the FreeMarker Manual for examples.
-     * 
-     * @since 2.3.24
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void setTemplateConfigurations(TemplateConfigurationFactory templateConfigurations) {
-        if (templateResolver.getTemplateConfigurations() != templateConfigurations) {
-            recreateTemplateResolverWith(templateResolver.getTemplateLoader(), templateResolver.getCacheStorage(),
-                    templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(),
-                    templateConfigurations);
-        }
+    @Override
+    public boolean isTemplateNameFormatSet() {
+        return true;
     }
-    
-    /**
-     * The getter pair of {@link #setTemplateConfigurations(TemplateConfigurationFactory)}.
-     */
+
+    @Override
     public TemplateConfigurationFactory getTemplateConfigurations() {
         if (templateResolver == null) {
             return null;
@@ -810,1183 +547,669 @@ public final class Configuration extends MutableProcessingConfiguration<Configur
     }
 
     /**
-     * Sets the {@link CacheStorage} used for caching {@link Template}-s;
-     * the earlier content of the template templateResolver will be dropt.
-     * 
-     * The default is a {@link SoftCacheStorage}. If the total size of the {@link Template}
-     * objects is significant but most templates are used rarely, using a
-     * {@link MruCacheStorage} instead might be advisable. If you don't want caching at
-     * all, use {@link org.apache.freemarker.core.templateresolver.impl.NullCacheStorage} (you can't use {@code null}).
-     * 
-     * <p>Note that setting the templateResolver storage will re-invoke the template templateResolver, so
-     * all its content will be lost.
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void setCacheStorage(CacheStorage cacheStorage) {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (getCacheStorage() != cacheStorage) {
-                recreateTemplateResolverWith(templateResolver.getTemplateLoader(), cacheStorage,
-                        templateResolver.getTemplateLookupStrategy(), templateResolver.getTemplateNameFormat(),
-                        templateResolver.getTemplateConfigurations());
-            }
-            cacheStorageExplicitlySet = true;
-        }
+    @Override
+    public boolean isTemplateConfigurationsSet() {
+        return true;
     }
-    
-    /**
-     * Resets the setting to its default, as if it was never set. This means that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
-     * {@link #isCacheStorageExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
-     */
-    public void unsetCacheStorage() {
-        if (cacheStorageExplicitlySet) {
-            setCacheStorage(getDefaultCacheStorage());
-            cacheStorageExplicitlySet = false;
-        }
+
+    @Override
+    public CacheStorage getCacheStorage() {
+        return templateResolver.getCacheStorage();
     }
-    
+
     /**
-     * Tells if {@link #setCacheStorage(CacheStorage)} (or equivalent) was already called on this instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public boolean isCacheStorageExplicitlySet() {
-        return cacheStorageExplicitlySet;
+    @Override
+    public boolean isCacheStorageSet() {
+        return true;
     }
-    
-    /**
-     * The getter pair of {@link #setCacheStorage(CacheStorage)}.
-     * 
-     * @since 2.3.20
-     */
-    public CacheStorage getCacheStorage() {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (templateResolver == null) {
-                return null;
-            }
-            return templateResolver.getCacheStorage();
-        }
+
+    @Override
+    public long getTemplateUpdateDelayMilliseconds() {
+        return templateResolver.getTemplateUpdateDelayMilliseconds();
     }
 
     /**
-     * Sets the file system directory from which to load templates. This is equivalent to
-     * {@code setTemplateLoader(new FileTemplateLoader(dir))}, so see
-     * {@link FileTemplateLoader#FileTemplateLoader(File)} for more details.
-     * 
-     * <p>
-     * Note that FreeMarker can load templates from non-file-system sources too. See
-     * {@link #setTemplateLoader(TemplateLoader)} from more details.
-     * 
-     * <p>
-     * Note that this shouldn't be used for loading templates that are coming from a WAR; use
-     * {@link #setServletContextForTemplateLoading(Object, String)} then. Servlet containers might not unpack the WAR
-     * file, in which case you clearly can't access the contained files via {@link File}. Even if the WAR is unpacked,
-     * the servlet container might not expose the location as a {@link File}.
-     * {@link #setServletContextForTemplateLoading(Object, String)} on the other hand will work in all these cases.
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void setDirectoryForTemplateLoading(File dir) throws IOException {
-        TemplateLoader tl = getTemplateLoader();
-        if (tl instanceof FileTemplateLoader) {
-            String path = ((FileTemplateLoader) tl).baseDir.getCanonicalPath();
-            if (path.equals(dir.getCanonicalPath()))
-                return;
-        }
-        setTemplateLoader(new FileTemplateLoader(dir));
+    @Override
+    public boolean isTemplateUpdateDelayMillisecondsSet() {
+        return true;
     }
 
-    /**
-     * Sets the servlet context from which to load templates.
-     * This is equivalent to {@code setTemplateLoader(new WebAppTemplateLoader(sctxt, path))}
-     * or {@code setTemplateLoader(new WebAppTemplateLoader(sctxt))} if {@code path} was
-     * {@code null}, so see {@code org.apache.freemarker.servlet.WebAppTemplateLoader} for more details.
-     * 
-     * @param servletContext the {@code javax.servlet.ServletContext} object. (The declared type is {@link Object}
-     *        to prevent class loading error when using FreeMarker in an environment where
-     *        there's no servlet classes available.)
-     * @param path the path relative to the ServletContext.
-     *
-     * @see #setTemplateLoader(TemplateLoader)
-     */
-    public void setServletContextForTemplateLoading(Object servletContext, String path) {
-        try {
-            // Don't introduce linking-time dependency on servlets
-            final Class webappTemplateLoaderClass = _ClassUtil.forName(
-                    "org.apache.freemarker.servlet.WebAppTemplateLoader");
-            
-            // Don't introduce linking-time dependency on servlets
-            final Class servletContextClass = _ClassUtil.forName("javax.servlet.ServletContext");
-            
-            final Class[] constructorParamTypes;
-            final Object[] constructorParams;
-            if (path == null) {
-                constructorParamTypes = new Class[] { servletContextClass };
-                constructorParams = new Object[] { servletContext };
-            } else {
-                constructorParamTypes = new Class[] { servletContextClass, String.class };
-                constructorParams = new Object[] { servletContext, path };
-            }
-            
-            setTemplateLoader( (TemplateLoader)
-                    webappTemplateLoaderClass
-                            .getConstructor(constructorParamTypes)
-                                    .newInstance(constructorParams));
-        } catch (Exception e) {
-            throw new BugException(e);
-        }
+    @Override
+    public Version getIncompatibleImprovements() {
+        return incompatibleImprovements;
+    }
+
+    @Override
+    public boolean getWhitespaceStripping() {
+        return whitespaceStripping;
     }
 
     /**
-     * Sets the class whose {@link Class#getResource(String)} method will be used to load templates, from the inside the
-     * package specified. See {@link ClassTemplateLoader#ClassTemplateLoader(Class, String)} for more details.
-     * 
-     * @param basePackagePath
-     *            Separate steps with {@code "/"}, not {@code "."}, and note that it matters if this starts with
-     *            {@code /} or not. See {@link ClassTemplateLoader#ClassTemplateLoader(Class, String)} for more details.
-     * 
-     * @see #setClassLoaderForTemplateLoading(ClassLoader, String)
-     * @see #setTemplateLoader(TemplateLoader)
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void setClassForTemplateLoading(Class resourceLoaderClass, String basePackagePath) {
-        setTemplateLoader(new ClassTemplateLoader(resourceLoaderClass, basePackagePath));
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return true;
     }
-    
+
     /**
-     * Sets the {@link ClassLoader} whose {@link ClassLoader#getResource(String)} method will be used to load templates,
-     * from the inside the package specified. See {@link ClassTemplateLoader#ClassTemplateLoader(Class, String)} for
-     * more details.
-     * 
-     * @param basePackagePath
-     *            Separate steps with {@code "/"}, not {@code "."}. See
-     *            {@link ClassTemplateLoader#ClassTemplateLoader(Class, String)} for more details.
-     * 
-     * @see #setClassForTemplateLoading(Class, String)
-     * @see #setTemplateLoader(TemplateLoader)
-     * 
-     * @since 2.3.22
+     * When auto-escaping should be enabled depending on the current {@linkplain OutputFormat output format};
+     * default is {@link ParsingConfiguration#ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}. Note that the default output
+     * format, {@link UndefinedOutputFormat}, is a non-escaping format, so there auto-escaping will be off.
+     * Note that the templates can turn auto-escaping on/off locally with directives like {@code <#ftl auto_esc=...>},
+     * which will ignore the policy.
+     *
+     * <p><b>About auto-escaping</b></p>
+     *
+     * <p>
+     * Auto-escaping has significance when a value is printed with <code>${...}</code> (or <code>#{...}</code>). If
+     * auto-escaping is on, FreeMarker will assume that the value is plain text (as opposed to markup or some kind of
+     * rich text), so it will escape it according the current output format (see {@link #getOutputFormat()}
+     * and {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat)}). If auto-escaping is off, FreeMarker
+     * will assume that the string value is already in the output format, so it prints it as is to the output.
+     *
+     * <p>Further notes on auto-escaping:
+     * <ul>
+     *   <li>When printing numbers, dates, and other kind of non-string values with <code>${...}</code>, they will be
+     *       first converted to string (according the formatting settings and locale), then they are escaped just like
+     *       string values.
+     *   <li>When printing {@link TemplateMarkupOutputModel}-s, they aren't escaped again (they are already escaped).
+     *   <li>Auto-escaping doesn't do anything if the current output format isn't an {@link MarkupOutputFormat}.
+     *       That's the case for the default output format, {@link UndefinedOutputFormat}, and also for
+     *       {@link PlainTextOutputFormat}.
+     *   <li>The output format inside a string literal expression is always {@link PlainTextOutputFormat}
+     *       (regardless of the output format of the containing template), which is a non-escaping format. Thus for
+     *       example, with <code>&lt;#assign s = "foo${bar}"&gt;</code>, {@code bar} will always get into {@code s}
+     *       without escaping, but with <code>&lt;#assign s&gt;foo${bar}&lt;#assign&gt;</code> it may will be escaped.
+     * </ul>
+     *
+     * <p>Note that what you set here is just a default, which can be overridden for individual templates with the
+     * {@linkplain #getTemplateConfigurations() template configurations setting}. This setting is also overridden by
+     * the standard file extensions; see them at {@link #getRecognizeStandardFileExtensions()}.
+     *
+     * @see Configuration.Builder#setAutoEscapingPolicy(int)
+     * @see TemplateConfiguration.Builder#setAutoEscapingPolicy(int)
+     * @see Configuration.Builder#setOutputFormat(OutputFormat)
+     * @see TemplateConfiguration.Builder#setOutputFormat(OutputFormat)
      */
-    public void setClassLoaderForTemplateLoading(ClassLoader classLoader, String basePackagePath) {
-        setTemplateLoader(new ClassTemplateLoader(classLoader, basePackagePath));
+    @Override
+    public int getAutoEscapingPolicy() {
+        return autoEscapingPolicy;
     }
 
     /**
-     * Sets the time in milliseconds that must elapse before checking whether there is a newer version of a template
-     * "file" exists than the cached one. Defaults to 5000 ms.
-     * 
-     * <p>
-     * When you get a template via {@link #getTemplate(String)} (or some of its overloads). FreeMarker will try to get
-     * the template from the template templateResolver. If the template is found, and at least this amount of time was elapsed
-     * since the template last modification date was checked, FreeMarker will re-check the last modification date (this
-     * could mean I/O), possibly reloading the template and updating the templateResolver as a consequence (can mean even more
-     * I/O). The {@link #getTemplate(String)} (or some of its overloads) call will only return after this all is
-     * done, so it will return the fresh template.
-     * 
-     * @since 2.3.23
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void setTemplateUpdateDelayMilliseconds(long millis) {
-        templateResolver.setTemplateUpdateDelayMilliseconds(millis);
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return true;
     }
-    
+
+    @Override
+    public OutputFormat getOutputFormat() {
+        return outputFormat;
+    }
+
     /**
-     * The getter pair of {@link #setTemplateUpdateDelayMilliseconds(long)}.
-     * 
-     * @since 2.3.23
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public long getTemplateUpdateDelayMilliseconds() {
-        return templateResolver.getTemplateUpdateDelayMilliseconds();
-    }
-    
     @Override
-    public void setObjectWrapper(ObjectWrapper objectWrapper) {
-        ObjectWrapper prevObjectWrapper = getObjectWrapper();
-        super.setObjectWrapper(objectWrapper);
-        objectWrapperExplicitlySet = true;
-        if (objectWrapper != prevObjectWrapper) {
-            try {
-                setSharedVariablesFromRewrappableSharedVariables();
-            } catch (TemplateModelException e) {
-                throw new RuntimeException(
-                        "Failed to re-wrap earliearly set shared variables with the newly set object wrapper",
-                        e);
+    public boolean isOutputFormatSet() {
+        return true;
+    }
+
+    /**
+     * Returns the output format for a name.
+     * 
+     * @param name
+     *            Either the name of the output format as it was registered with the
+     *            {@link Configuration#getRegisteredCustomOutputFormats registeredCustomOutputFormats} setting,
+     *            or a combined output format name.
+     *            A combined output format is created ad-hoc from the registered formats. For example, if you need RTF
+     *            embedded into HTML, the name will be <code>HTML{RTF}</code>, where "HTML" and "RTF" refer to the
+     *            existing formats. This logic can be used recursively, so for example <code>XML{HTML{RTF}}</code> is
+     *            also valid.
+     * 
+     * @return Not {@code null}.
+     * 
+     * @throws UnregisteredOutputFormatException
+     *             If there's no output format registered with the given name.
+     * @throws IllegalArgumentException
+     *             If the usage of <code>{</code> and <code>}</code> in the name is syntactically wrong, or if not all
+     *             {@link OutputFormat}-s are {@link MarkupOutputFormat}-s in the <code>...{...}</code> expression.
+     */
+    public OutputFormat getOutputFormat(String name) throws UnregisteredOutputFormatException {
+        if (name.length() == 0) {
+            throw new IllegalArgumentException("0-length format name");
+        }
+        if (name.charAt(name.length() - 1) == '}') {
+            // Combined markup
+            int openBrcIdx = name.indexOf('{');
+            if (openBrcIdx == -1) {
+                throw new IllegalArgumentException("Missing opening '{' in: " + name);
+            }
+            
+            MarkupOutputFormat outerOF = getMarkupOutputFormatForCombined(name.substring(0, openBrcIdx));
+            MarkupOutputFormat innerOF = getMarkupOutputFormatForCombined(
+                    name.substring(openBrcIdx + 1, name.length() - 1));
+            
+            return new CombinedMarkupOutputFormat(name, outerOF, innerOF);
+        } else {
+            OutputFormat custOF = registeredCustomOutputFormatsByName.get(name);
+            if (custOF != null) {
+                return custOF;
+            }
+            
+            OutputFormat stdOF = STANDARD_OUTPUT_FORMATS.get(name);
+            if (stdOF == null) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("Unregistered output format name, ");
+                sb.append(_StringUtil.jQuote(name));
+                sb.append(". The output formats registered in the Configuration are: ");
+                
+                Set<String> registeredNames = new TreeSet<>();
+                registeredNames.addAll(STANDARD_OUTPUT_FORMATS.keySet());
+                registeredNames.addAll(registeredCustomOutputFormatsByName.keySet());
+                
+                boolean first = true;
+                for (String registeredName : registeredNames) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        sb.append(", ");
+                    }
+                    sb.append(_StringUtil.jQuote(registeredName));
+                }
+                
+                throw new UnregisteredOutputFormatException(sb.toString());
             }
+            return stdOF;
         }
     }
 
-    @Override
-    protected ObjectWrapper getInheritedObjectWrapper() {
-        throw new BugException("Missing property value");
+    private MarkupOutputFormat getMarkupOutputFormatForCombined(String outerName)
+            throws UnregisteredOutputFormatException {
+        OutputFormat of = getOutputFormat(outerName);
+        if (!(of instanceof MarkupOutputFormat)) {
+            throw new IllegalArgumentException("The \"" + outerName + "\" output format can't be used in "
+                    + "...{...} expression, because it's not a markup format.");
+        }
+        return (MarkupOutputFormat) of;
     }
-
-    @Override
-    protected Charset getInheritedOutputEncoding() {
-        throw new BugException("Missing property value");
+    
+    /**
+     * The custom output formats that can be referred by their unique name ({@link OutputFormat#getName()}) from
+     * templates. Names are also used to look up the {@link OutputFormat} for standard file extensions; see them at
+     * {@link #getRecognizeStandardFileExtensions()}. Each must be different and has a unique name
+     * ({@link OutputFormat#getName()}) within this collection.
+     *
+     * <p>
+     * When there's a clash between a custom output format name and a standard output format name, the custom format
+     * will win, thus you can override the meaning of standard output format names. Except, it's not allowed to override
+     * {@link UndefinedOutputFormat} and {@link PlainTextOutputFormat}.
+     *
+     * <p>
+     * The default value is an empty collection.
+     *
+     * @throws IllegalArgumentException
+     *             When multiple different {@link OutputFormat}-s have the same name in the parameter collection. When
+     *             the same {@link OutputFormat} object occurs for multiple times in the collection. If an
+     *             {@link OutputFormat} name is 0 long. If an {@link OutputFormat} name doesn't start with letter or
+     *             digit. If an {@link OutputFormat} name contains {@code '+'} or <code>'{'</code> or <code>'}'</code>.
+     *             If an {@link OutputFormat} name equals to {@link UndefinedOutputFormat#getName()} or
+     *             {@link PlainTextOutputFormat#getName()}.
+     */
+    public Collection<OutputFormat> getRegisteredCustomOutputFormats() {
+        return registeredCustomOutputFormats;
     }
 
     @Override
-    protected Charset getInheritedURLEscapingCharset() {
-        throw new BugException("Missing property value");
+    public boolean getRecognizeStandardFileExtensions() {
+        return recognizeStandardFileExtensions == null
+                ? true
+                : recognizeStandardFileExtensions.booleanValue();
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
-        throw new BugException("Missing property value");
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return true;
     }
 
     @Override
-    protected boolean getInheritedAutoFlush() {
-        throw new BugException("Missing property value");
+    public TemplateLanguage getTemplateLanguage() {
+        return templateLanguage;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected boolean getInheritedShowErrorTips() {
-        throw new BugException("Missing property value");
+    public boolean isTemplateLanguageSet() {
+        return true;
     }
 
     @Override
-    protected boolean getInheritedAPIBuiltinEnabled() {
-        throw new BugException("Missing property value");
+    public int getTagSyntax() {
+        return tagSyntax;
     }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
-     * {@link #isObjectWrapperExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void unsetObjectWrapper() {
-        if (objectWrapperExplicitlySet) {
-            setObjectWrapper(getDefaultObjectWrapper());
-            objectWrapperExplicitlySet = false;
-        }
-    }
-    
+
     /**
-     * Tells if {@link #setObjectWrapper(ObjectWrapper)} (or equivalent) was already called on this instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public boolean isObjectWrapperExplicitlySet() {
-        return objectWrapperExplicitlySet;
+    @Override
+    public boolean isTagSyntaxSet() {
+        return true;
     }
 
-    @Override
-    public void setLocale(Locale locale) {
-        super.setLocale(locale);
-        localeExplicitlySet = true;
+    // [FM3] Use enum; won't be needed
+    static void validateNamingConventionValue(int namingConvention) {
+        if (namingConvention != ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION
+                && namingConvention != ParsingConfiguration.LEGACY_NAMING_CONVENTION
+                && namingConvention != ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) {
+            throw new IllegalArgumentException("\"naming_convention\" can only be set to one of these: "
+                    + "Configuration.AUTO_DETECT_NAMING_CONVENTION, "
+                    + "or Configuration.LEGACY_NAMING_CONVENTION"
+                    + "or Configuration.CAMEL_CASE_NAMING_CONVENTION");
+        }
     }
 
     @Override
-    protected Locale getInheritedLocale() {
-        throw new BugException("Missing property value");
+    public int getNamingConvention() {
+        return namingConvention;
     }
 
     /**
-     * Resets the setting to its default, as if it was never set.
-     *
-     * @since 2.3.26
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void unsetLocale() {
-        if (localeExplicitlySet) {
-            setLocale(getDefaultLocale());
-            localeExplicitlySet = false;
-        }
+    @Override
+    public boolean isNamingConventionSet() {
+        return true;
     }
 
-    /**
-     * Tells if {@link #setLocale(Locale)} (or equivalent) was already called on this instance, or it just holds the
-     * default value.
-     *
-     * @since 2.3.26
-     */
-    public boolean isLocaleExplicitlySet() {
-        return localeExplicitlySet;
+    @Override
+    public int getTabSize() {
+        return tabSize;
     }
 
-    static Locale getDefaultLocale() {
-        return Locale.getDefault();
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
+    @Override
+    public boolean isTabSizeSet() {
+        return true;
     }
 
     @Override
-    public void setTimeZone(TimeZone timeZone) {
-        super.setTimeZone(timeZone);
-        timeZoneExplicitlySet = true;
+    public Locale getLocale() {
+        return locale;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected TimeZone getInheritedTimeZone() {
-        throw new BugException("Missing property value");
+    public boolean isLocaleSet() {
+        return true;
     }
 
     @Override
-    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
-        throw new BugException("Missing property value");
+    public TimeZone getTimeZone() {
+        return timeZone;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected String getInheritedNumberFormat() {
-        throw new BugException("Missing property value");
+    public boolean isTimeZoneSet() {
+        return true;
     }
 
     @Override
-    protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
-        throw new BugException("Missing property value");
+    public TimeZone getSQLDateAndTimeTimeZone() {
+        return sqlDateAndTimeTimeZone;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
-        return null;
+    public boolean isSQLDateAndTimeTimeZoneSet() {
+        return true;
     }
 
     @Override
-    protected String getInheritedBooleanFormat() {
-        throw new BugException("Missing property value");
+    public ArithmeticEngine getArithmeticEngine() {
+        return arithmeticEngine;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected String getInheritedTimeFormat() {
-        throw new BugException("Missing property value");
+    public boolean isArithmeticEngineSet() {
+        return true;
     }
 
     @Override
-    protected String getInheritedDateFormat() {
-        throw new BugException("Missing property value");
+    public String getNumberFormat() {
+        return numberFormat;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected String getInheritedDateTimeFormat() {
-        throw new BugException("Missing property value");
+    public boolean isNumberFormatSet() {
+        return true;
     }
 
     @Override
-    protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
-        throw new BugException("Missing property value");
+    public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
+        return customNumberFormats;
     }
 
     @Override
-    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
-        return null;
+    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+        return customNumberFormats.get(name);
     }
 
     /**
-     * Resets the setting to its default, as if it was never set.
-     *
-     * @since 2.3.26
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void unsetTimeZone() {
-        if (timeZoneExplicitlySet) {
-            setTimeZone(getDefaultTimeZone());
-            timeZoneExplicitlySet = false;
-        }
+    @Override
+    public boolean isCustomNumberFormatsSet() {
+        return true;
     }
 
-    /**
-     * Tells if {@link #setTimeZone(TimeZone)} (or equivalent) was already called on this instance, or it just holds the
-     * default value.
-     *
-     * @since 2.3.26
-     */
-    public boolean isTimeZoneExplicitlySet() {
-        return timeZoneExplicitlySet;
+    @Override
+    public String getBooleanFormat() {
+        return booleanFormat;
     }
 
-    static TimeZone getDefaultTimeZone() {
-        return TimeZone.getDefault();
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
+    @Override
+    public boolean isBooleanFormatSet() {
+        return true;
     }
 
     @Override
-    public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
-        super.setTemplateExceptionHandler(templateExceptionHandler);
-        templateExceptionHandlerExplicitlySet = true;
+    public String getTimeFormat() {
+        return timeFormat;
     }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also 
-     * {@link #isTemplateExceptionHandlerExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
      */
-    public void unsetTemplateExceptionHandler() {
-        if (templateExceptionHandlerExplicitlySet) {
-            setTemplateExceptionHandler(getDefaultTemplateExceptionHandler());
-            templateExceptionHandlerExplicitlySet = false;
-        }
+    @Override
+    public boolean isTimeFormatSet() {
+        return true;
     }
-    
-    /**
-     * Tells if {@link #setTemplateExceptionHandler(TemplateExceptionHandler)} (or equivalent) was already called on
-     * this instance.
-     * 
-     * @since 2.3.22
-     */
-    public boolean isTemplateExceptionHandlerExplicitlySet() {
-        return templateExceptionHandlerExplicitlySet;
-    }    
-    
-    /**
-     * @since 2.3.22
-     */
+
     @Override
-    public void setLogTemplateExceptions(boolean value) {
-        super.setLogTemplateExceptions(value);
-        logTemplateExceptionsExplicitlySet = true;
+    public String getDateFormat() {
+        return dateFormat;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected boolean getInheritedLogTemplateExceptions() {
-        throw new BugException("Missing property value");
+    public boolean isDateFormatSet() {
+        return true;
     }
 
     @Override
-    protected boolean getInheritedLazyImports() {
-        throw new BugException("Missing property value");
+    public String getDateTimeFormat() {
+        return dateTimeFormat;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the corresponding getter is always safe.
+     */
     @Override
-    protected Boolean getInheritedLazyAutoImports() {
-        throw new BugException("Missing property value");
+    public boolean isDateTimeFormatSet() {
+        return true;
     }
 
     @Override
-    protected Map<String, String> getInheritedAutoImports() {
-        throw new BugException("Missing property value");
+

<TRUNCATED>


[03/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java b/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
index 95a396d..f0e63a8 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core;
 
+import static org.apache.freemarker.core.Configuration.ExtendableBuilder.TEMPLATE_LOOKUP_STRATEGY_KEY;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
@@ -39,26 +40,35 @@ public class TemplateLookupStrategyTest {
 
     @Test
     public void testSetSetting() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy());
+        assertSame(
+                DefaultTemplateLookupStrategy.INSTANCE,
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build()
+                        .getTemplateLookupStrategy());
 
-        cfg.setSetting(Configuration.TEMPLATE_LOOKUP_STRATEGY_KEY, MyTemplateLookupStrategy.class.getName() + "()");
-        assertTrue(cfg.getTemplateLookupStrategy() instanceof MyTemplateLookupStrategy);
-        
-        cfg.setSetting(Configuration.TEMPLATE_LOOKUP_STRATEGY_KEY, "dEfault");
-        assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy());
+        assertTrue(
+                new Configuration.Builder(Configuration.VERSION_3_0_0)
+                        .setting(TEMPLATE_LOOKUP_STRATEGY_KEY, MyTemplateLookupStrategy.class.getName() + "()")
+                        .build()
+                        .getTemplateLookupStrategy() instanceof MyTemplateLookupStrategy);
+        
+        assertSame(
+                DefaultTemplateLookupStrategy.INSTANCE,
+                new Configuration.Builder(Configuration.VERSION_3_0_0)
+                        .setting(TEMPLATE_LOOKUP_STRATEGY_KEY, "dEfault")
+                        .build()
+                        .getTemplateLookupStrategy());
     }
     
     @Test
     public void testCustomStrategy() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("test.ftl", "");
         tl.putTextTemplate("aa/test.ftl", "");
-        cfg.setTemplateLoader(tl);
-        
-        cfg.setTemplateLookupStrategy(MyTemplateLookupStrategy.INSTANCE);
+
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .templateLoader(tl)
+                .templateLookupStrategy(MyTemplateLookupStrategy.INSTANCE)
+                .build();
         
         final Locale locale = new Locale("aa", "BB", "CC_DD");
         
@@ -86,18 +96,16 @@ public class TemplateLookupStrategyTest {
     
     @Test
     public void testDefaultStrategy() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("test.ftl", "");
         tl.putTextTemplate("test_aa.ftl", "");
         tl.putTextTemplate("test_aa_BB.ftl", "");
         tl.putTextTemplate("test_aa_BB_CC.ftl", "");
         tl.putTextTemplate("test_aa_BB_CC_DD.ftl", "");
-        cfg.setTemplateLoader(tl);
-        
+
         try {
-            cfg.getTemplate("missing.ftl", new Locale("aa", "BB", "CC_DD"));
+            new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                    .getTemplate("missing.ftl", new Locale("aa", "BB", "CC_DD"));
             fail();
         } catch (TemplateNotFoundException e) {
             assertEquals("missing.ftl", e.getTemplateName());
@@ -110,12 +118,12 @@ public class TemplateLookupStrategyTest {
                             "missing.ftl"),
                     tl.getLoadNames());
             tl.clearEvents();
-            cfg.clearTemplateCache();
         }
-        
-        cfg.setLocale(new Locale("xx"));
+
         try {
-            cfg.getTemplate("missing.ftl");
+            new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl)
+                    .locale(new Locale("xx")).build()
+                    .getTemplate("missing.ftl");
             fail();
         } catch (TemplateNotFoundException e) {
             assertEquals("missing.ftl", e.getTemplateName());
@@ -123,12 +131,13 @@ public class TemplateLookupStrategyTest {
                     ImmutableList.of("missing_xx.ftl", "missing.ftl"),
                     tl.getLoadNames());
             tl.clearEvents();
-            cfg.clearTemplateCache();
         }
         
-        cfg.setLocalizedLookup(false);
         try {
-            cfg.getTemplate("missing.ftl");
+            new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl)
+                    .locale(new Locale("xx"))
+                    .localizedLookup(false).build()
+                    .getTemplate("missing.ftl");
             fail();
         } catch (TemplateNotFoundException e) {
             assertEquals("missing.ftl", e.getTemplateName());
@@ -136,12 +145,11 @@ public class TemplateLookupStrategyTest {
                     ImmutableList.of("missing.ftl"),
                     tl.getLoadNames());
             tl.clearEvents();
-            cfg.clearTemplateCache();
         }
-        cfg.setLocalizedLookup(true);
-        
+
         try {
-            cfg.getTemplate("_a_b_.ftl", new Locale("xx", "yy"));
+            new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                    .getTemplate("_a_b_.ftl", new Locale("xx", "yy"));
             fail();
         } catch (TemplateNotFoundException e) {
             assertEquals("_a_b_.ftl", e.getTemplateName());
@@ -149,13 +157,13 @@ public class TemplateLookupStrategyTest {
                     ImmutableList.of("_a_b__xx_YY.ftl", "_a_b__xx.ftl", "_a_b_.ftl"),
                     tl.getLoadNames());
             tl.clearEvents();
-            cfg.clearTemplateCache();
         }
 
         for (String templateName : new String[] { "test.ftl", "./test.ftl", "/test.ftl", "x/foo/../../test.ftl" }) {
             {
                 final Locale locale = new Locale("aa", "BB", "CC_DD");
-                final Template t = cfg.getTemplate("test.ftl", locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                        .getTemplate("test.ftl", locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test_aa_BB_CC_DD.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
@@ -163,24 +171,24 @@ public class TemplateLookupStrategyTest {
                 assertEquals(ImmutableList.of("test_aa_BB_CC_DD.ftl"), tl.getLoadNames());
                 assertNull(t.getCustomLookupCondition());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
             }
             
             {
                 final Locale locale = new Locale("aa", "BB", "CC_XX");
-                final Template t = cfg.getTemplate(templateName, locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                        .getTemplate(templateName, locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test_aa_BB_CC.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
                 assertNull(t.getCustomLookupCondition());
                 assertEquals(ImmutableList.of("test_aa_BB_CC_XX.ftl", "test_aa_BB_CC.ftl"), tl.getLoadNames());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
             }
             
             {
                 final Locale locale = new Locale("aa", "BB", "XX_XX");
-                final Template t = cfg.getTemplate(templateName, locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                        .getTemplate(templateName, locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test_aa_BB.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
@@ -189,13 +197,13 @@ public class TemplateLookupStrategyTest {
                         ImmutableList.of("test_aa_BB_XX_XX.ftl", "test_aa_BB_XX.ftl", "test_aa_BB.ftl"),
                         tl.getLoadNames());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
             }
     
             {
-                cfg.setLocalizedLookup(false);
                 final Locale locale = new Locale("aa", "BB", "XX_XX");
-                final Template t = cfg.getTemplate(templateName, locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl)
+                        .localizedLookup(false).build()
+                        .getTemplate(templateName, locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
@@ -204,13 +212,12 @@ public class TemplateLookupStrategyTest {
                         ImmutableList.of("test.ftl"),
                         tl.getLoadNames());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
-                cfg.setLocalizedLookup(true);
             }
     
             {
                 final Locale locale = new Locale("aa", "XX", "XX_XX");
-                final Template t = cfg.getTemplate(templateName, locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                        .getTemplate(templateName, locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test_aa.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
@@ -219,12 +226,12 @@ public class TemplateLookupStrategyTest {
                         ImmutableList.of("test_aa_XX_XX_XX.ftl", "test_aa_XX_XX.ftl", "test_aa_XX.ftl", "test_aa.ftl"),
                         tl.getLoadNames());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
             }
             
             {
                 final Locale locale = new Locale("xx", "XX", "XX_XX");
-                final Template t = cfg.getTemplate(templateName, locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                        .getTemplate(templateName, locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
@@ -234,12 +241,12 @@ public class TemplateLookupStrategyTest {
                                 "test_xx_XX_XX_XX.ftl", "test_xx_XX_XX.ftl", "test_xx_XX.ftl", "test_xx.ftl", "test.ftl"),
                         tl.getLoadNames());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
             }
             
             {
                 final Locale locale = new Locale("xx", "BB", "CC_DD");
-                final Template t = cfg.getTemplate(templateName, locale);
+                final Template t = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build()
+                        .getTemplate(templateName, locale);
                 assertEquals("test.ftl", t.getLookupName());
                 assertEquals("test.ftl", t.getSourceName());
                 assertEquals(locale, t.getLocale());
@@ -249,20 +256,18 @@ public class TemplateLookupStrategyTest {
                             "test_xx_BB_CC_DD.ftl", "test_xx_BB_CC.ftl", "test_xx_BB.ftl", "test_xx.ftl", "test.ftl"),
                         tl.getLoadNames());
                 tl.clearEvents();
-                cfg.clearTemplateCache();
             }
         }
     }
     
     @Test
     public void testAcquisition() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("t.ftl", "");
         tl.putTextTemplate("sub/i.ftl", "");
         tl.putTextTemplate("x/sub/i.ftl", "");
-        cfg.setTemplateLoader(tl);
+
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build();
 
         final Locale locale = new Locale("xx");
         
@@ -299,8 +304,18 @@ public class TemplateLookupStrategyTest {
 
     @Test
     public void testCustomLookupCondition() throws IOException, TemplateException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
+        MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
+
+        final Configuration cfg;
+        final Configuration cfgNoLocLU;
+        {
+            Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                    .templateLoader(tl)
+                    .templateLookupStrategy(new DomainTemplateLookupStrategy());
+            cfg = cfgB.build();
+            cfgNoLocLU = cfgB.localizedLookup(false).build();
+        }
+
         final String iAtDefaultContent = "i at default";
         final String iXxAtDefaultContent = "i_xx at default";
         final String iAtBaazComContent = "i at baaz.com";
@@ -314,7 +329,6 @@ public class TemplateLookupStrategyTest {
         final String t2XxLocaleExpectedOutput = "i3_xx at foo.com";
         final String t2OtherLocaleExpectedOutput = "i3 at foo.com";
         
-        MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("@foo.com/t.ftl", tAtFooComContent);
         tl.putTextTemplate("@bar.com/t.ftl", tAtBarComContent);
         tl.putTextTemplate("@default/t.ftl", tAtDefaultContent);
@@ -326,10 +340,7 @@ public class TemplateLookupStrategyTest {
         tl.putTextTemplate("@default/i2.ftl", "<#import 'i3.ftl' as i3 />");
         tl.putTextTemplate("@foo.com/i3.ftl", "<#global proof = '" + t2OtherLocaleExpectedOutput + "'>");
         tl.putTextTemplate("@foo.com/i3_xx.ftl", "<#global proof = '" + t2XxLocaleExpectedOutput + "'>");
-        cfg.setTemplateLoader(tl);
-        
-        cfg.setTemplateLookupStrategy(new DomainTemplateLookupStrategy());
-        
+
         {
             final Locale locale = new Locale("xx");
             final Domain domain = new Domain("foo.com");
@@ -423,10 +434,9 @@ public class TemplateLookupStrategyTest {
         }
 
         {
-            cfg.setLocalizedLookup(false);
             final Locale locale = new Locale("xx", "YY");
             final Domain domain = new Domain("nosuch.com");
-            final Template t = cfg.getTemplate("i.ftl", locale, domain);
+            final Template t = cfgNoLocLU.getTemplate("i.ftl", locale, domain);
             assertEquals("i.ftl", t.getLookupName());
             assertEquals("@default/i.ftl", t.getSourceName());
             assertEquals(locale, t.getLocale());
@@ -437,8 +447,7 @@ public class TemplateLookupStrategyTest {
                     tl.getLoadNames());
             
             tl.clearEvents();
-            cfg.setLocalizedLookup(true);
-            cfg.clearTemplateCache();
+            cfgNoLocLU.clearTemplateCache();
         }
         
         {
@@ -474,10 +483,9 @@ public class TemplateLookupStrategyTest {
         }
         
         {
-            cfg.setLocalizedLookup(false);
             final Locale locale = new Locale("xx");
             final Domain domain = new Domain("foo.com");
-            final Template t = cfg.getTemplate("t2.ftl", locale, domain);
+            final Template t = cfgNoLocLU.getTemplate("t2.ftl", locale, domain);
             assertOutputEquals(t2OtherLocaleExpectedOutput, t);
             assertEquals(
                     ImmutableList.of(
@@ -487,8 +495,7 @@ public class TemplateLookupStrategyTest {
                     tl.getLoadNames());
             
             tl.clearEvents();
-            cfg.setLocalizedLookup(true);
-            cfg.clearTemplateCache();
+            cfgNoLocLU.clearTemplateCache();
         }
         
         {
@@ -549,13 +556,12 @@ public class TemplateLookupStrategyTest {
     
     @Test
     public void testNonparsed() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("test.txt", "");
         tl.putTextTemplate("test_aa.txt", "");
-        cfg.setTemplateLoader(tl);
-        
+
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build();
+
         try {
             cfg.getTemplate("missing.txt", new Locale("aa", "BB"), null, false);
             fail();
@@ -585,12 +591,11 @@ public class TemplateLookupStrategyTest {
 
     @Test
     public void testParseError() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("test.ftl", "");
         tl.putTextTemplate("test_aa.ftl", "<#wrong>");
-        cfg.setTemplateLoader(tl);
+
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).templateLoader(tl).build();
         
         try {
             cfg.getTemplate("test.ftl", new Locale("aa", "BB"));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TemplateNameSpecialVariablesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateNameSpecialVariablesTest.java b/src/test/java/org/apache/freemarker/core/TemplateNameSpecialVariablesTest.java
index 71f761c..7c9a98f 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateNameSpecialVariablesTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateNameSpecialVariablesTest.java
@@ -20,64 +20,18 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.templateresolver.TemplateLoader;
-import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.test.TemplateTest;
-import org.junit.Before;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class TemplateNameSpecialVariablesTest extends TemplateTest {
     
-    private static TemplateLoader createTemplateLoader(String specVar) {
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("main.ftl",
-                "In main: ${" + specVar + "}\n"
-                + "<#import 'imp.ftl' as i>"
-                + "In imp: ${inImp}\n"
-                + "In main: ${" + specVar + "}\n"
-                + "<@i.impM>${" + specVar + "}</@>\n"
-                + "<@i.impM2 />\n"
-                + "In main: ${" + specVar + "}\n"
-                + "<#include 'inc.ftl'>"
-                + "In main: ${" + specVar + "}\n"
-                + "<@incM>${" + specVar + "}</@>\n"
-                + "<@incM2 />\n"
-                + "In main: ${" + specVar + "}\n"
-                );
-        tl.putTemplate("imp.ftl",
-                "<#global inImp = " + specVar + ">"
-                + "<#macro impM>"
-                    + "${" + specVar + "}\n"
-                    + "{<#nested>}"
-                + "</#macro>"
-                + "<#macro impM2>"
-                    + "In imp call imp:\n"
-                    + "<@impM>${" + specVar + "}</@>\n"
-                    + "After: ${" + specVar + "}"
-                + "</#macro>"
-                );
-        tl.putTemplate("inc.ftl",
-                "In inc: ${" + specVar + "}\n"
-                + "In inc call imp:\n"
-                + "<@i.impM>${" + specVar + "}</@>\n"
-                + "<#macro incM>"
-                    + "${" + specVar + "}\n"
-                    + "{<#nested>}"
-                + "</#macro>"
-                + "<#macro incM2>"
-                    + "In inc call imp:\n"
-                    + "<@i.impM>${" + specVar + "}</@>"
-                + "</#macro>"
-                );
-        return tl;
-    }
-
     private static final String PRINT_ALL_FTL
             = "ct=${.currentTemplateName!'-'}, mt=${.mainTemplateName!'-'}";
-    
+
     @Test
     public void testMainTemplateName() throws IOException, TemplateException {
-        getConfiguration().setTemplateLoader(createTemplateLoader(".mainTemplateName"));
+        addTemplateNameTestTemplates(".mainTemplateName");
         assertOutputForNamed("main.ftl",
                 "In main: main.ftl\n"
                 + "In imp: main.ftl\n"
@@ -104,7 +58,7 @@ public class TemplateNameSpecialVariablesTest extends TemplateTest {
 
     @Test
     public void testCurrentTemplateName() throws IOException, TemplateException {
-        getConfiguration().setTemplateLoader(createTemplateLoader(".currentTemplateName"));
+        addTemplateNameTestTemplates(".currentTemplateName");
         assertOutputForNamed("main.ftl",
                 "In main: main.ftl\n"
                 + "In imp: imp.ftl\n"
@@ -129,18 +83,52 @@ public class TemplateNameSpecialVariablesTest extends TemplateTest {
                 + "In main: main.ftl\n");
     }
 
-    @Before
-    public void setup() {
-        Configuration cfg = getConfiguration();
-        cfg.setWhitespaceStripping(false);
+    private void addTemplateNameTestTemplates(String specVar) {
+        addTemplate("main.ftl",
+                "In main: ${" + specVar + "}\n"
+                        + "<#import 'imp.ftl' as i>"
+                        + "In imp: ${inImp}\n"
+                        + "In main: ${" + specVar + "}\n"
+                        + "<@i.impM>${" + specVar + "}</@>\n"
+                        + "<@i.impM2 />\n"
+                        + "In main: ${" + specVar + "}\n"
+                        + "<#include 'inc.ftl'>"
+                        + "In main: ${" + specVar + "}\n"
+                        + "<@incM>${" + specVar + "}</@>\n"
+                        + "<@incM2 />\n"
+                        + "In main: ${" + specVar + "}\n"
+        );
+        addTemplate("imp.ftl",
+                "<#global inImp = " + specVar + ">"
+                        + "<#macro impM>"
+                        + "${" + specVar + "}\n"
+                        + "{<#nested>}"
+                        + "</#macro>"
+                        + "<#macro impM2>"
+                        + "In imp call imp:\n"
+                        + "<@impM>${" + specVar + "}</@>\n"
+                        + "After: ${" + specVar + "}"
+                        + "</#macro>"
+        );
+        addTemplate("inc.ftl",
+                "In inc: ${" + specVar + "}\n"
+                        + "In inc call imp:\n"
+                        + "<@i.impM>${" + specVar + "}</@>\n"
+                        + "<#macro incM>"
+                        + "${" + specVar + "}\n"
+                        + "{<#nested>}"
+                        + "</#macro>"
+                        + "<#macro incM2>"
+                        + "In inc call imp:\n"
+                        + "<@i.impM>${" + specVar + "}</@>"
+                        + "</#macro>"
+        );
     }
-    
+
     @Test
     public void testInAdhocTemplate() throws TemplateException, IOException {
-        StringTemplateLoader tl = new StringTemplateLoader();
-        tl.putTemplate("inc.ftl", "Inc: " + PRINT_ALL_FTL);
-        getConfiguration().setTemplateLoader(tl);
-        
+        addTemplate("inc.ftl", "Inc: " + PRINT_ALL_FTL);
+
         // In nameless templates, the deprecated .templateName is "", but the new variables are missing values. 
         assertOutput(new Template(null, PRINT_ALL_FTL + "; <#include 'inc.ftl'>", getConfiguration()),
                 "ct=-, mt=-; Inc: ct=inc.ftl, mt=-");
@@ -151,7 +139,7 @@ public class TemplateNameSpecialVariablesTest extends TemplateTest {
 
     @Test
     public void testInInterpretTemplate() throws TemplateException, IOException {
-        getConfiguration().setSharedVariable("t", PRINT_ALL_FTL);
+        addToDataModel("t", PRINT_ALL_FTL);
         assertOutput(new Template("foo.ftl", PRINT_ALL_FTL + "; <@t?interpret />", getConfiguration()),
                 "ct=foo.ftl, mt=foo.ftl; "
                 + "ct=foo.ftl->anonymous_interpreted, mt=foo.ftl");
@@ -162,5 +150,10 @@ public class TemplateNameSpecialVariablesTest extends TemplateTest {
                 "ct=foo.ftl, mt=foo.ftl; "
                 + "ct=foo.ftl->bar, mt=foo.ftl");
     }
-    
+
+    @Override
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return new TestConfigurationBuilder().whitespaceStripping(false).build();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java b/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
index 7348a69..9e6d540 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
@@ -98,19 +98,17 @@ public class TemplateNotFoundMessageTest {
 
     @Test
     public void testDefaultTemplateLoader() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        String errMsg = failWith(cfg);
+        String errMsg = failWith(null);
         showErrorMessage(errMsg);
         assertThat(errMsg, allOf(containsString("setTemplateLoader"), containsString("null")));
     }
     
     @Test
     public void testOtherMessageDetails() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setTemplateLoader(new StringTemplateLoader());
-        
+        // Non-null TemplateLoader:
+        StringTemplateLoader emptyLoader = new StringTemplateLoader();
         {
-            String errMsg = failWith("../x", cfg);
+            String errMsg = failWith(emptyLoader, "../x");
             showErrorMessage(errMsg);
             assertThat(errMsg,
                     allOf(
@@ -118,7 +116,7 @@ public class TemplateNotFoundMessageTest {
                             containsStringIgnoringCase("root directory")));
         }
         {
-            String errMsg = failWith("x\u0000y", cfg);
+            String errMsg = failWith(emptyLoader, "x\u0000y");
             showErrorMessage(errMsg);
             assertThat(errMsg,
                     allOf(
@@ -126,48 +124,54 @@ public class TemplateNotFoundMessageTest {
                             containsStringIgnoringCase("null character")));
         }
         {
-            String errMsg = failWith("x\\y", cfg);
+            String errMsg = failWith(emptyLoader, "x\\y");
             showErrorMessage(errMsg);
             assertThat(errMsg,
                     allOf(containsStringIgnoringCase("warning"), containsStringIgnoringCase("backslash")));
         }
         {
-            String errMsg = failWith("x/./y", cfg);
+            String errMsg = failWith(emptyLoader, "x/./y");
             showErrorMessage(errMsg);
             assertThat(errMsg,
                     allOf(containsStringIgnoringCase("normalized"), containsStringIgnoringCase("x/y")));
         }
         {
-            String errMsg = failWith("/x/y", cfg);
+            String errMsg = failWith(emptyLoader, "/x/y");
             showErrorMessage(errMsg);
             assertThat(errMsg, not(containsStringIgnoringCase("normalized")));
         }
         {
-            String errMsg = failWith("x/y", cfg);
+            String errMsg = failWith(emptyLoader, "x/y");
             showErrorMessage(errMsg);
             assertThat(errMsg, not(containsStringIgnoringCase("normalized")));
             assertThat(errMsg, not(containsStringIgnoringCase("lookup strategy")));
         }
-        
-        cfg.setTemplateLookupStrategy(new TemplateLookupStrategy() {
-            @Override
-            public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
-                return ctx.lookupWithAcquisitionStrategy(ctx.getTemplateName());
-            }
-        });
+
+        Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .templateLoader(new StringTemplateLoader())
+                .templateLookupStrategy(
+                        new TemplateLookupStrategy() {
+                            @Override
+                            public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
+                                return ctx.lookupWithAcquisitionStrategy(ctx.getTemplateName());
+                            }
+                        }
+        );
         {
-            String errMsg = failWith("x/y", cfg);
+            String errMsg = failWith(emptyLoader, "x/y",
+                    new TemplateLookupStrategy() {
+                        @Override
+                        public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
+                            return ctx.lookupWithAcquisitionStrategy(ctx.getTemplateName());
+                        }
+                    }
+            );
             showErrorMessage(errMsg);
             assertThat(errMsg, containsStringIgnoringCase("lookup strategy"));
         }
         
         try {
-            cfg.getTemplate("./missing", null, new Serializable() {
-                @Override
-                public String toString() {
-                    return "example.com";
-                }
-            });
+            cfgB.build().getTemplate("./missing", null, new DomainLookupCondition());
             fail();
         } catch (TemplateNotFoundException e) {
             showErrorMessage(e.getMessage());
@@ -179,12 +183,14 @@ public class TemplateNotFoundMessageTest {
         // System.out.println(errMsg);
     }
 
-    private String failWith(TemplateLoader tl, String name, Configuration cfg) {
-        if (tl != null) {
-            cfg.setTemplateLoader(tl);
-        }
+    private String failWith(TemplateLoader tl, String name, TemplateLookupStrategy templateLookupStrategy) {
         try {
-            cfg.getTemplate(name);
+            Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+            cfgB.setTemplateLoader(tl);
+            if (templateLookupStrategy != null) {
+                cfgB.setTemplateLookupStrategy(templateLookupStrategy);
+            }
+            cfgB.build().getTemplate(name);
             fail();
         } catch (TemplateNotFoundException | MalformedTemplateNameException e) {
             return e.getMessage();
@@ -193,17 +199,21 @@ public class TemplateNotFoundMessageTest {
         }
         return null;
     }
-    
-    private String failWith(TemplateLoader tl) {
-        return failWith(tl, "missing.ftl", new Configuration(Configuration.VERSION_3_0_0));
+
+    private String failWith(TemplateLoader tl, String name) {
+        return failWith(tl, name, null);
     }
 
-    private String failWith(Configuration cfg) {
-        return failWith(null, "missing.ftl", cfg);
+    private String failWith(TemplateLoader tl) {
+        return failWith(tl, "missing.ftl", null);
     }
 
-    private String failWith(String name, Configuration cfg) {
-        return failWith(null, name, cfg);
+    @SuppressWarnings("serial")
+    private static final class DomainLookupCondition implements Serializable {
+        @Override
+        public String toString() {
+            return "example.com";
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java b/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
index 54f2a36..a0174b3 100644
--- a/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
+++ b/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
@@ -29,6 +29,7 @@ import org.apache.freemarker.core.model.TemplateDirectiveBody;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,7 +39,7 @@ public class TheadInterruptingSupportTest {
     private static final Logger LOG = LoggerFactory.getLogger(TheadInterruptingSupportTest.class);
 
     private static final int TEMPLATE_INTERRUPTION_TIMEOUT = 5000;
-    private final Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+    private final Configuration cfg = new TestConfigurationBuilder().build();
 
     @Test
     public void test() throws IOException, InterruptedException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/UnclosedCommentTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/UnclosedCommentTest.java b/src/test/java/org/apache/freemarker/core/UnclosedCommentTest.java
index df8ae38..99bf5d3 100644
--- a/src/test/java/org/apache/freemarker/core/UnclosedCommentTest.java
+++ b/src/test/java/org/apache/freemarker/core/UnclosedCommentTest.java
@@ -28,7 +28,6 @@ public class UnclosedCommentTest extends TemplateTest {
     
     @Test
     public void test() throws IOException, TemplateException {
-        setConfiguration(new Configuration(Configuration.VERSION_3_0_0));
         assertErrorContains("foo<#--", "end of file");  // Not too good...
         assertErrorContains("foo<#-- ", "Unclosed", "<#--");
         assertErrorContains("foo<#--bar", "Unclosed", "<#--");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/WhitespaceStrippingTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/WhitespaceStrippingTest.java b/src/test/java/org/apache/freemarker/core/WhitespaceStrippingTest.java
index 604bb48..8e5b52e 100644
--- a/src/test/java/org/apache/freemarker/core/WhitespaceStrippingTest.java
+++ b/src/test/java/org/apache/freemarker/core/WhitespaceStrippingTest.java
@@ -22,17 +22,11 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class WhitespaceStrippingTest extends TemplateTest {
 
-    private final Configuration cfgStripWS = new Configuration(Configuration.VERSION_3_0_0);
-    
-    private final Configuration cfgNoStripWS = new Configuration(Configuration.VERSION_3_0_0);
-    {
-        cfgNoStripWS.setWhitespaceStripping(false);
-    }
-
     @Test
     public void testBasics() throws Exception {
         assertOutput("<#assign x = 1>\n<#assign y = 2>\n${x}\n${y}", "1\n2", "\n\n1\n2");
@@ -59,10 +53,10 @@ public class WhitespaceStrippingTest extends TemplateTest {
     
     private void assertOutput(String ftl, String expectedOutStripped, String expectedOutNonStripped)
             throws IOException, TemplateException {
-        setConfiguration(cfgStripWS);
+        setConfiguration(new TestConfigurationBuilder().build());
         assertOutput(ftl, expectedOutStripped);
         
-        setConfiguration(cfgNoStripWS);
+        setConfiguration(new TestConfigurationBuilder().whitespaceStripping(false).build());
         assertOutput(ftl, expectedOutNonStripped);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
index 93d8a31..6e9ae25 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
@@ -68,6 +68,7 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
@@ -242,6 +243,7 @@ public class DefaultObjectWrapperTest {
         assertRoundtrip(ow, linkedList, DefaultListAdapter.class, LinkedList.class, linkedList.toString());
         assertRoundtrip(ow, intArray, DefaultArrayAdapter.class, int[].class, null);
         assertRoundtrip(ow, stringArray, DefaultArrayAdapter.class, String[].class, null);
+        assertRoundtrip(ow, pureIterable, DefaultIterableAdapter.class, PureIterable.class, pureIterable.toString());
         assertRoundtrip(ow, hashSet, DefaultNonListCollectionAdapter.class, HashSet.class, hashSet.toString());
     }
 
@@ -452,7 +454,7 @@ public class DefaultObjectWrapperTest {
             String expectedPojoToString)
             throws TemplateModelException {
         final TemplateModel objTM = dow.wrap(obj);
-        assertTrue(expectedTMClass.isAssignableFrom(objTM.getClass()));
+        assertThat(objTM.getClass(), typeCompatibleWith(expectedTMClass));
 
         final TemplateHashModel testBeanTM = (TemplateHashModel) dow.wrap(new RoundtripTesterBean());
 
@@ -460,7 +462,7 @@ public class DefaultObjectWrapperTest {
             TemplateMethodModelEx getClassM = (TemplateMethodModelEx) testBeanTM.get("getClass");
             Object r = getClassM.exec(Collections.singletonList(objTM));
             final Class rClass = (Class) ((WrapperTemplateModel) r).getWrappedObject();
-            assertTrue(rClass + " instanceof " + expectedPojoClass, expectedPojoClass.isAssignableFrom(rClass));
+            assertThat(rClass, typeCompatibleWith(expectedPojoClass));
         }
 
         if (expectedPojoToString != null) {
@@ -758,9 +760,10 @@ public class DefaultObjectWrapperTest {
     
     private String processTemplate(ObjectWrapper objectWrapper, Object value, String ftl)
             throws TemplateException, IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLogTemplateExceptions(false);
-        cfg.setObjectWrapper(objectWrapper);
+        Configuration cfg = new TestConfigurationBuilder()
+                .logTemplateExceptions(false)
+                .objectWrapper(objectWrapper)
+                .build();
         StringWriter out = new StringWriter();
         new Template(null, ftl, cfg).process(ImmutableMap.of("value", value), out);
         return out.toString();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java b/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java
index e402ba2..a7063fd 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java
@@ -37,6 +37,7 @@ import org.apache.freemarker.test.MonitoredTemplateLoader;
 import org.apache.freemarker.test.MonitoredTemplateLoader.CloseSessionEvent;
 import org.apache.freemarker.test.MonitoredTemplateLoader.CreateSessionEvent;
 import org.apache.freemarker.test.MonitoredTemplateLoader.LoadEvent;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
@@ -48,10 +49,12 @@ public class DefaultTemplateResolverTest {
     public void testCachedException() throws Exception {
         MockTemplateLoader loader = new MockTemplateLoader();
         DefaultTemplateResolver tr = new DefaultTemplateResolver(
-                loader, new StrongCacheStorage(),
-                DefaultTemplateLookupStrategy.INSTANCE, DefaultTemplateNameFormat.INSTANCE,
-                new Configuration(Configuration.VERSION_3_0_0));
-        tr.setTemplateUpdateDelayMilliseconds(1000L);
+                loader,
+                new StrongCacheStorage(), 1000L,
+                DefaultTemplateLookupStrategy.INSTANCE, true,
+                DefaultTemplateNameFormat.INSTANCE,
+                null,
+                new TestConfigurationBuilder().build());
         loader.setThrowException(true);
         try {
             tr.getTemplate("t", Locale.getDefault(), null).getTemplate();
@@ -86,10 +89,11 @@ public class DefaultTemplateResolverTest {
     public void testCachedNotFound() throws Exception {
         MockTemplateLoader loader = new MockTemplateLoader();
         DefaultTemplateResolver cache = new DefaultTemplateResolver(
-                loader, new StrongCacheStorage(), DefaultTemplateLookupStrategy.INSTANCE,
-                DefaultTemplateNameFormat.INSTANCE, new Configuration());
-        cache.setTemplateUpdateDelayMilliseconds(1000L);
-        cache.setLocalizedLookup(false);
+                loader,
+                new StrongCacheStorage(), 1000L,
+                DefaultTemplateLookupStrategy.INSTANCE, false,
+                DefaultTemplateNameFormat.INSTANCE,
+                null, new TestConfigurationBuilder().build());
         assertNull(cache.getTemplate("t", Locale.getDefault(), null).getTemplate());
         assertEquals(1, loader.getLoadAttemptCount());
         assertNull(cache.getTemplate("t", Locale.getDefault(), null).getTemplate());
@@ -136,13 +140,14 @@ public class DefaultTemplateResolverTest {
     }
     
     @Test
-    public void testManualRemovalPlain() throws IOException {
-        Configuration cfg = new Configuration();
-        cfg.setCacheStorage(new StrongCacheStorage());
+    public void testManualRemovalPlain() throws Exception {
         StringTemplateLoader loader = new StringTemplateLoader();
-        cfg.setTemplateLoader(loader);
-        cfg.setTemplateUpdateDelayMilliseconds(Integer.MAX_VALUE);
-        
+        Configuration cfg = new TestConfigurationBuilder()
+                .cacheStorage(new StrongCacheStorage())
+                .templateLoader(loader)
+                .templateUpdateDelayMilliseconds(Long.MAX_VALUE)
+                .build();
+
         loader.putTemplate("1.ftl", "1 v1");
         loader.putTemplate("2.ftl", "2 v1");
         assertEquals("1 v1", cfg.getTemplate("1.ftl").toString()); 
@@ -163,14 +168,14 @@ public class DefaultTemplateResolverTest {
     }
 
     @Test
-    public void testManualRemovalI18ed() throws IOException {
-        Configuration cfg = new Configuration();
-        cfg.setCacheStorage(new StrongCacheStorage());
-        cfg.setLocale(Locale.US);
+    public void testManualRemovalI18ed() throws Exception {
         StringTemplateLoader loader = new StringTemplateLoader();
-        cfg.setTemplateLoader(loader);
-        cfg.setTemplateUpdateDelayMilliseconds(Integer.MAX_VALUE);
-        
+        Configuration cfg = new TestConfigurationBuilder()
+                .cacheStorage(new StrongCacheStorage())
+                .templateLoader(loader)
+                .templateUpdateDelayMilliseconds(Long.MAX_VALUE)
+                .build();
+
         loader.putTemplate("1_en_US.ftl", "1_en_US v1");
         loader.putTemplate("1_en.ftl", "1_en v1");
         loader.putTemplate("1.ftl", "1 v1");
@@ -204,13 +209,15 @@ public class DefaultTemplateResolverTest {
     }
 
     @Test
-    public void testZeroUpdateDelay() throws IOException, InterruptedException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        cfg.setCacheStorage(new StrongCacheStorage());
+    public void testZeroUpdateDelay() throws Exception {
         MonitoredTemplateLoader loader = new MonitoredTemplateLoader();
-        cfg.setTemplateLoader(loader);
-        cfg.setTemplateUpdateDelayMilliseconds(0);
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder()
+                .cacheStorage(new StrongCacheStorage())
+                .templateLoader(loader)
+                .templateUpdateDelayMilliseconds(0);
+
+        Configuration cfg = cfgB.build();
+
         for (int i = 1; i <= 3; i++) {
             loader.putTextTemplate("t.ftl", "v" + i);
             assertEquals("v" + i, cfg.getTemplate("t.ftl").toString());
@@ -243,7 +250,7 @@ public class DefaultTemplateResolverTest {
                 ),
                 loader.getEvents(LoadEvent.class));
         
-        cfg.setLocalizedLookup(false);
+        cfg = cfgB.localizedLookup(false).build();
         loader.clearEvents();
         loader.putTextTemplate("t.ftl", "v10");
         assertEquals("v10", cfg.getTemplate("t.ftl").toString());
@@ -270,18 +277,14 @@ public class DefaultTemplateResolverTest {
     }
     
     @Test
-    public void testWrongEncodingReload() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        cfg.setSourceEncoding(StandardCharsets.UTF_8);
-        
-        MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
-        tl.putBinaryTemplate("utf-8_en.ftl", "<#ftl encoding='utf-8'>Béka");
-        tl.putBinaryTemplate("utf-8.ftl", "Bar");
-        tl.putBinaryTemplate("iso-8859-1_en_US.ftl", "<#ftl encoding='ISO-8859-1'>Béka", StandardCharsets.ISO_8859_1,
-                "v1");
-        cfg.setTemplateLoader(tl);
-        
+    public void testWrongEncodingReload() throws Exception {
+        MonitoredTemplateLoader loader = new MonitoredTemplateLoader();
+        loader.putBinaryTemplate("utf-8_en.ftl", "<#ftl encoding='utf-8'>Béka");
+        loader.putBinaryTemplate("utf-8.ftl", "Bar");
+        loader.putBinaryTemplate("iso-8859-1_en_US.ftl", "<#ftl encoding='ISO-8859-1'>Béka",
+                StandardCharsets.ISO_8859_1, "v1");
+        Configuration cfg = new TestConfigurationBuilder().templateLoader(loader).build();
+
         {
             Template t = cfg.getTemplate("utf-8.ftl");
             assertEquals("utf-8.ftl", t.getLookupName());
@@ -295,11 +298,11 @@ public class DefaultTemplateResolverTest {
                             new LoadEvent("utf-8_en_US.ftl", TemplateLoadingResultStatus.NOT_FOUND),
                             new LoadEvent("utf-8_en.ftl", TemplateLoadingResultStatus.OPENED),
                             CloseSessionEvent.INSTANCE),
-                    tl.getEvents());
+                    loader.getEvents());
         }
 
         {
-            tl.clearEvents();
+            loader.clearEvents();
             
             Template t = cfg.getTemplate("iso-8859-1.ftl");
             assertEquals("iso-8859-1.ftl", t.getLookupName());
@@ -312,20 +315,16 @@ public class DefaultTemplateResolverTest {
                             CreateSessionEvent.INSTANCE,
                             new LoadEvent("iso-8859-1_en_US.ftl", TemplateLoadingResultStatus.OPENED),
                             CloseSessionEvent.INSTANCE),
-                    tl.getEvents());
+                    loader.getEvents());
         }
     }
 
     @Test
-    public void testNoWrongEncodingForTemplateLoader2WithReader() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setLocale(Locale.US);
-        cfg.setSourceEncoding(StandardCharsets.UTF_8);
-        
-        MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
-        tl.putTextTemplate("foo_en.ftl", "<#ftl encoding='utf-8'>Å‘");
-        tl.putTextTemplate("foo.ftl", "B");
-        cfg.setTemplateLoader(tl);
+    public void testNoWrongEncodingForTemplateLoader2WithReader() throws Exception {
+        MonitoredTemplateLoader loader = new MonitoredTemplateLoader();
+        loader.putTextTemplate("foo_en.ftl", "<#ftl encoding='utf-8'>Å‘");
+        loader.putTextTemplate("foo.ftl", "B");
+        Configuration cfg = new TestConfigurationBuilder().templateLoader(loader).build();
         
         {
             Template t = cfg.getTemplate("foo.ftl");
@@ -340,14 +339,15 @@ public class DefaultTemplateResolverTest {
                             new LoadEvent("foo_en_US.ftl", TemplateLoadingResultStatus.NOT_FOUND),
                             new LoadEvent("foo_en.ftl", TemplateLoadingResultStatus.OPENED),
                             CloseSessionEvent.INSTANCE),                
-                    tl.getEvents());
+                    loader.getEvents());
         }
     }
 
     @Test
-    public void testTemplateNameFormatException() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+    public void testTemplateNameFormatException() throws Exception {
+        Configuration cfg = new TestConfigurationBuilder()
+                .templateNameFormat(DefaultTemplateNameFormat.INSTANCE)
+                .build();
         try {
             cfg.getTemplate("../x");
             fail();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java b/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
index 0b5861d..8efb83c 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
@@ -29,6 +29,7 @@ import org.apache.commons.lang.SystemUtils;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateNotFoundException;
 import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -39,7 +40,7 @@ public class FileTemplateLoaderTest {
     
     private File templateRootDir;
     
-    private Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+    private Configuration cfg;
     
     @Before
     public void setup() throws IOException {
@@ -51,8 +52,8 @@ public class FileTemplateLoaderTest {
         }
         File tFile = new File(sub2Dir, "t.ftl");
         FileUtils.write(tFile, "foo");
-        
-        cfg.setDirectoryForTemplateLoading(templateRootDir);
+
+        cfg = new TestConfigurationBuilder().templateLoader(new FileTemplateLoader(templateRootDir)).build();
     }
     
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 727faa1..a7259d8 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
@@ -25,14 +25,11 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.junit.Test;
 
 public class TemplateConfigurationFactoryTest {
     
-    private Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-
     @Test
     public void testCondition1() throws IOException, TemplateConfigurationFactoryException {
         TemplateConfiguration tc = newTemplateConfiguration(1);
@@ -168,7 +165,7 @@ public class TemplateConfigurationFactoryTest {
     private void assertApplicable(TemplateConfigurationFactory tcf, String sourceName, TemplateConfiguration... expectedTCs)
             throws IOException, TemplateConfigurationFactoryException {
         TemplateConfiguration mergedTC = tcf.get(sourceName, DummyTemplateLoadingSource.INSTANCE);
-        List<Object> mergedTCAttNames = new ArrayList<Object>(mergedTC.getCustomAttributes().keySet());
+        List<Object> mergedTCAttNames = new ArrayList<>(mergedTC.getCustomAttributes().keySet());
 
         for (TemplateConfiguration expectedTC : expectedTCs) {
             Integer tcId = (Integer) expectedTC.getCustomAttribute("id");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/templateresolver/TemplateNameFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateNameFormatTest.java b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateNameFormatTest.java
index 013fd64..3a24bfc 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/TemplateNameFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/TemplateNameFormatTest.java
@@ -33,6 +33,7 @@ import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
 import org.apache.freemarker.test.MonitoredTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -224,11 +225,10 @@ public class TemplateNameFormatTest {
     
     @Test
     public void assertBackslashNotSpecialWith23() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("foo\\bar.ftl", "");
-        cfg.setTemplateLoader(tl);
+
+        Configuration cfg = new TestConfigurationBuilder().templateLoader(tl).build();
 
         {
             final String name = "foo\\bar.ftl";
@@ -274,9 +274,10 @@ public class TemplateNameFormatTest {
 
     @Test
     public void assertBackslashNotAllowed() throws IOException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setTemplateLoader(new ByteArrayTemplateLoader());
-        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+        Configuration cfg = new TestConfigurationBuilder()
+                .templateLoader(new ByteArrayTemplateLoader())
+                .templateNameFormat(DefaultTemplateNameFormat.INSTANCE)
+                .build();
         try {
             cfg.getTemplate("././foo\\bar.ftl", Locale.US);
             fail();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
index 327bd94..4f22d73 100644
--- a/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
@@ -41,13 +41,14 @@ import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
 import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory;
 import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory;
 import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFactory;
 import org.apache.freemarker.core.userpkg.PrintfGTemplateNumberFormatFactory;
 import org.apache.freemarker.core.valueformat.impl.AliasTemplateNumberFormatFactory;
 import org.apache.freemarker.test.TemplateTest;
-import org.junit.Before;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -56,28 +57,16 @@ import com.google.common.collect.ImmutableMap;
 @SuppressWarnings("boxing")
 public class NumberFormatTest extends TemplateTest {
     
-    @Before
-    public void setup() {
-        Configuration cfg = getConfiguration();
-        cfg.setLocale(Locale.US);
-        
-        cfg.setCustomNumberFormats(ImmutableMap.of(
-                "hex", HexTemplateNumberFormatFactory.INSTANCE,
-                "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
-                "base", BaseNTemplateNumberFormatFactory.INSTANCE,
-                "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE));
-    }
-
     @Test
     public void testUnknownCustomFormat() throws Exception {
         {
-            getConfiguration().setNumberFormat("@noSuchFormat");
+            setConfigurationWithNumberFormat("@noSuchFormat");
             Throwable exc = assertErrorContains("${1}", "\"@noSuchFormat\"", "\"noSuchFormat\"");
             assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
         }
 
         {
-            getConfiguration().setNumberFormat("number");
+            setConfigurationWithNumberFormat("number");
             Throwable exc = assertErrorContains("${1?string('@noSuchFormat2')}",
                     "\"@noSuchFormat2\"", "\"noSuchFormat2\"");
             assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
@@ -86,17 +75,19 @@ public class NumberFormatTest extends TemplateTest {
     
     @Test
     public void testStringBI() throws Exception {
+        setConfigurationWithNumberFormat(null);
         assertOutput("${11} ${11?string.@hex} ${12} ${12?string.@hex}", "11 b 12 c");
     }
 
     @Test
     public void testSetting() throws Exception {
-        getConfiguration().setNumberFormat("@hex");
+        setConfigurationWithNumberFormat("@hex");
         assertOutput("${11?string.number} ${11} ${12?string.number} ${12}", "11 b 12 c");
     }
 
     @Test
     public void testSetting2() throws Exception {
+        setConfigurationWithNumberFormat(null);
         assertOutput(
                 "<#setting numberFormat='@hex'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}"
                 + "<#setting numberFormat='@loc'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}",
@@ -106,43 +97,40 @@ public class NumberFormatTest extends TemplateTest {
     
     @Test
     public void testUnformattableNumber() throws Exception {
-        getConfiguration().setNumberFormat("@hex");
+        setConfigurationWithNumberFormat("@hex");
         assertErrorContains("${1.1}", "hexadecimal int", "doesn't fit into an int");
     }
 
     @Test
     public void testLocaleSensitive() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setNumberFormat("@loc");
+        setConfigurationWithNumberFormat("@loc");
         assertOutput("${1.1}", "1.1_en_US");
-        cfg.setLocale(Locale.GERMANY);
+        setConfigurationWithNumberFormat("@loc", null, null, Locale.GERMANY);
         assertOutput("${1.1}", "1.1_de_DE");
     }
 
     @Test
     public void testLocaleSensitive2() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setNumberFormat("@loc");
+        setConfigurationWithNumberFormat("@loc");
         assertOutput("${1.1} <#setting locale='de_DE'>${1.1}", "1.1_en_US 1.1_de_DE");
     }
 
     @Test
     public void testCustomParameterized() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setNumberFormat("@base 2");
+        setConfigurationWithNumberFormat("@base 2");
         assertOutput("${11}", "1011");
         assertOutput("${11?string}", "1011");
         assertOutput("${11?string.@base_3}", "102");
         
         assertErrorContains("${11?string.@base_xyz}", "\"@base_xyz\"", "\"xyz\"");
-        cfg.setNumberFormat("@base");
+        setConfigurationWithNumberFormat("@base");
         assertErrorContains("${11}", "\"@base\"", "format parameter is required");
     }
 
     @Test
     public void testCustomWithFallback() throws Exception {
         Configuration cfg = getConfiguration();
-        cfg.setNumberFormat("@base 2|0.0#");
+        setConfigurationWithNumberFormat("@base 2|0.0#");
         assertOutput("${11}", "1011");
         assertOutput("${11.34}", "11.34");
         assertOutput("${11?string('@base 3|0.00')}", "102");
@@ -151,6 +139,8 @@ public class NumberFormatTest extends TemplateTest {
 
     @Test
     public void testEnvironmentGetters() throws Exception {
+        setConfigurationWithNumberFormat(null);
+
         Template t = new Template(null, "", getConfiguration());
         Environment env = t.createProcessingEnvironment(null, null);
         
@@ -220,38 +210,43 @@ public class NumberFormatTest extends TemplateTest {
     @Test
     public void testAtPrefix() throws Exception {
         Configuration cfg = getConfiguration();
-        
-        cfg.setNumberFormat("@hex");
+
+        setConfigurationWithNumberFormat("@hex");
         assertOutput("${10}", "a");
-        cfg.setNumberFormat("'@'0");
+        setConfigurationWithNumberFormat("'@'0");
         assertOutput("${10}", "@10");
-        cfg.setNumberFormat("@@0");
+        setConfigurationWithNumberFormat("@@0");
         assertOutput("${10}", "@@10");
-        
-        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        cfg.setNumberFormat("@hex");
+
+        setConfigurationWithNumberFormat(
+                "@hex", Collections.<String, TemplateNumberFormatFactory>emptyMap());
         assertErrorContains("${10}", "custom", "\"hex\"");
-        cfg.setNumberFormat("'@'0");
+
+        setConfigurationWithNumberFormat(
+                "'@'0", Collections.<String, TemplateNumberFormatFactory>emptyMap());
         assertOutput("${10}", "@10");
-        cfg.setNumberFormat("@@0");
+
+        setConfigurationWithNumberFormat(
+                "@@0", Collections.<String, TemplateNumberFormatFactory>emptyMap());
         assertOutput("${10}", "@@10");
     }
 
     @Test
     public void testAlieses() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setCustomNumberFormats(ImmutableMap.of(
-                "f", new AliasTemplateNumberFormatFactory("0.#'f'"),
-                "d", new AliasTemplateNumberFormatFactory("0.0#"),
-                "hex", HexTemplateNumberFormatFactory.INSTANCE));
-        
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setCustomNumberFormats(ImmutableMap.of(
-                "d", new AliasTemplateNumberFormatFactory("0.#'d'"),
-                "i", new AliasTemplateNumberFormatFactory("@hex")));
-        cfg.setTemplateConfigurations(
-                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tcb.build()));
-        
+        setConfigurationWithNumberFormat(
+                "'@'0",
+                ImmutableMap.of(
+                        "f", new AliasTemplateNumberFormatFactory("0.#'f'"),
+                        "d", new AliasTemplateNumberFormatFactory("0.0#"),
+                        "hex", HexTemplateNumberFormatFactory.INSTANCE),
+                new ConditionalTemplateConfigurationFactory(
+                        new FileNameGlobMatcher("*2*"),
+                        new TemplateConfiguration.Builder()
+                                .customNumberFormats(ImmutableMap.of(
+                                        "d", new AliasTemplateNumberFormatFactory("0.#'d'"),
+                                        "i", new AliasTemplateNumberFormatFactory("@hex")))
+                                .build()));
+
         String commonFtl = "${1?string.@f} ${1?string.@d} "
                 + "<#setting locale='fr_FR'>${1.5?string.@d} "
                 + "<#attempt>${10?string.@i}<#recover>E</#attempt>";
@@ -264,14 +259,14 @@ public class NumberFormatTest extends TemplateTest {
 
     @Test
     public void testAlieses2() throws Exception {
-        Configuration cfg = getConfiguration();
-        cfg.setCustomNumberFormats(ImmutableMap.of(
-                "n", new AliasTemplateNumberFormatFactory("0.0",
-                        ImmutableMap.of(
-                                new Locale("en"), "0.0'_en'",
-                                Locale.UK, "0.0'_en_GB'",
-                                Locale.FRANCE, "0.0'_fr_FR'"))));
-        cfg.setNumberFormat("@n");
+        setConfigurationWithNumberFormat(
+                "@n",
+                ImmutableMap.of(
+                        "n", new AliasTemplateNumberFormatFactory("0.0",
+                                ImmutableMap.of(
+                                        new Locale("en"), "0.0'_en'",
+                                        Locale.UK, "0.0'_en_GB'",
+                                        Locale.FRANCE, "0.0'_fr_FR'"))));
         assertOutput(
                 "<#setting locale='en_US'>${1} "
                 + "<#setting locale='en_GB'>${1} "
@@ -283,7 +278,7 @@ public class NumberFormatTest extends TemplateTest {
     
     @Test
     public void testMarkupFormat() throws IOException, TemplateException {
-        getConfiguration().setNumberFormat("@printfG_3");
+        setConfigurationWithNumberFormat("@printfG_3");
 
         String commonFTL = "${1234567} ${'cat:' + 1234567} ${0.0000123}";
         String commonOutput = "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>";
@@ -298,6 +293,7 @@ public class NumberFormatTest extends TemplateTest {
 
     @Test
     public void testPrintG() throws IOException, TemplateException {
+        setConfigurationWithNumberFormat(null);
         for (Number n : new Number[] {
                 1234567, 1234567L, 1234567d, 1234567f, BigInteger.valueOf(1234567), BigDecimal.valueOf(1234567) }) {
             addToDataModel("n", n);
@@ -308,7 +304,49 @@ public class NumberFormatTest extends TemplateTest {
             assertOutput("${0.0000123?string.@printfG}", "1.23000E-05");
         }
     }
-    
+
+    private void setConfigurationWithNumberFormat(
+            String numberFormat,
+            Map<String, TemplateNumberFormatFactory> customNumberFormats,
+            TemplateConfigurationFactory templateConfigurationFactory,
+            Locale locale) {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(Configuration.VERSION_3_0_0);
+
+        if (numberFormat != null) {
+            cfgB.setNumberFormat(numberFormat);
+        }
+        cfgB.setCustomNumberFormats(
+                customNumberFormats != null ? customNumberFormats
+                        : ImmutableMap.of(
+                                "hex", HexTemplateNumberFormatFactory.INSTANCE,
+                                "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
+                                "base", BaseNTemplateNumberFormatFactory.INSTANCE,
+                                "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE));
+        if (locale != null) {
+            cfgB.setLocale(locale);
+        }
+        if (templateConfigurationFactory != null) {
+            cfgB.setTemplateConfigurations(templateConfigurationFactory);
+        }
+
+        setConfiguration(cfgB.build());
+    }
+
+    private void setConfigurationWithNumberFormat(String numberFormat) {
+        setConfigurationWithNumberFormat(numberFormat, null, null, null);
+    }
+
+    private void setConfigurationWithNumberFormat(
+            String numberFormat, Map<String, TemplateNumberFormatFactory> customNumberFormats) {
+        setConfigurationWithNumberFormat(numberFormat, customNumberFormats, null, null);
+    }
+
+    private void setConfigurationWithNumberFormat(
+            String numberFormat, Map<String, TemplateNumberFormatFactory> customNumberFormats,
+            TemplateConfigurationFactory templateConfigurationFactory) {
+        setConfigurationWithNumberFormat(numberFormat, customNumberFormats, templateConfigurationFactory, null);
+    }
+
     private static class MutableTemplateNumberModel implements TemplateNumberModel {
         
         private Number number;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java b/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java
index 9625a5e..76c0bfc 100644
--- a/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java
+++ b/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java
@@ -28,9 +28,9 @@ import java.text.DecimalFormatSymbols;
 import java.text.ParseException;
 import java.util.Locale;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
 public class ExtendedDecimalFormatTest extends TemplateTest {
@@ -276,16 +276,15 @@ public class ExtendedDecimalFormatTest extends TemplateTest {
     
     @Test
     public void testTemplates() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-        cfg.setLocale(Locale.US);
-        
-        cfg.setNumberFormat(",000.#");
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder();
+
+        setConfiguration(cfgB.numberFormat(",000.#").build());
         assertOutput("${1000.15} ${1000.25}", "1,000.2 1,000.2");
-        cfg.setNumberFormat(",000.#;; roundingMode=halfUp groupingSeparator=_");
+        setConfiguration(cfgB.numberFormat(",000.#;; roundingMode=halfUp groupingSeparator=_").build());;
         assertOutput("${1000.15} ${1000.25}", "1_000.2 1_000.3");
-        cfg.setLocale(Locale.GERMANY);
+        setConfiguration(cfgB.locale(Locale.GERMANY).build());;
         assertOutput("${1000.15} ${1000.25}", "1_000,2 1_000,3");
-        cfg.setLocale(Locale.US);
+        setConfiguration(cfgB.locale(Locale.US).build());;
         assertOutput(
                 "${1000.15}; "
                 + "${1000.15?string(',##.#;;groupingSeparator=\" \"')}; "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java b/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java
index e8dedce..036bace 100644
--- a/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java
+++ b/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java
@@ -18,9 +18,12 @@
  */
 package org.apache.freemarker.manualtest;
 
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
-public class AutoEscapingExample extends ExamplesTest {
+public class AutoEscapingExample extends TemplateTest {
 
     @Test
     public void testInfoBox() throws Exception {
@@ -61,5 +64,9 @@ public class AutoEscapingExample extends ExamplesTest {
     public void testStringConcat() throws Exception {
         assertOutputForNamed("AutoEscapingExample-stringConcat.ftlh");
     }
-    
+
+    @Override
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return new TestConfigurationBuilder(AutoEscapingExample.class).build();
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java b/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
index 85ffa81..40c1297 100644
--- a/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
+++ b/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java
@@ -20,7 +20,8 @@ package org.apache.freemarker.manualtest;
 
 import static org.junit.Assert.*;
 
-import org.apache.freemarker.core.Configuration;
+import java.io.IOException;
+
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
@@ -30,87 +31,75 @@ import org.apache.freemarker.core.templateresolver.FileExtensionMatcher;
 import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.OrMatcher;
 import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
-public class ConfigureOutputFormatExamples extends ExamplesTest {
+public class ConfigureOutputFormatExamples extends TemplateTest {
     
     @Test
     public void test() throws Exception {
-        Configuration cfg = getConfiguration();
-        
         addTemplate("mail/t.ftl", "");
         addTemplate("t.html", "");
         addTemplate("t.htm", "");
         addTemplate("t.xml", "");
         addTemplate("t.rtf", "");
-        
-        // Example 2/a:
-        {
-            TemplateConfiguration.Builder tcHTML = new TemplateConfiguration.Builder();
-            tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
-            
-            cfg.setTemplateConfigurations(
-                    new ConditionalTemplateConfigurationFactory(
-                            new PathGlobMatcher("mail/**"),
-                            tcHTML.build()));
-            
-            assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("mail/t.ftl").getOutputFormat());
-        }
 
-        // Example 2/b:
-        {
-            cfg.setTemplateConfigurations(null); // Just to be sure...
-            
-            cfg.setSettings(loadPropertiesFile("ConfigureOutputFormatExamples1.properties"));
-                
-            assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("mail/t.ftl").getOutputFormat());
-        }
-        
-        // Example 3/a:
-        {
-            TemplateConfiguration.Builder tcHTML = new TemplateConfiguration.Builder();
-            tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
-            
-            TemplateConfiguration.Builder tcXML = new TemplateConfiguration.Builder();
-            tcXML.setOutputFormat(XMLOutputFormat.INSTANCE);
+        example2(true);
+        example2(false);
+        example3(true);
+        example3(false);
+    }
 
-            TemplateConfiguration.Builder tcRTF = new TemplateConfiguration.Builder();
-            tcRTF.setOutputFormat(RTFOutputFormat.INSTANCE);
-            
-            cfg.setTemplateConfigurations(
-                    new FirstMatchTemplateConfigurationFactory(
-                            new ConditionalTemplateConfigurationFactory(
-                                    new FileExtensionMatcher("xml"),
-                                    tcXML.build()),
-                            new ConditionalTemplateConfigurationFactory(
-                                    new OrMatcher(
-                                            new FileExtensionMatcher("html"),
-                                            new FileExtensionMatcher("htm")),
-                                    tcHTML.build()),
-                            new ConditionalTemplateConfigurationFactory(
-                                    new FileExtensionMatcher("rtf"),
-                                    tcRTF.build())
-                    ).allowNoMatch(true)
-            );
-            
-            assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.html").getOutputFormat());
-            assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.htm").getOutputFormat());
-            assertEquals(XMLOutputFormat.INSTANCE, cfg.getTemplate("t.xml").getOutputFormat());
-            assertEquals(RTFOutputFormat.INSTANCE, cfg.getTemplate("t.rtf").getOutputFormat());
-        }
+    private void example2(boolean javaCfg) throws IOException {
+        setConfiguration(
+                javaCfg
+                        ? new TestConfigurationBuilder()
+                                .templateConfigurations(
+                                        new ConditionalTemplateConfigurationFactory(
+                                                new PathGlobMatcher("mail/**"),
+                                                new TemplateConfiguration.Builder()
+                                                        .outputFormat(HTMLOutputFormat.INSTANCE)
+                                                        .build()))
+                                .build()
+                        : new TestConfigurationBuilder()
+                                .settings(loadPropertiesFile("ConfigureOutputFormatExamples1.properties"))
+                                .build());
+        assertEquals(HTMLOutputFormat.INSTANCE, getConfiguration().getTemplate("mail/t.ftl").getOutputFormat());
+    }
 
-        // Example 3/b:
-        {
-            cfg.setTemplateConfigurations(null); // Just to be sure...
-            
-            cfg.setSettings(loadPropertiesFile("ConfigureOutputFormatExamples2.properties"));
-            
-            assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.html").getOutputFormat());
-            assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.htm").getOutputFormat());
-            assertEquals(XMLOutputFormat.INSTANCE, cfg.getTemplate("t.xml").getOutputFormat());
-            assertEquals(RTFOutputFormat.INSTANCE, cfg.getTemplate("t.rtf").getOutputFormat());
-        }
-        
+    private void example3(boolean javaCfg) throws IOException {
+        setConfiguration(
+                javaCfg
+                        ? new TestConfigurationBuilder()
+                                .templateConfigurations(
+                                        new FirstMatchTemplateConfigurationFactory(
+                                                new ConditionalTemplateConfigurationFactory(
+                                                        new FileExtensionMatcher("xml"),
+                                                        new TemplateConfiguration.Builder()
+                                                                .outputFormat(XMLOutputFormat.INSTANCE)
+                                                                .build()),
+                                                new ConditionalTemplateConfigurationFactory(
+                                                        new OrMatcher(
+                                                                new FileExtensionMatcher("html"),
+                                                                new FileExtensionMatcher("htm")),
+                                                        new TemplateConfiguration.Builder()
+                                                                .outputFormat(HTMLOutputFormat.INSTANCE)
+                                                                .build()),
+                                                new ConditionalTemplateConfigurationFactory(
+                                                        new FileExtensionMatcher("rtf"),
+                                                        new TemplateConfiguration.Builder()
+                                                                .outputFormat(RTFOutputFormat.INSTANCE)
+                                                                .build()))
+                                        .allowNoMatch(true))
+                                .build()
+                        : new TestConfigurationBuilder()
+                                .settings(loadPropertiesFile("ConfigureOutputFormatExamples2.properties"))
+                                .build());
+        assertEquals(HTMLOutputFormat.INSTANCE, getConfiguration().getTemplate("t.html").getOutputFormat());
+        assertEquals(HTMLOutputFormat.INSTANCE, getConfiguration().getTemplate("t.htm").getOutputFormat());
+        assertEquals(XMLOutputFormat.INSTANCE, getConfiguration().getTemplate("t.xml").getOutputFormat());
+        assertEquals(RTFOutputFormat.INSTANCE, getConfiguration().getTemplate("t.rtf").getOutputFormat());
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java b/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java
index 212129c..5da6615 100644
--- a/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java
+++ b/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java
@@ -21,36 +21,31 @@ package org.apache.freemarker.manualtest;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory;
-import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
-import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 import org.apache.freemarker.core.valueformat.impl.AliasTemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.impl.AliasTemplateNumberFormatFactory;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableMap;
+
 @SuppressWarnings("boxing")
-public class CustomFormatsExample extends ExamplesTest {
+public class CustomFormatsExample extends TemplateTest {
 
     @Test
     public void aliases1() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-
-        Map<String, TemplateNumberFormatFactory> customNumberFormats
-                = new HashMap<>();
-        customNumberFormats.put("price", new AliasTemplateNumberFormatFactory(",000.00"));
-        customNumberFormats.put("weight", new AliasTemplateNumberFormatFactory("0.##;; roundingMode=halfUp"));
-        cfg.setCustomNumberFormats(customNumberFormats);
-
-        Map<String, TemplateDateFormatFactory> customDateFormats
-                = new HashMap<>();
-        customDateFormats.put("fileDate", new AliasTemplateDateFormatFactory("dd/MMM/yy hh:mm a"));
-        customDateFormats.put("logEventTime", new AliasTemplateDateFormatFactory("iso ms u"));
-        cfg.setCustomDateFormats(customDateFormats);
+        setConfiguration(new TestConfigurationBuilder(this.getClass())
+            .customNumberFormats(ImmutableMap.of(
+                    "price", new AliasTemplateNumberFormatFactory(",000.00"),
+                    "weight", new AliasTemplateNumberFormatFactory("0.##;; roundingMode=halfUp")))
+            .customDateFormats(ImmutableMap.of(
+                    "fileDate", new AliasTemplateDateFormatFactory("dd/MMM/yy hh:mm a"),
+                    "logEventTime", new AliasTemplateDateFormatFactory("iso ms u")
+                    ))
+            .build());
 
         addToDataModel("p", 10000);
         addToDataModel("w", new BigDecimal("10.305"));
@@ -62,26 +57,22 @@ public class CustomFormatsExample extends ExamplesTest {
 
     @Test
     public void aliases2() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
+        setConfiguration(new TestConfigurationBuilder(this.getClass())
+                .customNumberFormats(ImmutableMap.of(
+                        "base", BaseNTemplateNumberFormatFactory.INSTANCE,
+                        "oct", new AliasTemplateNumberFormatFactory("@base 8")))
+                .build());
 
-        Map<String, TemplateNumberFormatFactory> customNumberFormats
-                = new HashMap<>();
-        customNumberFormats.put("base", BaseNTemplateNumberFormatFactory.INSTANCE);
-        customNumberFormats.put("oct", new AliasTemplateNumberFormatFactory("@base 8"));
-        cfg.setCustomNumberFormats(customNumberFormats);
-        
         assertOutputForNamed("CustomFormatsExample-alias2.ftlh");
     }
 
     @Test
     public void modelAware() throws IOException, TemplateException {
-        Configuration cfg = getConfiguration();
-
-        Map<String, TemplateNumberFormatFactory> customNumberFormats
-                = new HashMap<>();
-        customNumberFormats.put("ua", UnitAwareTemplateNumberFormatFactory.INSTANCE);
-        cfg.setCustomNumberFormats(customNumberFormats);
-        cfg.setNumberFormat("@ua 0.####;; roundingMode=halfUp");
+        setConfiguration(new TestConfigurationBuilder(this.getClass())
+                .customNumberFormats(ImmutableMap.of(
+                        "ua", UnitAwareTemplateNumberFormatFactory.INSTANCE))
+                .numberFormat("@ua 0.####;; roundingMode=halfUp")
+                .build());
 
         addToDataModel("weight", new UnitAwareTemplateNumberModel(1.5, "kg"));
         


[08/11] incubator-freemarker git commit: Configuration is now immutable. Instead, you should use Configuration.Builder to set up the setting values, then create the Configuration with the builder's build() method. FreemarkerServlet (including some of its

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java b/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java
index 7be9458..ddeb811 100644
--- a/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java
+++ b/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java
@@ -30,7 +30,7 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 public class NonStringOrTemplateOutputException extends UnexpectedTypeException {
 
     static final String STRING_COERCABLE_TYPES_OR_TOM_DESC
-            = NonStringException.STRING_COERCABLE_TYPES_DESC + ", or \"template output\" ";
+            = NonStringException.STRING_COERCABLE_TYPES_DESC + ", or \"template output\"";
     
     static final Class[] STRING_COERCABLE_TYPES_AND_TOM;
     static {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java b/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
index b62daac..0eb9569 100644
--- a/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
@@ -21,24 +21,73 @@ package org.apache.freemarker.core;
 import java.nio.charset.Charset;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
 
 /**
  * Implemented by FreeMarker core classes (not by you) that provide configuration settings that affect template parsing
  * (as opposed to {@linkplain Template#process (Object, Writer) template processing}). <b>New methods may be added
- * anytime in future FreeMarker versions, so don't try to implement this interface yourself!</b>
+ * any time in future FreeMarker versions, so don't try to implement this interface yourself!</b>
  *
  * @see ProcessingConfiguration
+ * @see ParsingAndProcessingConfiguration
  */
-// TODO Clean up API docs like in ProcessingConfiguration, when Configuration is done
 public interface ParsingConfiguration {
 
+    int AUTO_DETECT_NAMING_CONVENTION = 10;
+    int LEGACY_NAMING_CONVENTION = 11;
+    int CAMEL_CASE_NAMING_CONVENTION = 12;
+
+    int AUTO_DETECT_TAG_SYNTAX = 0;
+    int ANGLE_BRACKET_TAG_SYNTAX = 1;
+    int SQUARE_BRACKET_TAG_SYNTAX = 2;
+
+    /**
+     * Don't enable auto-escaping, regardless of what the {@link OutputFormat} is. Note that a {@code
+     * <#ftl auto_esc=true>} in the template will override this.
+     */
+    int DISABLE_AUTO_ESCAPING_POLICY = 20;
+    /**
+     * Enable auto-escaping if the output format supports it and {@link MarkupOutputFormat#isAutoEscapedByDefault()} is
+     * {@code true}.
+     */
+    int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 21;
+    /** Enable auto-escaping if the {@link OutputFormat} supports it. */
+    int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 22;
+
+    /**
+     * The template language used; this is often overridden for certain file extension with the
+     * {@link Configuration#getTemplateConfigurations() templateConfigurations} setting of the {@link Configuration}.
+     */
     TemplateLanguage getTemplateLanguage();
 
     boolean isTemplateLanguageSet();
 
     /**
-     * See {@link Configuration#getTagSyntax()}.
+     * Determines the syntax of the template files (angle bracket VS square bracket)
+     * that has no {@code #ftl} in it. The {@code tagSyntax}
+     * parameter must be one of:
+     * <ul>
+     *   <li>{@link #AUTO_DETECT_TAG_SYNTAX}:
+     *     use the syntax of the first FreeMarker tag (can be anything, like <tt>#list</tt>,
+     *     <tt>#include</tt>, user defined, etc.)
+     *   <li>{@link #ANGLE_BRACKET_TAG_SYNTAX}:
+     *     use the angle bracket syntax (the normal syntax)
+     *   <li>{@link #SQUARE_BRACKET_TAG_SYNTAX}:
+     *     use the square bracket syntax
+     * </ul>
+     *
+     * <p>In FreeMarker 2.3.x {@link #ANGLE_BRACKET_TAG_SYNTAX} is the
+     * default for better backward compatibility. Starting from 2.4.x {@link
+     * ParsingConfiguration#AUTO_DETECT_TAG_SYNTAX} is the default, so it's recommended to use
+     * that even for 2.3.x.
+     *
+     * <p>This setting is ignored for the templates that have {@code ftl} directive in
+     * it. For those templates the syntax used for the {@code ftl} directive determines
+     * the syntax.
      */
     int getTagSyntax();
 
@@ -50,7 +99,44 @@ public interface ParsingConfiguration {
     boolean isTagSyntaxSet();
 
     /**
-     * See {@link Configuration#getNamingConvention()}.
+     * The naming convention used for the identifiers that are part of the template language. The available naming
+     * conventions are legacy (directive (tag) names are all-lower-case {@code likethis}, others are snake case
+     * {@code like_this}), and camel case ({@code likeThis}). The default is auto-detect, which detects the naming
+     * convention used and enforces that same naming convention for the whole template.
+     *
+     * <p>
+     * This setting doesn't influence what naming convention is used for the setting names outside templates. Also, it
+     * won't ever convert the names of user-defined things, like of data-model members, or the names of user defined
+     * macros/functions. It only influences the names of the built-in directives ({@code #elseIf} VS {@code elseif}),
+     * built-ins ({@code ?upper_case} VS {@code ?upperCase} ), special variables ({@code .data_model} VS
+     * {@code .dataModel}).
+     *
+     * <p>
+     * Which convention to use: FreeMarker prior to 2.3.23 has only supported
+     * {@link #LEGACY_NAMING_CONVENTION}, so that's how most templates and examples out there are written
+     * as of 2015. But as templates today are mostly written by programmers and often access Java API-s which already
+     * use camel case, {@link #CAMEL_CASE_NAMING_CONVENTION} is the recommended option for most projects.
+     * However, it's no necessary to make a application-wide decision; see auto-detection below.
+     *
+     * <p>
+     * FreeMarker will decide the naming convention automatically for each template individually when this setting is
+     * set to {@link #AUTO_DETECT_NAMING_CONVENTION} (which is the default). The naming convention of a template is
+     * decided when the first core (non-user-defined) identifier is met during parsing (not during processing) where the
+     * naming convention is relevant (like for {@code s?upperCase} or {@code s?upper_case} it's relevant, but for
+     * {@code s?length} it isn't). At that point, the naming convention of the template is decided, and any later core
+     * identifier that uses a different convention will be a parsing error. As the naming convention is decided per
+     * template, it's not a problem if a template and the other template it {@code #include}-s/{@code #import} uses a
+     * different convention.
+     *
+     * <p>
+     * FreeMarker always enforces the same naming convention to be used consistently within the same template "file".
+     * Additionally, when this setting is set to non-{@link #AUTO_DETECT_NAMING_CONVENTION}, the selected naming
+     * convention is enforced on all templates. Thus such a setup can be used to enforce an application-wide naming
+     * convention.
+     *
+     * @return
+     *            One of the {@link #AUTO_DETECT_NAMING_CONVENTION} or
+     *            {@link #LEGACY_NAMING_CONVENTION} or {@link #CAMEL_CASE_NAMING_CONVENTION}.
      */
     int getNamingConvention();
 
@@ -62,7 +148,7 @@ public interface ParsingConfiguration {
     boolean isNamingConventionSet();
 
     /**
-     * See {@link Configuration#getWhitespaceStripping()}.
+     * Whether the template parser will try to remove superfluous white-space around certain tags.
      */
     boolean getWhitespaceStripping();
 
@@ -99,7 +185,25 @@ public interface ParsingConfiguration {
     boolean isAutoEscapingPolicySet();
 
     /**
-     * See {@link Configuration#getOutputEncoding()}.
+     * The output format to use, which among others influences auto-escaping (see {@link #getAutoEscapingPolicy}
+     * autoEscapingPolicy}), and possibly the MIME type of the output.
+     * <p>
+     * On the {@link Configuration} level, usually, you should leave this on its default, which is
+     * {@link UndefinedOutputFormat#INSTANCE}, and then use standard file extensions like "ftlh" (for HTML) or "ftlx"
+     * (for XML) (and ensure that {@link #getRecognizeStandardFileExtensions() recognizeStandardFileExtensions} is
+     * {@code true}; see more there). Where you can't use the standard extensions, templates still can be associated
+     * to output formats with patterns matching their name (their path) using the
+     * {@link Configuration#getTemplateConfigurations() templateConfigurations} setting of the {@link Configuration}.
+     * But if all templates will have the same output format, you may set the
+     * {@link #getOutputFormat() outputFormat} setting of the {@link Configuration}
+     * after all, to a value like {@link HTMLOutputFormat#INSTANCE}, {@link XMLOutputFormat#INSTANCE}, etc. Also
+     * note that templates can specify their own output format like {@code <#ftl output_format="HTML">}, which
+     * overrides any configuration settings.
+     *
+     * @see Configuration#getRegisteredCustomOutputFormats()
+     * @see Configuration#getTemplateConfigurations()
+     * @see #getRecognizeStandardFileExtensions()
+     * @see #getAutoEscapingPolicy()
      */
     OutputFormat getOutputFormat();
 
@@ -111,7 +215,29 @@ public interface ParsingConfiguration {
     boolean isOutputFormatSet();
 
     /**
-     * See {@link Configuration#getRecognizeStandardFileExtensions()}.
+     * Tells if the "file" extension part of the source name ({@link Template#getSourceName()}) will influence certain
+     * parsing settings. For backward compatibility, it defaults to {@code false} if
+     * {@link #getIncompatibleImprovements()} is less than 2.3.24. Starting from {@code incompatibleImprovements}
+     * 2.3.24, it defaults to {@code true}, so the following standard file extensions take their effect:
+     *
+     * <ul>
+     *   <li>{@code ftlh}: Sets the {@link #getOutputFormat() outputFormat} setting to {@code "HTML"}
+     *       (i.e., {@link HTMLOutputFormat#INSTANCE}, unless the {@code "HTML"} name is overridden by
+     *       the {@link Configuration#getRegisteredCustomOutputFormats registeredOutputFormats} setting) and
+     *       the {@link #getAutoEscapingPolicy() autoEscapingPolicy} setting to
+     *       {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
+     *   <li>{@code ftlx}: Sets the {@link #getOutputFormat() outputFormat} setting to
+     *       {@code "XML"} (i.e., {@link XMLOutputFormat#INSTANCE}, unless the {@code "XML"} name is overridden by
+     *       the {@link Configuration#getRegisteredCustomOutputFormats registeredOutputFormats} setting) and
+     *       the {@link #getAutoEscapingPolicy() autoEscapingPolicy} setting to
+     *       {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
+     * </ul>
+     *
+     * <p>These file extensions are not case sensitive. The file extension is the part after the last dot in the source
+     * name. If the source name contains no dot, then it has no file extension.
+     *
+     * <p>The settings activated by these file extensions override the setting values dictated by the
+     * {@link Configuration#getTemplateConfigurations templateConfigurations} setting of the {@link Configuration}.
      */
     boolean getRecognizeStandardFileExtensions();
 
@@ -123,15 +249,18 @@ public interface ParsingConfiguration {
     boolean isRecognizeStandardFileExtensionsSet();
 
     /**
-     * See {@link Configuration#getIncompatibleImprovements()}; as this is normally directly delegates to
-     * {@link Configuration#getIncompatibleImprovements()}, it's always set.
+     * See {@link TopLevelConfiguration#getIncompatibleImprovements()}; this is normally directly delegates to
+     * {@link Configuration#getIncompatibleImprovements()}, and it's always set.
      */
     Version getIncompatibleImprovements();
 
     /**
-     * See {@link Configuration#getTabSize()}.
-     * 
-     * @since 2.3.25
+     * The assumed display width of the tab character (ASCII 9), which influences the column number shown in error
+     * messages (or the column number you get through other API-s). So for example if the users edit templates in an
+     * editor where the tab width is set to 4, you should set this to 4 so that the column numbers printed by FreeMarker
+     * will match the column number shown in the editor. This setting doesn't affect the output of templates, as a tab
+     * in the template will remain a tab in the output too.
+     * It's value is at least 1, at most 256.
      */
     int getTabSize();
 
@@ -143,12 +272,28 @@ public interface ParsingConfiguration {
     boolean isTabSizeSet();
 
     /**
-     * Gets the default encoding for converting bytes to characters when
-     * reading template files in a locale for which no explicit encoding
-     * was specified. Defaults to the default system encoding.
+     * Sets the charset used for decoding template files.
+     * <p>
+     * Defaults to the default system {@code fileEncoding}, which can change from one server to
+     * another, so <b>you should always set this setting</b>. If you don't know what charset your should chose,
+     * {@code "UTF-8"} is usually a good choice.
+     * <p>
+     * When a project contains groups (like folders) of template files where the groups use different encodings,
+     * consider using the {@link Configuration#getTemplateConfigurations() templateConfigurations} setting on the
+     * {@link Configuration} level.
+     * <p>
+     * Individual templates may specify their own charset by starting with
+     * <tt>&lt;#ftl sourceEncoding="..."&gt;</tt>. However, before that's detected, at least part of template must be
+     * decoded with some charset first, so this setting (and
+     * {@link Configuration#getTemplateConfigurations() templateConfigurations}) still have role.
      */
     Charset getSourceEncoding();
 
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
     boolean isSourceEncodingSet();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
index e90ceb3..545a313 100644
--- a/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -40,9 +40,10 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 /**
  * Implemented by FreeMarker core classes (not by you) that provide configuration settings that affect {@linkplain
  * Template#process(Object, Writer) template processing} (as opposed to template parsing). <b>New methods may be added
- * anytime in future FreeMarker versions, so don't try to implement this interface yourself!</b>
+ * any time in future FreeMarker versions, so don't try to implement this interface yourself!</b>
  *
  * @see ParsingConfiguration
+ * @see ParsingAndProcessingConfiguration
  */
 public interface ProcessingConfiguration {
 
@@ -608,9 +609,9 @@ public interface ProcessingConfiguration {
      * it only affects the main template directly, as the imports will create a global variable there, the imports
      * will be visible from the further imported templates too.
      * <p>
-     * It's recommended to set the {@code lazy_auto_imports} setting ({@link Configuration#setLazyAutoImports(Boolean)})
-     * to {@code true} when using this, so that auto-imports that are unused in a template won't degrade performance by
-     * unnecessary loading and initializing the imported library.
+     * It's recommended to set the {@link Configuration#getLazyAutoImports() lazyAutoImports} setting to {@code true}
+     * when using this, so that auto-imports that are unused in a template won't degrade performance by unnecessary
+     * loading and initializing the imported library.
      * <p>
      * If the imports aren't lazy, the order of the imports will be the same as the order in which the {@link Map}
      * iterates through its entries.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 23082b2..c4787e4 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -53,11 +53,14 @@ import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
 /**
  * <p>
  * Stores an already parsed template, ready to be processed (rendered) for unlimited times, possibly from multiple
@@ -76,7 +79,7 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * changing FreeMarker settings. Those must not be used while the template is being processed, or if the template object
  * is already accessible from multiple threads. If some templates need different settings that those coming from the
  * 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.
+ * use the {@link Configuration#getTemplateConfigurations() templateConfigurations} setting to achieve that.
  */
 // TODO [FM3] Try to make Template serializable for distributed caching. Transient fields will have to be restored.
 public class Template implements ProcessingConfiguration, CustomStateScope {
@@ -119,7 +122,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
 
     private int actualNamingConvention;
     // Custom state:
-    private final Object lock = new Object();
+    private final Object customStateMapLock = new Object();
     private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0);
 
     /**
@@ -175,7 +178,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
      *            straightforwardly in real files (they often aren't; see {@link TemplateLoader}), the name shouldn't be
      *            an absolute file path. Like if the template is stored in {@code "/www/templates/forum/main.ftl"}, and
      *            you are using {@code "/www/templates/"} as the template root directory via
-     *            {@link Configuration#setDirectoryForTemplateLoading(java.io.File)}, then the template name will be
+     *            {@link FileTemplateLoader#FileTemplateLoader(java.io.File)}, then the template name will be
      *            {@code "forum/main.ftl"}. The name can be {@code null} (should be used for template made on-the-fly
      *            instead of being loaded from somewhere), in which case relative paths in it will be relative to
      *            the template root directory (and here again, it's the {@link TemplateLoader} that knows what that
@@ -393,10 +396,10 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
      *            also use an object that already implements {@link TemplateHashModel}; in that case it won't be
      *            wrapped. If it's {@code null}, an empty data model is used.
      * @param out
-     *            The {@link Writer} where the output of the template will go. Note that unless you have used
-     *            {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at
-     *            the when the template processing was finished. {@link Writer#close()} is not called. Can't be
-     *            {@code null}.
+     *            The {@link Writer} where the output of the template will go. Note that unless you have set
+     *            {@link ProcessingConfiguration#getAutoFlush() autoFlush} to {@code false} to disable this,
+     *            {@link Writer#flush()} will be called at the when the template processing was finished.
+     *            {@link Writer#close()} is not called. Can't be {@code null}.
      * 
      * @throws TemplateException
      *             if an exception occurs during template processing
@@ -656,10 +659,10 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
 
     /**
      * Returns the tag syntax the parser has chosen for this template. If the syntax could be determined, it's
-     * {@link Configuration#SQUARE_BRACKET_TAG_SYNTAX} or {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}. If the syntax
+     * {@link ParsingConfiguration#SQUARE_BRACKET_TAG_SYNTAX} or {@link ParsingConfiguration#ANGLE_BRACKET_TAG_SYNTAX}. If the syntax
      * couldn't be determined (like because there was no tags in the template, or it was a plain text template), this
      * returns whatever the default is in the current configuration, so it's maybe
-     * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}.
+     * {@link ParsingConfiguration#AUTO_DETECT_TAG_SYNTAX}.
      * 
      * @since 2.3.20
      */
@@ -669,10 +672,10 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
     
     /**
      * Returns the naming convention the parser has chosen for this template. If it could be determined, it's
-     * {@link Configuration#LEGACY_NAMING_CONVENTION} or {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it
+     * {@link ParsingConfiguration#LEGACY_NAMING_CONVENTION} or {@link ParsingConfiguration#CAMEL_CASE_NAMING_CONVENTION}. If it
      * couldn't be determined (like because there no identifier that's part of the template language was used where
      * the naming convention matters), this returns whatever the default is in the current configuration, so it's maybe
-     * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}.
+     * {@link ParsingConfiguration#AUTO_DETECT_TAG_SYNTAX}.
      * 
      * @since 2.3.23
      */
@@ -681,7 +684,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
     }
     
     /**
-     * Returns the output format (see {@link Configuration#setOutputFormat(OutputFormat)}) used for this template.
+     * Returns the output format (see {@link Configuration#getOutputFormat()}) used for this template.
      * The output format of a template can come from various places, in order of increasing priority:
      * {@link Configuration#getOutputFormat()}, {@link ParsingConfiguration#getOutputFormat()} (which is usually
      * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's
@@ -701,8 +704,8 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
     }
     
     /**
-     * Returns if the auto-escaping policy (see {@link Configuration#setAutoEscapingPolicy(int)}) that this template
-     * uses. This is decided from these, in increasing priority:
+     * Returns the {@link Configuration#getAutoEscapingPolicy()} autoEscapingPolicy) that this template uses.
+     * This is decided from these, in increasing priority:
      * {@link Configuration#getAutoEscapingPolicy()}, {@link ParsingConfiguration#getAutoEscapingPolicy()},
      * {@code #ftl} header's {@code auto_esc} option in the template.
      */
@@ -1315,10 +1318,11 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
 
     @Override
     @SuppressWarnings("unchecked")
+    @SuppressFBWarnings("AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION")
     public <T> T getCustomState(CustomStateKey<T> customStateKey) {
         T customState = (T) customStateMap.get(customStateKey);
         if (customState == null) {
-            synchronized (lock) {
+            synchronized (customStateMapLock) {
                 customState = (T) customStateMap.get(customStateKey);
                 if (customState == null) {
                     customState = customStateKey.create();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 5f62202..a8fc5ae 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -676,140 +676,145 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         }
 
         @Override
-        protected Locale getInheritedLocale() {
+        protected Locale getDefaultLocale() {
             throw new SettingValueNotSetException("locale");
         }
 
         @Override
-        protected TimeZone getInheritedTimeZone() {
+        protected TimeZone getDefaultTimeZone() {
             throw new SettingValueNotSetException("timeZone");
         }
 
         @Override
-        protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
+        protected TimeZone getDefaultSQLDateAndTimeTimeZone() {
             throw new SettingValueNotSetException("SQLDateAndTimeTimeZone");
         }
 
         @Override
-        protected String getInheritedNumberFormat() {
+        protected String getDefaultNumberFormat() {
             throw new SettingValueNotSetException("numberFormat");
         }
 
         @Override
-        protected Map<String, TemplateNumberFormatFactory> getInheritedCustomNumberFormats() {
+        protected Map<String, TemplateNumberFormatFactory> getDefaultCustomNumberFormats() {
             throw new SettingValueNotSetException("customNumberFormats");
         }
 
         @Override
-        protected TemplateNumberFormatFactory getInheritedCustomNumberFormat(String name) {
+        protected TemplateNumberFormatFactory getDefaultCustomNumberFormat(String name) {
             return null;
         }
 
         @Override
-        protected String getInheritedBooleanFormat() {
+        protected String getDefaultBooleanFormat() {
             throw new SettingValueNotSetException("booleanFormat");
         }
 
         @Override
-        protected String getInheritedTimeFormat() {
+        protected String getDefaultTimeFormat() {
             throw new SettingValueNotSetException("timeFormat");
         }
 
         @Override
-        protected String getInheritedDateFormat() {
+        protected String getDefaultDateFormat() {
             throw new SettingValueNotSetException("dateFormat");
         }
 
         @Override
-        protected String getInheritedDateTimeFormat() {
+        protected String getDefaultDateTimeFormat() {
             throw new SettingValueNotSetException("dateTimeFormat");
         }
 
         @Override
-        protected Map<String, TemplateDateFormatFactory> getInheritedCustomDateFormats() {
+        protected Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats() {
             throw new SettingValueNotSetException("customDateFormats");
         }
 
         @Override
-        protected TemplateDateFormatFactory getInheritedCustomDateFormat(String name) {
+        protected TemplateDateFormatFactory getDefaultCustomDateFormat(String name) {
             throw new SettingValueNotSetException("customDateFormat");
         }
 
         @Override
-        protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
+        protected TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
             throw new SettingValueNotSetException("templateExceptionHandler");
         }
 
         @Override
-        protected ArithmeticEngine getInheritedArithmeticEngine() {
+        protected ArithmeticEngine getDefaultArithmeticEngine() {
             throw new SettingValueNotSetException("arithmeticEngine");
         }
 
         @Override
-        protected ObjectWrapper getInheritedObjectWrapper() {
+        protected ObjectWrapper getDefaultObjectWrapper() {
             throw new SettingValueNotSetException("objectWrapper");
         }
 
         @Override
-        protected Charset getInheritedOutputEncoding() {
+        protected Charset getDefaultOutputEncoding() {
             throw new SettingValueNotSetException("outputEncoding");
         }
 
         @Override
-        protected Charset getInheritedURLEscapingCharset() {
+        protected Charset getDefaultURLEscapingCharset() {
             throw new SettingValueNotSetException("URLEscapingCharset");
         }
 
         @Override
-        protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
+        protected TemplateClassResolver getDefaultNewBuiltinClassResolver() {
             throw new SettingValueNotSetException("newBuiltinClassResolver");
         }
 
         @Override
-        protected boolean getInheritedAutoFlush() {
+        protected boolean getDefaultAutoFlush() {
             throw new SettingValueNotSetException("autoFlush");
         }
 
         @Override
-        protected boolean getInheritedShowErrorTips() {
+        protected boolean getDefaultShowErrorTips() {
             throw new SettingValueNotSetException("showErrorTips");
         }
 
         @Override
-        protected boolean getInheritedAPIBuiltinEnabled() {
+        protected boolean getDefaultAPIBuiltinEnabled() {
             throw new SettingValueNotSetException("APIBuiltinEnabled");
         }
 
         @Override
-        protected boolean getInheritedLogTemplateExceptions() {
+        protected boolean getDefaultLogTemplateExceptions() {
             throw new SettingValueNotSetException("logTemplateExceptions");
         }
 
         @Override
-        protected boolean getInheritedLazyImports() {
+        protected boolean getDefaultLazyImports() {
             throw new SettingValueNotSetException("lazyImports");
         }
 
         @Override
-        protected Boolean getInheritedLazyAutoImports() {
+        protected Boolean getDefaultLazyAutoImports() {
             throw new SettingValueNotSetException("lazyAutoImports");
         }
 
         @Override
-        protected Map<String, String> getInheritedAutoImports() {
+        protected Map<String, String> getDefaultAutoImports() {
             throw new SettingValueNotSetException("autoImports");
         }
 
         @Override
-        protected List<String> getInheritedAutoIncludes() {
+        protected List<String> getDefaultAutoIncludes() {
             throw new SettingValueNotSetException("autoIncludes");
         }
 
         @Override
-        protected Object getInheritedCustomAttribute(Object name) {
+        protected Object getDefaultCustomAttribute(Object name) {
             return null;
         }
 
+        @Override
+        protected Map<Object, Object> getDefaultCustomAttributes() {
+            throw new SettingValueNotSetException("customAttributes");
+        }
+
         /**
          * Set all settings in this {@link Builder} that were set in the parameter
          * {@link TemplateConfiguration}, possibly overwriting the earlier value in this object. (A setting is said to be
@@ -937,47 +942,47 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         }
 
         @Override
-        protected int getInheritedTagSyntax() {
+        protected int getDefaultTagSyntax() {
             throw new SettingValueNotSetException("tagSyntax");
         }
 
         @Override
-        protected TemplateLanguage getInheritedTemplateLanguage() {
+        protected TemplateLanguage getDefaultTemplateLanguage() {
             throw new SettingValueNotSetException("templateLanguage");
         }
 
         @Override
-        protected int getInheritedNamingConvention() {
+        protected int getDefaultNamingConvention() {
             throw new SettingValueNotSetException("namingConvention");
         }
 
         @Override
-        protected boolean getInheritedWhitespaceStripping() {
+        protected boolean getDefaultWhitespaceStripping() {
             throw new SettingValueNotSetException("whitespaceStripping");
         }
 
         @Override
-        protected int getInheritedAutoEscapingPolicy() {
+        protected int getDefaultAutoEscapingPolicy() {
             throw new SettingValueNotSetException("autoEscapingPolicy");
         }
 
         @Override
-        protected OutputFormat getInheritedOutputFormat() {
+        protected OutputFormat getDefaultOutputFormat() {
             throw new SettingValueNotSetException("outputFormat");
         }
 
         @Override
-        protected boolean getInheritedRecognizeStandardFileExtensions() {
+        protected boolean getDefaultRecognizeStandardFileExtensions() {
             throw new SettingValueNotSetException("recognizeStandardFileExtensions");
         }
 
         @Override
-        protected Charset getInheritedSourceEncoding() {
+        protected Charset getDefaultSourceEncoding() {
             throw new SettingValueNotSetException("sourceEncoding");
         }
 
         @Override
-        protected int getInheritedTabSize() {
+        protected int getDefaultTabSize() {
             throw new SettingValueNotSetException("tabSize");
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java b/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java
new file mode 100644
index 0000000..8b253a2
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.util.Map;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
+
+/**
+ * Implemented by FreeMarker core classes (not by you) that provide {@link Configuration}-level settings. <b>New
+ * methods may be added any time in future FreeMarker versions, so don't try to implement this interface yourself!</b>
+ *
+ * @see ParsingAndProcessingConfiguration
+ */
+public interface TopLevelConfiguration extends ParsingAndProcessingConfiguration {
+
+    /**
+     * The {@link TemplateLoader} that is used to look up and load templates.
+     * By providing your own {@link TemplateLoader} implementation, you can load templates from whatever kind of
+     * storages, like from relational databases, NoSQL-storages, etc.
+     *
+     * <p>You can chain several {@link TemplateLoader}-s together with {@link MultiTemplateLoader}.
+     *
+     * <p>Default value: You should always set the template loader instead of relying on the default value.
+     * (But if you still care what it is, before "incompatible improvements" 2.3.21 it's a {@link FileTemplateLoader}
+     * that uses the current directory as its root; as it's hard tell what that directory will be, it's not very useful
+     * and dangerous. Starting with "incompatible improvements" 2.3.21 the default is {@code null}.)
+     */
+    TemplateLoader getTemplateLoader();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateLoaderSet();
+
+    /**
+     * The {@link TemplateLookupStrategy} that is used to look up templates based on the requested name, locale and
+     * custom lookup condition. Its default is {@link DefaultTemplateLookupStrategy#INSTANCE}.
+     */
+    TemplateLookupStrategy getTemplateLookupStrategy();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateLookupStrategySet();
+
+    /**
+     * The template name format used; see {@link TemplateNameFormat}. The default is
+     * {@link DefaultTemplateNameFormatFM2#INSTANCE}, while the recommended value for new projects is
+     * {@link DefaultTemplateNameFormat#INSTANCE}.
+     */
+    TemplateNameFormat getTemplateNameFormat();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateNameFormatSet();
+
+    /**
+     * The {@link TemplateConfigurationFactory} that will configure individual templates where their settings differ
+     * from those coming from the common {@link Configuration} object. A typical use case for that is specifying the
+     * {@link #getOutputFormat() outputFormat} or {@link #getSourceEncoding() sourceEncoding} for templates based on
+     * their file extension or parent directory.
+     * <p>
+     * Note that the settings suggested by standard file extensions are stronger than that you set here. See
+     * {@link #getRecognizeStandardFileExtensions()} for more information about standard file extensions.
+     * <p>
+     * See "Template configurations" in the FreeMarker Manual for examples.
+     */
+    TemplateConfigurationFactory getTemplateConfigurations();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateConfigurationsSet();
+
+    /**
+     * The map-like object used for caching templates to avoid repeated loading and parsing of the template "files".
+     * Its {@link Configuration}-level default is a {@link SoftCacheStorage}.
+     */
+    CacheStorage getCacheStorage();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isCacheStorageSet();
+
+    /**
+     * The time in milliseconds that must elapse before checking whether there is a newer version of a template
+     * "file" than the cached one. Defaults to 5000 ms.
+     */
+    long getTemplateUpdateDelayMilliseconds();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateUpdateDelayMillisecondsSet();
+
+    /**
+     * Returns the value of the "incompatible improvements" setting; this is the FreeMarker version number where the
+     * not 100% backward compatible bug fixes and improvements that you want to enable were already implemented. In
+     * new projects you should set this to the FreeMarker version that you are actually using. In older projects it's
+     * also usually better to keep this high, however you better check the changes activated (find them below), at
+     * least if not only the 3rd version number (the micro version) of {@code incompatibleImprovements} is increased.
+     * Generally, as far as you only increase the last version number of this setting, the changes are always low
+     * risk.
+     * <p>
+     * Bugfixes and improvements that are fully backward compatible, also those that are important security fixes,
+     * are enabled regardless of the incompatible improvements setting.
+     * <p>
+     * An important consequence of setting this setting is that now your application will check if the stated minimum
+     * FreeMarker version requirement is met. Like if you set this setting to 3.0.1, but accidentally the
+     * application is deployed with FreeMarker 3.0.0, then FreeMarker will fail, telling that a higher version is
+     * required. After all, the fixes/improvements you have requested aren't available on a lower version.
+     * <p>
+     * Note that as FreeMarker's minor (2nd) or major (1st) version number increments, it's possible that emulating
+     * some of the old bugs will become unsupported, that is, even if you set this setting to a low value, it
+     * silently wont bring back the old behavior anymore. Information about that will be present here.
+     *
+     * <p>Currently the effects of this setting are:
+     * <ul>
+     *   <li><p>
+     *     3.0.0: This is the lowest supported value in FreeMarker 3.
+     *   </li>
+     * </ul>
+     *
+     * @return Never {@code null}.
+     */
+    @Override
+    Version getIncompatibleImprovements();
+
+    /**
+     * Whether localized template lookup is enabled. Enabled by default.
+     *
+     * <p>
+     * With the default {@link TemplateLookupStrategy}, localized lookup works like this: Let's say your locale setting
+     * is {@code Locale("en", "AU")}, and you call {@link Configuration#getTemplate(String) cfg.getTemplate("foo.ftl")}.
+     * Then FreeMarker will look for the template under these names, stopping at the first that exists:
+     * {@code "foo_en_AU.ftl"}, {@code "foo_en.ftl"}, {@code "foo.ftl"}. See the description of the default value at
+     * {@link #getTemplateLookupStrategy()} for a more details. If you need to generate different
+     * template names, set your own a {@link TemplateLookupStrategy} implementation as the value of the
+     * {@link #getTemplateLookupStrategy() templateLookupStrategy} setting.
+     */
+    boolean getLocalizedLookup();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isLocalizedLookupSet();
+
+    /**
+     * Shared variables are variables that are visible as top-level variables for all templates, except where the data
+     * model contains a variable with the same name (which then shadows the shared variable).
+     *
+     * @return Not {@code null}; the {@link Map} is possibly mutable in builders, but immutable in
+     *      {@link Configuration}.
+     *
+     * @see Configuration.Builder#setSharedVariables(Map)
+     */
+    Map<String, Object> getSharedVariables();
+
+    /**
+     * Tells if this setting was explicitly set (if not, the default value of the setting will be used).
+     */
+    boolean isSharedVariablesSet();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java b/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
index a3a266f..a4e562c 100644
--- a/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
+++ b/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
@@ -18,10 +18,11 @@
  */
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
 import org.apache.freemarker.core.util._StringUtil;
 
 /**
- * Thrown by {@link Configuration#setSetting(String, String)}; The setting name was not recognized.
+ * Thrown by {@link ExtendableBuilder#setSetting(String, String)} if the setting name was not recognized.
  */
 @SuppressWarnings("serial")
 public class UnknownConfigurationSettingException extends ConfigurationException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java b/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
index 29ded29..2d09062 100644
--- a/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
+++ b/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
@@ -225,7 +225,7 @@ public class _ErrorDescriptionBuilder {
                                     || (partStr.charAt(1) == '/') && (partStr.charAt(2) == '#' || partStr.charAt(2) == '@')
                     )
                             && partStr.charAt(partStr.length() - 1) == '>') {
-                        if (template.getActualTagSyntax() == Configuration.SQUARE_BRACKET_TAG_SYNTAX) {
+                        if (template.getActualTagSyntax() == ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX) {
                             sb.append('[');
                             sb.append(partStr.substring(1, partStr.length() - 1));
                             sb.append(']');

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java b/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
index 29dc9ad..afe22be 100644
--- a/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
+++ b/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
@@ -26,7 +26,7 @@ import org.apache.freemarker.core.TemplateException;
 
 /**
  * Implements the arithmetic operations executed by the template language; see
- * {@link Configuration#setArithmeticEngine(ArithmeticEngine)}.
+ * {@link Configuration#getArithmeticEngine()}.
  */
 public abstract class ArithmeticEngine {
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
index ce49afb..38b1d0a 100644
--- a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
@@ -197,12 +197,12 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
         {
             @Override
             Collection keySet() {
-                return ((Configuration) ProcessingConfiguration).getSharedVariableNames();
+                return ((Configuration) ProcessingConfiguration).getSharedVariables().keySet();
             }
         
             @Override
             public TemplateModel get(String key) {
-                return ((Configuration) ProcessingConfiguration).getSharedVariable(key);
+                return ((Configuration) ProcessingConfiguration).getWrappedSharedVariable(key);
             }
         };
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
index 46d6f72..42f09d8 100644
--- a/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
@@ -34,7 +34,7 @@ import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
  * {@link TemplateHashModel} implementation that will call {@link Map#get(Object)} or the getter method, transparently
  * to the template language.
  * 
- * @see Configuration#setObjectWrapper(ObjectWrapper)
+ * @see Configuration#getObjectWrapper()
  */
 public interface ObjectWrapper {
     
@@ -49,10 +49,10 @@ public interface ObjectWrapper {
      * 
      * @return a {@link TemplateModel} wrapper of the object passed in. To support un-wrapping, you may consider the
      *     return value to implement {@link WrapperTemplateModel} and {@link AdapterTemplateModel}.  
-     *     The default expectation is that the {@link TemplateModel} isn't less thread safe than the wrapped object.
-     *     If the {@link ObjectWrapper} returns less thread safe objects, that should be clearly documented, as it
-     *     restricts how it can be used, like, then it can't be used to wrap "shared variables"
-     *     ({@link Configuration#setSharedVariables(Map)}).
+     *     It's normally expectated that the {@link TemplateModel} isn't less thread safe than the wrapped object.
+     *     If the {@link ObjectWrapper} returns less thread safe objects that should be clearly documented, as it
+     *     restricts how it can be used, like, then it can't be used to wrap
+     *     {@linkplain Configuration#getSharedVariables() shared variables}).
      */
     TemplateModel wrap(Object obj) throws TemplateModelException;
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/model/TemplateModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/TemplateModel.java b/src/main/java/org/apache/freemarker/core/model/TemplateModel.java
index fb5f9fb..bbe3c03 100644
--- a/src/main/java/org/apache/freemarker/core/model/TemplateModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/TemplateModel.java
@@ -30,7 +30,7 @@ import org.apache.freemarker.core.util.FTLUtil;
  * {@link TemplateModel}-s.
  * 
  * <p>Mapping the plain Java objects to {@link TemplateModel}-s (or the other way around sometimes) is the
- * responsibility of the {@link ObjectWrapper} (can be set via {@link Configuration#setObjectWrapper(ObjectWrapper)}).
+ * responsibility of the {@link ObjectWrapper} (see the {@link Configuration#getObjectWrapper objectWrapper} setting).
  * But not all {@link TemplateModel}-s are for wrapping a plain object. For example, a value created within a template
  * is not made to wrap an earlier existing object; it's a value that has always existed in the template language's
  * domain. Users can also write {@link TemplateModel} implementations and put them directly into the data-model for

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
index a20695a..7e04776 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
@@ -43,7 +43,6 @@ import java.util.WeakHashMap;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core._CoreAPI;
-import org.apache.freemarker.core._CoreLogs;
 import org.apache.freemarker.core._DelayedFTLTypeDescription;
 import org.apache.freemarker.core._DelayedShortClassName;
 import org.apache.freemarker.core._TemplateModelException;
@@ -67,28 +66,25 @@ import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.dom.NodeModel;
-import org.slf4j.Logger;
 import org.w3c.dom.Node;
 
 /**
  * The default implementation of the {@link ObjectWrapper} interface. Usually, you don't need to invoke instances of
  * this, as an instance of this is already the default value of the
- * {@link Configuration#setObjectWrapper(ObjectWrapper) object_wrapper setting}. Then the
+ * {@link Configuration#getObjectWrapper() objectWrapper} setting. Then the
  * {@link ExtendableBuilder#ExtendableBuilder(Version, boolean) incompatibleImprovements} of the
  * {@link DefaultObjectWrapper} will be the same that you have set for the {@link Configuration} itself.
  * 
  * <p>
  * If you still need to invoke an instance, that should be done with {@link Builder#build()} (or
- * with {@link Configuration#setSetting(String, String)} with {@code "objectWrapper"} key); the constructor isn't
- * public.
+ * with {@link org.apache.freemarker.core.Configuration.ExtendableBuilder#setSetting(String, String)} with
+ * {@code "objectWrapper"} key); the constructor isn't public.
  *
  * <p>
  * This class is thread-safe.
  */
 public class DefaultObjectWrapper implements RichObjectWrapper {
 
-    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
-
     /**
      * At this level of exposure, all methods and properties of the
      * wrapped objects are exposed to the template.
@@ -1179,9 +1175,9 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
     }
 
     /**
-     * Gets/creates a {@link DefaultObjectWrapper} singleton instance that's already configured as specified in the properties of
-     * this object; this is recommended over using the {@link DefaultObjectWrapper} constructors. The returned instance can't be
-     * further configured (it's write protected).
+     * Gets/creates a {@link DefaultObjectWrapper} singleton instance that's already configured as specified in the
+     * properties of this object; this is recommended over using the {@link DefaultObjectWrapper} constructors. The
+     * returned instance can't be further configured (it's write protected).
      *
      * <p>The builder meant to be used as a drop-away object (not stored in a field), like in this example:
      * <pre>
@@ -1318,11 +1314,10 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
     }
 
     /**
-     * Holds {@link DefaultObjectWrapper} configuration settings and defines their defaults.
-     * You will not use this abstract class directly, but concrete subclasses like {@link Builder}.
-     * Unless, you are developing a builder for a custom {@link DefaultObjectWrapper} subclass. In that case, note that
-     * overriding the {@link #equals} and {@link #hashCode} is important, as these objects are used as {@link ObjectWrapper}
-     * singleton lookup keys.
+     * You will not use this abstract class directly, but concrete subclasses like {@link Builder}, unless you are
+     * developing a builder for a custom {@link DefaultObjectWrapper} subclass. In that case, note that overriding the
+     * {@link #equals} and {@link #hashCode} is important, as these objects are used as {@link ObjectWrapper} singleton
+     * lookup keys.
      */
     protected abstract static class ExtendableBuilder<
             ProductT extends DefaultObjectWrapper, SelfT extends ExtendableBuilder<ProductT, SelfT>>
@@ -1362,8 +1357,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          *         check the list of effects below. Increasing the 2nd or 1st version number possibly mean substantial
          *         changes with higher risk of breaking the application, but again, see the list of effects below.
          *         <p>
-         *         The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that
-         *         {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two
+         *         The reason it's separate from {@link Configuration#getIncompatibleImprovements()} is that
+         *         {@link ObjectWrapper} objects are sometimes shared among multiple {@link Configuration}-s, so the two
          *         version numbers are technically independent. But it's recommended to keep those two version numbers
          *         the same.
          *         <p>
@@ -1605,6 +1600,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          * @deprecated Does nothing in FreeMarker 3 - we kept it for now to postopne reworking some JUnit tests.
          */
         // [FM3] Remove
+        @Deprecated
         public void setUseModelCache(boolean useModelCache) {
             this.useModelCache = useModelCache;
             useModelCacheSet = true;
@@ -1614,6 +1610,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          * Fluent API equivalent of {@link #setUseModelCache(boolean)}.
          * @deprecated Does nothing in FreeMarker 3 - we kept it for now to postopne reworking some JUnit tests.
          */
+        @Deprecated
         public SelfT useModelCache(boolean useModelCache) {
             setUseModelCache(useModelCache);
             return self();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java b/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
index b584ea7..aac7d54 100644
--- a/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
+++ b/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
@@ -30,8 +30,8 @@ import org.apache.freemarker.core.outputformat.impl.TemplateHTMLOutputModel;
 /**
  * Superclass of {@link OutputFormat}-s that represent a "markup" format, which is any format where certain character
  * sequences have special meaning and thus may need escaping. (Escaping is important for FreeMarker, as typically it has
- * to insert non-markup text from the data-model into the output markup. See also:
- * {@link Configuration#setOutputFormat(OutputFormat)}.)
+ * to insert non-markup text from the data-model into the output markup. See also the
+ * {@link Configuration#getOutputFormat() outputFormat} configuration setting.)
  * 
  * <p>
  * A {@link MarkupOutputFormat} subclass always has a corresponding {@link TemplateMarkupOutputModel} subclass pair
@@ -128,7 +128,7 @@ public abstract class MarkupOutputFormat<MO extends TemplateMarkupOutputModel> e
      * Tells if by default auto-escaping should be on for this format. It should be {@code true} if you need to escape
      * on most of the places where you insert values.
      * 
-     * @see Configuration#setAutoEscapingPolicy(int)
+     * @see Configuration#getAutoEscapingPolicy()
      */
     public abstract boolean isAutoEscapedByDefault();
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java b/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
index ced33f5..8004ae2 100644
--- a/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
+++ b/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
@@ -27,8 +27,8 @@ import org.apache.freemarker.core.util._StringUtil;
 /**
  * Represents an output format. If you need auto-escaping, see its subclass, {@link MarkupOutputFormat}.
  * 
- * @see Configuration#setOutputFormat(OutputFormat)
- * @see Configuration#setRegisteredCustomOutputFormats(java.util.Collection)
+ * @see Configuration#getOutputFormat()
+ * @see Configuration#getRegisteredCustomOutputFormats()
  * @see MarkupOutputFormat
  * 
  * @since 2.3.24

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java b/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
index 4dceb80..b5412e2 100644
--- a/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
+++ b/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
@@ -24,9 +24,8 @@ import org.apache.freemarker.core.outputformat.OutputFormat;
 
 /**
  * Represents the output format used when the template output format is undecided. This is the default output format if
- * FreeMarker can't select anything more specific (see
- * {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)}). This format doesn't
- * support auto-escaping ({@link Configuration#setAutoEscapingPolicy(int)}). It will print
+ * FreeMarker can't select anything more specific (see {@link Configuration#getTemplateConfigurations()}). This format
+ * doesn't support auto-escaping ({@link Configuration#getAutoEscapingPolicy()}). It will print
  * {@link TemplateMarkupOutputModel}-s as is (doesn't try to convert them).
  * 
  * @see PlainTextOutputFormat

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
index d497e55..c70fa94 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
@@ -19,13 +19,15 @@
 
 package org.apache.freemarker.core.templateresolver;
 
+import org.apache.freemarker.core.Configuration;
+
 /**
  * Cache storage abstracts away the storage aspects of a cache - associating
  * an object with a key, retrieval and removal via the key. It is actually a
  * small subset of the {@link java.util.Map} interface. 
  * The implementations must be thread safe.
  *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ * @see Configuration#getCacheStorage()
  */
 public interface CacheStorage {
     Object get(Object key);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
index 60992d6..fc6a4aa 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
@@ -32,7 +32,8 @@ import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
  * implementations.
  * 
  * <p>
- * To set the {@link TemplateLoader} used by FreeMaker, use {@link Configuration#setTemplateLoader(TemplateLoader)}.
+ * The {@link TemplateLoader} used by FreeMaker is specified by the {@link Configuration#getTemplateLoader()
+ * templateLoader} configuration setting.
  * 
  * <p>
  * Implementations of this interface should be thread-safe.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 2253384..c685d93 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
@@ -27,6 +27,7 @@ import java.nio.charset.Charset;
 import java.util.Date;
 
 import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
 import org.apache.freemarker.core.util._NullArgumentException;
@@ -96,7 +97,7 @@ public final class TemplateLoadingResult {
      *            Usually {@code null}, as usually the backing storage mechanism doesn't store such information; see
      *            {@link #getTemplateConfiguration()}. The most probable application is supplying the charset (encoding)
      *            used by the {@link InputStream} (via
-     *            {@link TemplateConfiguration.Builder#setSourceEncoding(Charset)}), but only do that if the storage
+     *            {@link ExtendableBuilder#setSourceEncoding(Charset)}), but only do that if the storage
      *            mechanism really knows what the charset is.
      */
     public TemplateLoadingResult(TemplateLoadingSource source, Serializable version, InputStream inputStream,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
index 25efdf0..7021b5b 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
@@ -50,7 +50,7 @@ import org.apache.freemarker.core.Template;
  * applications, yet it can be confusing.)
  * </ul>
  * 
- * @see Configuration#setTemplateLookupStrategy(TemplateLookupStrategy)
+ * @see Configuration#getTemplateLookupStrategy()
  * 
  * @since 2.3.22
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
index 3795148..57773f4 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
@@ -20,11 +20,9 @@
 package org.apache.freemarker.core.templateresolver;
 
 /**
- * Symbolized template name format. The API of this class isn't exposed as it's too immature, so custom
- * template name formats aren't possible yet.
- *
- * @since 2.3.22
+ * Symbolizes a template name format, which defines the basic syntax of names through algorithms such as normalization.
  */
+// TODO [FM3] Before it becomes a BC problem, shouldn't we add methods like splitting to directory name and file name?
 public abstract class TemplateNameFormat {
 
     protected TemplateNameFormat() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
index f257e2b..bf7280a 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
@@ -35,10 +35,10 @@ import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
  * delegate some of those duties back to the {@link Configuration} setting:
  * 
  * <ul>
- * <li>{@link Configuration#setTemplateLoader(TemplateLoader) template_loader}
- * <li>{@link Configuration#setTemplateNameFormat(TemplateNameFormat) template_name_format}
- * <li>{@link Configuration#setTemplateLookupStrategy(TemplateLookupStrategy) template_lookup_strategy}
- * <li>{@link Configuration#setCacheStorage(CacheStorage) cache_storage}
+ * <li>{@link Configuration#getTemplateLoader() templateLoader}
+ * <li>{@link Configuration#getTemplateNameFormat() templateNameFormat}
+ * <li>{@link Configuration#getTemplateLookupStrategy() templateLookupStrategy}
+ * <li>{@link Configuration#getCacheStorage() cacheStorage}
  * </ul>
  * 
  * @since 3.0.0
@@ -104,8 +104,8 @@ public abstract class TemplateResolver {
 
     /**
      * Removes a template from the template cache, hence forcing the re-loading of it when it's next time requested;
-     * this is an optional operation. This is to give the application finer control over cache updating than
-     * {@link Configuration#setTemplateUpdateDelayMilliseconds(long)} alone does.
+     * this is an optional operation. This is to give the application finer control over cache updating than the
+     * {@link Configuration#getTemplateUpdateDelayMilliseconds() templateUpdateDelayMilliseconds} setting alone gives.
      * 
      * <p>
      * For the meaning of the parameters, see {@link #getTemplate(String, Locale, Serializable)}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java
index f85a1f8..69fa390 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java
@@ -20,16 +20,15 @@ package org.apache.freemarker.core.templateresolver.impl;
 
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateNotFoundException;
-import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
 import org.apache.freemarker.core.util._StringUtil;
 
 /**
- * The default template name format only when {@link Configuration#Configuration(Version) incompatible_improvements}
- * is set to 2.4.0 (or higher). This is not the out-of-the-box default format of FreeMarker 2.4.x, because the
- * default {@code incompatible_improvements} is still 2.3.0 there.
+ * The default template name format only when {@link Configuration#getIncompatibleImprovements()
+ * incompatible_improvements} is set to 2.4.0 (or higher). This is not the out-of-the-box default format of FreeMarker
+ * 2.4.x, because the default {@code incompatible_improvements} is still 2.3.0 there.
  * 
  * <p>
  * Differences to the {@link DefaultTemplateNameFormatFM2} format:

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java
index 29b953b..c5db8e5 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java
@@ -19,13 +19,12 @@
 package org.apache.freemarker.core.templateresolver.impl;
 
 import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
 import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
 
 /**
- * The default template name format when {@link Configuration#Configuration(Version) incompatible_improvements} is
- * below 2.4.0. As of FreeMarker 2.4.0, the default {@code incompatible_improvements} is still {@code 2.3.0}, and it
+ * The default template name format when {@link Configuration#getIncompatibleImprovements() incompatible_improvements}
+ * is below 2.4.0. As of FreeMarker 2.4.0, the default {@code incompatible_improvements} is still {@code 2.3.0}, and it
  * will certainly remain so for a very long time. In new projects it's highly recommended to use
  * {@link DefaultTemplateNameFormat#INSTANCE} instead.
  * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/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 48650e8..ea1cc63 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
@@ -63,16 +63,16 @@ import org.slf4j.Logger;
  * The actual template "file" loading is delegated to a {@link TemplateLoader} that you can specify in the constructor.
  * Some aspects of caching is delegated to a {@link CacheStorage} that you can also specify in the constructor.
  * 
- * <p>Typically you don't instantiate or otherwise use this class directly. The {@link Configuration} embeds an
- * instance of this class, that you access indirectly through {@link Configuration#getTemplate(String)} and other
- * {@link Configuration} API-s. Then {@link TemplateLoader} and {@link CacheStorage} can be set with
- * {@link Configuration#setTemplateLoader(TemplateLoader)} and
- * {@link Configuration#setCacheStorage(CacheStorage)}.
+ * <p>Typically you don't instantiate or otherwise use this class directly. By default the {@link Configuration} embeds
+ * an instance of this class, that you access indirectly through {@link Configuration#getTemplate(String)} and other
+ * {@link Configuration} API-s. When you set the {@link Configuration#getTemplateLoader() templateLoader} or
+ * {@link Configuration#getCacheStorage() cacheStorage} of the {@link Configuration}, you indirectly configure the
+ * {@link TemplateResolver}.
  */
 public class DefaultTemplateResolver extends TemplateResolver {
     
     /**
-     * The default template update delay; see {@link Configuration#setTemplateUpdateDelayMilliseconds(long)}.
+     * The default template update delay; see {@link Configuration#getTemplateUpdateDelayMilliseconds()}.
      * 
      * @since 2.3.23
      */
@@ -92,28 +92,12 @@ public class DefaultTemplateResolver extends TemplateResolver {
     private final TemplateLookupStrategy templateLookupStrategy;
     private final TemplateNameFormat templateNameFormat;
     private final TemplateConfigurationFactory templateConfigurations;
-    
-    /** {@link Configuration#setTemplateUpdateDelayMilliseconds(long)} */
-    private long templateUpdateDelayMilliseconds = DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS;
-    /** {@link Configuration#setLocalizedLookup(boolean)} */
-    private boolean localizedLookup = true;
+    private final long templateUpdateDelayMilliseconds;
+    private final boolean localizedLookup;
 
     private Configuration config;
     
     /**
-     * Same as
-     * {@link #DefaultTemplateResolver(TemplateLoader, CacheStorage, TemplateLookupStrategy, TemplateNameFormat,
-     * TemplateConfigurationFactory, Configuration)} with {@code null} for {@code templateConfigurations}-s.
-     * 
-     * @since 2.3.22
-     */
-    public DefaultTemplateResolver(TemplateLoader templateLoader, CacheStorage cacheStorage,
-            TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,
-            Configuration config) {
-        this(templateLoader, cacheStorage, templateLookupStrategy, templateNameFormat, null, config);
-    }
-
-    /**
      * @param templateLoader
      *            The {@link TemplateLoader} to use. Can be {@code null}, though then every request will result in
      *            {@link TemplateNotFoundException}.
@@ -121,6 +105,8 @@ public class DefaultTemplateResolver extends TemplateResolver {
      *            The {@link CacheStorage} to use. Can't be {@code null}.
      * @param templateLookupStrategy
      *            The {@link TemplateLookupStrategy} to use. Can't be {@code null}.
+     * @param templateUpdateDelayMilliseconds
+     *            See {@link Configuration#getTemplateUpdateDelayMilliseconds()}
      * @param templateNameFormat
      *            The {@link TemplateNameFormat} to use. Can't be {@code null}.
      * @param templateConfigurations
@@ -132,8 +118,11 @@ public class DefaultTemplateResolver extends TemplateResolver {
      * 
      * @since 2.3.24
      */
-    public DefaultTemplateResolver(TemplateLoader templateLoader, CacheStorage cacheStorage,
-            TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,
+    public DefaultTemplateResolver(
+            TemplateLoader templateLoader,
+            CacheStorage cacheStorage, long templateUpdateDelayMilliseconds,
+            TemplateLookupStrategy templateLookupStrategy, boolean localizedLookup,
+            TemplateNameFormat templateNameFormat,
             TemplateConfigurationFactory templateConfigurations,
             Configuration config) {
         super(config);
@@ -143,6 +132,10 @@ public class DefaultTemplateResolver extends TemplateResolver {
         _NullArgumentException.check("cacheStorage", cacheStorage);
         this.cacheStorage = cacheStorage;
         
+        this.templateUpdateDelayMilliseconds = templateUpdateDelayMilliseconds;
+        
+        this.localizedLookup = localizedLookup;
+        
         _NullArgumentException.check("templateLookupStrategy", templateLookupStrategy);
         this.templateLookupStrategy = templateLookupStrategy;
 
@@ -518,7 +511,6 @@ public class DefaultTemplateResolver extends TemplateResolver {
         cacheStorage.put(cacheKey, cachedResult);
     }
 
-    @SuppressWarnings("deprecation")
     private Template loadTemplate(
             TemplateLoadingResult templateLoaderResult,
             final String name, final String sourceName, Locale locale, final Serializable customLookupCondition)
@@ -633,18 +625,6 @@ public class DefaultTemplateResolver extends TemplateResolver {
     }
 
     /**
-     * Sets the delay in milliseconds between checking for newer versions of a
-     * template sources.
-     * @param templateUpdateDelayMilliseconds the new value of the delay
-     */
-    public void setTemplateUpdateDelayMilliseconds(long templateUpdateDelayMilliseconds) {
-        // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not.
-        synchronized (this) {
-            this.templateUpdateDelayMilliseconds = templateUpdateDelayMilliseconds;
-        }
-    }
-
-    /**
      * Returns if localized template lookup is enabled or not.
      */
     public boolean getLocalizedLookup() {
@@ -655,19 +635,6 @@ public class DefaultTemplateResolver extends TemplateResolver {
     }
 
     /**
-     * Setis if localized template lookup is enabled or not.
-     */
-    public void setLocalizedLookup(boolean localizedLookup) {
-        // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not.
-        synchronized (this) {
-            if (this.localizedLookup != localizedLookup) {
-                this.localizedLookup = localizedLookup;
-                clearTemplateCache();
-            }
-        }
-    }
-
-    /**
      * Removes all entries from the cache, forcing reloading of templates on subsequent
      * {@link #getTemplate(String, Locale, Serializable)} calls.
      * 
@@ -698,8 +665,8 @@ public class DefaultTemplateResolver extends TemplateResolver {
 
     /**
      * Removes an entry from the cache, hence forcing the re-loading of it when it's next time requested. (It doesn't
-     * delete the template file itself.) This is to give the application finer control over cache updating than
-     * {@link #setTemplateUpdateDelayMilliseconds(long)} alone does.
+     * delete the template file itself.) This is to give the application finer control over cache updating than the
+     * update delay ({@link #getTemplateUpdateDelayMilliseconds()}) alone does.
      * 
      * For the meaning of the parameters, see
      * {@link Configuration#getTemplate(String, Locale, Serializable, boolean)}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
index 0387d64..9f004fe 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
@@ -24,7 +24,7 @@ import java.lang.ref.SoftReference;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
 
 /**
@@ -55,7 +55,7 @@ import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
  * memory-sensitive) most-recently-used caching through 
  * {@link SoftCacheStorage} as well.
  *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ * @see Configuration#getCacheStorage()
  */
 public class MruCacheStorage implements CacheStorageWithGetSize {
     private final MruEntry strongHead = new MruEntry();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
index 33d3d02..c8ff55c 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core.templateresolver.impl;
 
+import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.templateresolver.CacheStorage;
 import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
 
@@ -26,7 +27,7 @@ import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
  * A cache storage that doesn't store anything. Use this if you
  * don't want caching.
  *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ * @see Configuration#getCacheStorage()
  * 
  * @since 2.3.17
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
index a5546fe..3e22c33 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
@@ -25,6 +25,7 @@ import java.lang.ref.SoftReference;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
 import org.apache.freemarker.core.templateresolver.CacheStorage;
 import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
 
@@ -34,7 +35,7 @@ import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
  * class is thread-safe to the extent that its underlying map is. The parameterless constructor uses a thread-safe map
  * since 2.3.24 or Java 5.
  *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ * @see ExtendableBuilder#setCacheStorage(CacheStorage)
  */
 public class SoftCacheStorage implements CacheStorage, CacheStorageWithGetSize {
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
index eea7331..1d0533b 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
@@ -23,6 +23,7 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.templateresolver.CacheStorage;
 import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
 
@@ -31,7 +32,7 @@ import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
  * it was passed, therefore prevents the cache from being purged during garbage collection. This class is always
  * thread-safe since 2.3.24, before that if we are running on Java 5 or later.
  *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ * @see Configuration#getCacheStorage()
  */
 public class StrongCacheStorage implements CacheStorage, CacheStorageWithGetSize {