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/03/27 11:48:50 UTC

[1/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 1870d0294 -> 053afbf5d


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 539f220..9b643b9 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
@@ -37,7 +37,7 @@ public class ConfigurableTest {
 
     @Test
     public void testGetSettingNamesAreSorted() throws Exception {
-        Configurable cfgable = createConfigurable();
+        MutableProcessingConfiguration cfgable = createConfigurable();
         for (boolean camelCase : new boolean[] { false, true }) {
             Collection<String> names = cfgable.getSettingNames(camelCase);
             String prevName = null;
@@ -52,7 +52,7 @@ public class ConfigurableTest {
 
     @Test
     public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception {
-        Configurable cfgable = createConfigurable();
+        MutableProcessingConfiguration cfgable = createConfigurable();
         Collection<String> names = cfgable.getSettingNames(false);
         for (String name : names) {
                 assertTrue("No field was found for " + name, keyFieldExists(name));
@@ -61,10 +61,10 @@ public class ConfigurableTest {
     
     @Test
     public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception {
-        Configurable cfgable = createConfigurable();
+        MutableProcessingConfiguration cfgable = createConfigurable();
         Collection<String> names = cfgable.getSettingNames(false);
         
-        for (Field f : Configurable.class.getFields()) {
+        for (Field f : MutableProcessingConfiguration.class.getFields()) {
             if (f.getName().endsWith("_KEY")) {
                 final Object name = f.get(null);
                 assertTrue("Missing setting name: " + name, names.contains(name));
@@ -74,19 +74,19 @@ public class ConfigurableTest {
 
     @Test
     public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException {
-        ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configurable.class);
+        ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(MutableProcessingConfiguration.class);
     }
     
     @Test
     public void testGetSettingNamesNameConventionsContainTheSame() throws Exception {
-        Configurable cfgable = createConfigurable();
+        MutableProcessingConfiguration cfgable = createConfigurable();
         ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame(
                 new ArrayList<>(cfgable.getSettingNames(false)),
                 new ArrayList<>(cfgable.getSettingNames(true)));
     }
 
     public static void testKeyStaticFieldsHasAllVariationsAndCorrectFormat(
-            Class<? extends Configurable> confClass) throws IllegalArgumentException, IllegalAccessException {
+            Class<? extends MutableProcessingConfiguration> confClass) throws IllegalArgumentException, IllegalAccessException {
         // For all _KEY fields there must be a _KEY_CAMEL_CASE and a _KEY_SNAKE_CASE field.
         // Their content must not contradict the expected naming convention.
         // They _KEY filed value must be deducable from the field name
@@ -160,13 +160,13 @@ public class ConfigurableTest {
         }
     }
     
-    private Configurable createConfigurable() throws IOException {
+    private MutableProcessingConfiguration createConfigurable() throws IOException {
         return new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
     }
 
     private boolean keyFieldExists(String name) throws Exception {
         try {
-            Configurable.class.getField(name.toUpperCase() + "_KEY");
+            MutableProcessingConfiguration.class.getField(name.toUpperCase() + "_KEY");
         } catch (NoSuchFieldException e) {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 ab10de1..c7e3636 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -259,7 +259,7 @@ public class ConfigurationTest extends TestCase {
         
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         cfg.setLocale(Locale.GERMAN);
-        cfg.setDefaultEncoding(latin1);
+        cfg.setEncoding(latin1);
 
         TemplateConfiguration huTC = new TemplateConfiguration();
         huTC.setEncoding(latin2);
@@ -489,14 +489,14 @@ public class ConfigurationTest extends TestCase {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         
         {
-            cfg.setSetting(Configurable.OBJECT_WRAPPER_KEY, "defAult");
+            cfg.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "defAult");
             assertSame(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0), cfg.getObjectWrapper());
             DefaultObjectWrapper dow = (DefaultObjectWrapper) cfg.getObjectWrapper();
             assertEquals(Configuration.VERSION_3_0_0, dow.getIncompatibleImprovements());
         }
         
         {
-            cfg.setSetting(Configurable.OBJECT_WRAPPER_KEY, "restricted");
+            cfg.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "restricted");
             assertThat(cfg.getObjectWrapper(), instanceOf(RestrictedObjectWrapper.class));
         }
     }
@@ -780,13 +780,13 @@ public class ConfigurationTest extends TestCase {
             
             Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
             assertEquals(sysDefTZ, cfg.getTimeZone());
-            cfg.setSetting(Configurable.TIME_ZONE_KEY, "JVM default");
+            cfg.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default");
             assertEquals(sysDefTZ, cfg.getTimeZone());
             
             TimeZone newSysDefTZ = TimeZone.getTimeZone("GMT+09");
             TimeZone.setDefault(newSysDefTZ);
             assertEquals(sysDefTZ, cfg.getTimeZone());
-            cfg.setSetting(Configurable.TIME_ZONE_KEY, "JVM default");
+            cfg.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default");
             assertEquals(newSysDefTZ, cfg.getTimeZone());
         } finally {
             TimeZone.setDefault(origSysDefTZ);
@@ -805,10 +805,10 @@ public class ConfigurationTest extends TestCase {
             cfg.setSQLDateAndTimeTimeZone(null);
             assertNull(cfg.getSQLDateAndTimeTimeZone());
             
-            cfg.setSetting(Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "JVM default");
+            cfg.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "JVM default");
             assertEquals(sysDefTZ, cfg.getSQLDateAndTimeTimeZone());
             
-            cfg.setSetting(Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "null");
+            cfg.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "null");
             assertNull(cfg.getSQLDateAndTimeTimeZone());
         } finally {
             TimeZone.setDefault(origSysDefTZ);
@@ -891,7 +891,7 @@ public class ConfigurationTest extends TestCase {
     public void testSetLogTemplateExceptionsViaSetSettingAPI() throws ConfigurationException {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         assertFalse(cfg.getLogTemplateExceptions());
-        cfg.setSetting(Configurable.LOG_TEMPLATE_EXCEPTIONS_KEY, "true");
+        cfg.setSetting(MutableProcessingConfiguration.LOG_TEMPLATE_EXCEPTIONS_KEY, "true");
         assertTrue(cfg.getLogTemplateExceptions());
     }
     
@@ -952,7 +952,7 @@ public class ConfigurationTest extends TestCase {
             new Template(null, "${1?api}", cfg).process(null, _NullWriter.INSTANCE);
             fail();
         } catch (TemplateException e) {
-            assertThat(e.getMessage(), containsString(Configurable.API_BUILTIN_ENABLED_KEY));
+            assertThat(e.getMessage(), containsString(MutableProcessingConfiguration.API_BUILTIN_ENABLED_KEY));
         }
             
         cfg.setAPIBuiltinEnabled(true);
@@ -1050,13 +1050,13 @@ public class ConfigurationTest extends TestCase {
             assertThat(e.getMessage(), containsString("@wrong"));
         }
         
-        cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
+        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
                 "{ 'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "() }");
         assertEquals(
                 Collections.singletonMap("base", BaseNTemplateNumberFormatFactory.INSTANCE),
                 cfg.getCustomNumberFormats());
         
-        cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
+        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
                 "{ "
                 + "'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "(), "
                 + "'hex': " + HexTemplateNumberFormatFactory.class.getName() + "()"
@@ -1067,11 +1067,11 @@ public class ConfigurationTest extends TestCase {
                         "hex", HexTemplateNumberFormatFactory.INSTANCE),
                 cfg.getCustomNumberFormats());
         
-        cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY, "{}");
+        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY, "{}");
         assertEquals(Collections.emptyMap(), cfg.getCustomNumberFormats());
         
         try {
-            cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
+            cfg.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
                     "{ 'x': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }");
             fail();
         } catch (ConfigurationException e) {
@@ -1176,13 +1176,13 @@ public class ConfigurationTest extends TestCase {
             assertThat(e.getMessage(), containsString("@wrong"));
         }
         
-        cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
+        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
                 "{ 'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }");
         assertEquals(
                 Collections.singletonMap("epoch", EpochMillisTemplateDateFormatFactory.INSTANCE),
                 cfg.getCustomDateFormats());
         
-        cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
+        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
                 "{ "
                 + "'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "(), "
                 + "'epochDiv': " + EpochMillisDivTemplateDateFormatFactory.class.getName() + "()"
@@ -1193,11 +1193,11 @@ public class ConfigurationTest extends TestCase {
                         "epochDiv", EpochMillisDivTemplateDateFormatFactory.INSTANCE),
                 cfg.getCustomDateFormats());
         
-        cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY, "{}");
+        cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY, "{}");
         assertEquals(Collections.emptyMap(), cfg.getCustomDateFormats());
         
         try {
-            cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
+            cfg.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
                     "{ 'x': " + HexTemplateNumberFormatFactory.class.getName() + "() }");
             fail();
         } catch (ConfigurationException e) {
@@ -1339,20 +1339,20 @@ public class ConfigurationTest extends TestCase {
         String defaultFileEncoding = System.getProperty("file.encoding");
         assertNotNull(defaultFileEncoding);
 
-        assertEquals(defaultFileEncoding, cfg.getDefaultEncoding());
+        assertEquals(defaultFileEncoding, cfg.getEncoding());
         assertFalse(cfg.isDefaultEncodingExplicitlySet());
 
         String nonDefault = defaultFileEncoding.equalsIgnoreCase("UTF-8") ? "ISO-8859-1" : "UTF-8";
-        cfg.setDefaultEncoding(nonDefault);
+        cfg.setEncoding(nonDefault);
         assertTrue(cfg.isDefaultEncodingExplicitlySet());
-        assertEquals(nonDefault, cfg.getDefaultEncoding());
+        assertEquals(nonDefault, cfg.getEncoding());
 
         cfg.unsetDefaultEncoding();
-        assertEquals(defaultFileEncoding, cfg.getDefaultEncoding());
+        assertEquals(defaultFileEncoding, cfg.getEncoding());
         assertFalse(cfg.isDefaultEncodingExplicitlySet());
 
-        cfg.setSetting(Configuration.DEFAULT_ENCODING_KEY, "JVM default");
-        assertEquals(defaultFileEncoding, cfg.getDefaultEncoding());
+        cfg.setSetting(Configuration.ENCODING_KEY, "JVM default");
+        assertEquals(defaultFileEncoding, cfg.getEncoding());
         assertTrue(cfg.isDefaultEncodingExplicitlySet());
     }
 
@@ -1421,7 +1421,7 @@ public class ConfigurationTest extends TestCase {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         Collection<String> names = cfg.getSettingNames(false);
         
-        for (Class<? extends Configurable> cfgableClass : new Class[] { Configuration.class, Configurable.class }) {
+        for (Class<? extends MutableProcessingConfiguration> cfgableClass : new Class[] { Configuration.class, MutableProcessingConfiguration.class }) {
             for (Field f : cfgableClass.getFields()) {
                 if (f.getName().endsWith("_KEY")) {
                     final Object name = f.get(null);
@@ -1441,7 +1441,7 @@ public class ConfigurationTest extends TestCase {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         Collection<String> names = cfg.getSettingNames(false);
         
-        for (Field f : Configurable.class.getFields()) {
+        for (Field f : MutableProcessingConfiguration.class.getFields()) {
             if (f.getName().endsWith("_KEY")) {
                 final Object name = f.get(null);
                 assertTrue("Missing setting name: " + name, names.contains(name));
@@ -1453,10 +1453,10 @@ public class ConfigurationTest extends TestCase {
     public void testSetSettingSupportsBothNamingConventions() throws Exception {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         
-        cfg.setSetting(Configuration.DEFAULT_ENCODING_KEY_CAMEL_CASE, "UTF-16LE");
-        assertEquals("UTF-16LE", cfg.getDefaultEncoding());
-        cfg.setSetting(Configuration.DEFAULT_ENCODING_KEY_SNAKE_CASE, "UTF-8");
-        assertEquals("UTF-8", cfg.getDefaultEncoding());
+        cfg.setSetting(Configuration.ENCODING_KEY_CAMEL_CASE, "UTF-16LE");
+        assertEquals("UTF-16LE", cfg.getEncoding());
+        cfg.setSetting(Configuration.ENCODING_KEY_SNAKE_CASE, "UTF-8");
+        assertEquals("UTF-8", cfg.getEncoding());
         
         for (String nameCC : cfg.getSettingNames(true)) {
             for (String value : new String[] { "1", "default", "true" }) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 6b0c1d9..cf336bd 100644
--- a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
+++ b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
@@ -24,7 +24,6 @@ import static org.junit.Assert.*;
 import java.math.BigDecimal;
 import java.util.Arrays;
 
-import org.apache.freemarker.core.util._NullWriter;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -44,13 +43,8 @@ public class CustomAttributeTest {
             "s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A"));
     private static final Object VALUE_BIGDECIMAL = BigDecimal.valueOf(22);
 
-    private static final CustomAttribute CUST_ATT_TMP_1 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
-    private static final CustomAttribute CUST_ATT_TMP_2 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
-    private static final CustomAttribute CUST_ATT_ENV_1 = new CustomAttribute(CustomAttribute.SCOPE_ENVIRONMENT);
-    private static final CustomAttribute CUST_ATT_ENV_2 = new CustomAttribute(CustomAttribute.SCOPE_ENVIRONMENT);
-    private static final CustomAttribute CUST_ATT_CFG_1 = new CustomAttribute(CustomAttribute.SCOPE_CONFIGURATION);
-    private static final CustomAttribute CUST_ATT_CFG_2 = new CustomAttribute(CustomAttribute.SCOPE_CONFIGURATION);
-    
+    private static final Object CUST_ATT_KEY = new Object();
+
     @Test
     public void testStringKey() throws Exception {
         Template t = new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
@@ -160,65 +154,13 @@ public class CustomAttributeTest {
 
     @Test
     public void testObjectKey() throws Exception {
-        Template t = new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
-        assertNull(CUST_ATT_TMP_1.get(t));
-        
-        CUST_ATT_TMP_1.set(VALUE_1, t);
-        assertSame(VALUE_1, CUST_ATT_TMP_1.get(t));
-        assertEquals(0, t.getCustomAttributeNames().length);
-        
-        t.setCustomAttribute(KEY_2, VALUE_2);
-        assertArrayEquals(new String[] { KEY_2 }, t.getCustomAttributeNames());        
-        assertSame(VALUE_1, CUST_ATT_TMP_1.get(t));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
-        
-        CUST_ATT_TMP_2.set(VALUE_3, t);
-        assertSame(VALUE_3, CUST_ATT_TMP_2.get(t));
-        assertArrayEquals(new String[] { KEY_2 }, t.getCustomAttributeNames());        
+        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        Template t = new Template(null, "", cfg);
+        assertNull(t.getCustomAttribute(CUST_ATT_KEY));
+        cfg.setCustomAttribute(CUST_ATT_KEY, "cfg");
+        assertEquals("cfg", t.getCustomAttribute(CUST_ATT_KEY));
+        t.setCustomAttribute(CUST_ATT_KEY, "t");
+        assertEquals("t", t.getCustomAttribute(CUST_ATT_KEY));
     }
 
-    @Test
-    public void testScopes() throws Exception {
-        try {
-            assertNull(CUST_ATT_ENV_1.get());
-            fail();
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-        try {
-            assertNull(CUST_ATT_CFG_1.get());
-            fail();
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-        
-        final Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        
-        final Template t = new Template(null, "${testScopesFromTemplateStep1()}", cfg);
-        Environment env = t.createProcessingEnvironment(this, _NullWriter.INSTANCE);
-        CUST_ATT_TMP_2.set(123, env);
-        CUST_ATT_ENV_2.set(1234, env);
-        CUST_ATT_CFG_2.set(12345, env);
-        env.process();
-    }
-
-    public void testScopesFromTemplateStep1() throws Exception {
-        assertNull(CUST_ATT_TMP_1.get());
-        assertEquals(123, CUST_ATT_TMP_2.get());
-        
-        assertNull(CUST_ATT_ENV_1.get());
-        assertEquals(1234, CUST_ATT_ENV_2.get());
-        
-        assertNull(CUST_ATT_CFG_1.get());
-        assertEquals(12345, CUST_ATT_CFG_2.get());
-    }
-
-    public void testScopesFromTemplateStep2() throws Exception {
-        
-    }
-
-    public void testScopesFromTemplateStep3() throws Exception {
-        
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 30d9e69..dd46cf9 100644
--- a/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
+++ b/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
@@ -59,7 +59,7 @@ public class EncodingOverrideTest {
     private Configuration createConfig(String charset) {
        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
        cfg.setClassForTemplateLoading(EncodingOverrideTest.class, "");
-       cfg.setDefaultEncoding(charset);
+       cfg.setEncoding(charset);
        return cfg;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/test/java/org/apache/freemarker/core/EnvironmentCustomStateTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/EnvironmentCustomStateTest.java b/src/test/java/org/apache/freemarker/core/EnvironmentCustomStateTest.java
deleted file mode 100644
index 7c9d935..0000000
--- a/src/test/java/org/apache/freemarker/core/EnvironmentCustomStateTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.freemarker.core;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-
-public class EnvironmentCustomStateTest {
-    
-    private static final Object KEY_1 = new Object();
-    private static final Object KEY_2 = new Object();
-
-    @Test
-    public void test() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        Environment env = t.createProcessingEnvironment(null, null);
-        assertNull(env.getCustomState(KEY_1));
-        assertNull(env.getCustomState(KEY_2));
-        env.setCustomState(KEY_1, "a");
-        env.setCustomState(KEY_2, "b");
-        assertEquals("a", env.getCustomState(KEY_1));
-        assertEquals("b", env.getCustomState(KEY_2));
-        env.setCustomState(KEY_1, "c");
-        env.setCustomState(KEY_2, null);
-        assertEquals("c", env.getCustomState(KEY_1));
-        assertNull(env.getCustomState(KEY_2));
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 3092145..a267645 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -301,7 +301,7 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         assertEquals(expectedOutput, sw.toString());
     }
 
-    private void setLazynessOfConfigurable(Configurable cfg, Boolean lazyImports, Boolean lazyAutoImports,
+    private void setLazynessOfConfigurable(MutableProcessingConfiguration cfg, Boolean lazyImports, Boolean lazyAutoImports,
             boolean setLazyAutoImports) {
         if (lazyImports != null) {
             cfg.setLazyImports(lazyImports);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 1b36efe..66bdef6 100644
--- a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
@@ -357,17 +357,17 @@ public class ObjectBuilderSettingsTest {
         
         {
             Properties props = new Properties();
-            props.setProperty(Configurable.OBJECT_WRAPPER_KEY,
+            props.setProperty(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY,
                     "org.apache.freemarker.core.model.impl.DefaultObjectWrapper(3.0.0)");
-            props.setProperty(Configurable.ARITHMETIC_ENGINE_KEY,
+            props.setProperty(MutableProcessingConfiguration.ARITHMETIC_ENGINE_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyArithmeticEngine");
-            props.setProperty(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY,
+            props.setProperty(MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateExceptionHandler");
             props.setProperty(Configuration.CACHE_STORAGE_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyCacheStorage()");
-            props.setProperty(Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY,
+            props.setProperty(MutableProcessingConfiguration.NEW_BUILTIN_CLASS_RESOLVER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyNewBuiltinClassResolver()");
-            props.setProperty(Configuration.DEFAULT_ENCODING_KEY, "utf-8");
+            props.setProperty(Configuration.ENCODING_KEY, "utf-8");
             props.setProperty(Configuration.TEMPLATE_LOADER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateLoader()");
             cfg.setSettings(props);
@@ -379,19 +379,19 @@ public class ObjectBuilderSettingsTest {
             assertEquals(DummyCacheStorage.class, cfg.getCacheStorage().getClass());
             assertEquals(DummyNewBuiltinClassResolver.class, cfg.getNewBuiltinClassResolver().getClass());
             assertEquals(DummyTemplateLoader.class, cfg.getTemplateLoader().getClass());
-            assertEquals("utf-8", cfg.getDefaultEncoding());
+            assertEquals("utf-8", cfg.getEncoding());
         }
         
         {
             Properties props = new Properties();
-            props.setProperty(Configurable.OBJECT_WRAPPER_KEY, "defAult");
-            props.setProperty(Configurable.ARITHMETIC_ENGINE_KEY,
+            props.setProperty(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "defAult");
+            props.setProperty(MutableProcessingConfiguration.ARITHMETIC_ENGINE_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyArithmeticEngine(x = 1)");
-            props.setProperty(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY,
+            props.setProperty(MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateExceptionHandler(x = 1)");
             props.setProperty(Configuration.CACHE_STORAGE_KEY,
                     "soft: 500, strong: 100");
-            props.setProperty(Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY,
+            props.setProperty(MutableProcessingConfiguration.NEW_BUILTIN_CLASS_RESOLVER_KEY,
                     "allows_nothing");
             cfg.setSettings(props);
             assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
@@ -401,14 +401,14 @@ public class ObjectBuilderSettingsTest {
                     ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
             assertEquals(500, ((MruCacheStorage) cfg.getCacheStorage()).getSoftSizeLimit());
             assertEquals(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER, cfg.getNewBuiltinClassResolver());
-            assertEquals("utf-8", cfg.getDefaultEncoding());
+            assertEquals("utf-8", cfg.getEncoding());
         }
 
         {
             Properties props = new Properties();
-            props.setProperty(Configurable.OBJECT_WRAPPER_KEY, "Default");
-            props.setProperty(Configurable.ARITHMETIC_ENGINE_KEY, "bigdecimal");
-            props.setProperty(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY, "rethrow");
+            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());
@@ -419,7 +419,7 @@ public class ObjectBuilderSettingsTest {
         
         {
             Properties props = new Properties();
-            props.setProperty(Configurable.OBJECT_WRAPPER_KEY, "DefaultObjectWrapper(3.0.0)");
+            props.setProperty(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "DefaultObjectWrapper(3.0.0)");
             cfg.setSettings(props);
             assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
             assertEquals(

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 2690905..07c3a35 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -30,6 +30,7 @@ import java.io.StringReader;
 import java.io.StringWriter;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -50,6 +51,7 @@ import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
 import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.FileExtensionMatcher;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory;
 import org.apache.freemarker.core.userpkg.EpochMillisDivTemplateDateFormatFactory;
@@ -60,6 +62,7 @@ import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFac
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+import org.apache.freemarker.test.MonitoredTemplateLoader;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -146,7 +149,7 @@ public class TemplateConfigurationTest {
     private static final String NON_DEFAULT_ENCODING;
 
     static {
-        String defaultEncoding = DEFAULT_CFG.getDefaultEncoding();
+        String defaultEncoding = DEFAULT_CFG.getEncoding();
         String encoding = "UTF-16";
         if (encoding.equals(defaultEncoding)) {
             encoding = "UTF-8";
@@ -162,7 +165,7 @@ public class TemplateConfigurationTest {
     static {
         SETTING_ASSIGNMENTS = new HashMap<>();
 
-        // "Configurable" settings:
+        // "MutableProcessingConfiguration" settings:
         SETTING_ASSIGNMENTS.put("APIBuiltinEnabled", true);
         SETTING_ASSIGNMENTS.put("SQLDateAndTimeTimeZone", NON_DEFAULT_TZ);
         SETTING_ASSIGNMENTS.put("URLEscapingCharset", "utf-16");
@@ -185,6 +188,7 @@ public class TemplateConfigurationTest {
                 ImmutableMap.of("dummy", HexTemplateNumberFormatFactory.INSTANCE));
         SETTING_ASSIGNMENTS.put("customDateFormats",
                 ImmutableMap.of("dummy", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        SETTING_ASSIGNMENTS.put("customAttributes", ImmutableMap.of("dummy", 123));
 
         // Parser-only settings:
         SETTING_ASSIGNMENTS.put("templateLanguage", TemplateLanguage.STATIC_TEXT);
@@ -256,7 +260,7 @@ public class TemplateConfigurationTest {
     static {
         CONFIGURABLE_PROP_NAMES = new HashSet<>();
         try {
-            for (PropertyDescriptor propDesc : Introspector.getBeanInfo(Configurable.class).getPropertyDescriptors()) {
+            for (PropertyDescriptor propDesc : Introspector.getBeanInfo(MutableProcessingConfiguration.class).getPropertyDescriptors()) {
                 String propName = propDesc.getName();
                 if (!IGNORED_PROP_NAMES.contains(propName)) {
                     CONFIGURABLE_PROP_NAMES.add(propName);
@@ -273,10 +277,11 @@ public class TemplateConfigurationTest {
         // It's an interface; can't use standard Inrospector
         for (Method m : ParserConfiguration.class.getMethods()) {
             String propertyName;
-            if (m.getName().startsWith("get")) {
-                propertyName = m.getName().substring(3);
-            } else if (m.getName().startsWith("is")) {
-                propertyName = m.getName().substring(2);
+            String name = m.getName();
+            if (name.startsWith("get")) {
+                propertyName = name.substring(3);
+            } else if (name.startsWith("is") && !name.endsWith("Set")) {
+                propertyName = name.substring(2);
             } else {
                 propertyName = null;
             }
@@ -295,10 +300,10 @@ public class TemplateConfigurationTest {
         SPECIAL_PROP_NAMES.add("encoding");
     }
     
-    private static final CustomAttribute CA1 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE); 
-    private static final CustomAttribute CA2 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE); 
-    private static final CustomAttribute CA3 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE); 
-    private static final CustomAttribute CA4 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE); 
+    private static final Object CA1 = new Object();
+    private static final Object CA2 = new Object();
+    private static final Object CA3 = new Object();
+    private static final Object CA4 = new Object();
 
     @Test
     public void testMergeBasicFunctionality() throws Exception {
@@ -415,19 +420,19 @@ public class TemplateConfigurationTest {
         tc1.setCustomAttribute("k1", "v1");
         tc1.setCustomAttribute("k2", "v1");
         tc1.setCustomAttribute("k3", "v1");
-        CA1.set("V1", tc1);
-        CA2.set("V1", tc1);
-        CA3.set("V1", tc1);
+        tc1.setCustomAttribute(CA1, "V1");
+        tc1.setCustomAttribute(CA2, "V1");
+        tc1.setCustomAttribute(CA3, "V1");
 
         TemplateConfiguration tc2 = new TemplateConfiguration();
         tc2.setCustomAttribute("k1", "v2");
         tc2.setCustomAttribute("k2", "v2");
-        CA1.set("V2", tc2);
-        CA2.set("V2", tc2);
+        tc2.setCustomAttribute(CA1, "V2");
+        tc2.setCustomAttribute(CA2, "V2");
 
         TemplateConfiguration tc3 = new TemplateConfiguration();
         tc3.setCustomAttribute("k1", "v3");
-        CA1.set("V3", tc2);
+        tc3.setCustomAttribute(CA1, "V3");
 
         tc1.merge(tc2);
         tc1.merge(tc3);
@@ -435,37 +440,35 @@ public class TemplateConfigurationTest {
         assertEquals("v3", tc1.getCustomAttribute("k1"));
         assertEquals("v2", tc1.getCustomAttribute("k2"));
         assertEquals("v1", tc1.getCustomAttribute("k3"));
-        assertEquals("V3", CA1.get(tc1));
-        assertEquals("V2", CA2.get(tc1));
-        assertEquals("V1", CA3.get(tc1));
+        assertEquals("V3", tc1.getCustomAttribute(CA1));
+        assertEquals("V2", tc1.getCustomAttribute(CA2));
+        assertEquals("V1", tc1.getCustomAttribute(CA3));
     }
-    
+
     @Test
     public void testMergeNullCustomAttributes() throws Exception {
         TemplateConfiguration tc1 = new TemplateConfiguration();
         tc1.setCustomAttribute("k1", "v1");
         tc1.setCustomAttribute("k2", "v1");
-        tc1.setCustomAttribute(null, "v1");
-        CA1.set("V1", tc1);
-        CA2.set("V1", tc1);
-        CA3.set(null, tc1);
-        
+        tc1.setCustomAttribute(CA1, "V1");
+        tc1.setCustomAttribute(CA2,"V1");
+
         assertEquals("v1", tc1.getCustomAttribute("k1"));
         assertEquals("v1", tc1.getCustomAttribute("k2"));
         assertNull("v1", tc1.getCustomAttribute("k3"));
-        assertEquals("V1", CA1.get(tc1));
-        assertEquals("V1", CA2.get(tc1));
-        assertNull(CA3.get(tc1));
+        assertEquals("V1", tc1.getCustomAttribute(CA1));
+        assertEquals("V1", tc1.getCustomAttribute(CA2));
+        assertNull(tc1.getCustomAttribute(CA3));
 
         TemplateConfiguration tc2 = new TemplateConfiguration();
         tc2.setCustomAttribute("k1", "v2");
         tc2.setCustomAttribute("k2", null);
-        CA1.set("V2", tc2);
-        CA2.set(null, tc2);
+        tc2.setCustomAttribute(CA1, "V2");
+        tc2.setCustomAttribute(CA2, null);
 
         TemplateConfiguration tc3 = new TemplateConfiguration();
         tc3.setCustomAttribute("k1", null);
-        CA1.set(null, tc2);
+        tc2.setCustomAttribute(CA1, null);
 
         tc1.merge(tc2);
         tc1.merge(tc3);
@@ -473,24 +476,24 @@ public class TemplateConfigurationTest {
         assertNull(tc1.getCustomAttribute("k1"));
         assertNull(tc1.getCustomAttribute("k2"));
         assertNull(tc1.getCustomAttribute("k3"));
-        assertNull(CA1.get(tc1));
-        assertNull(CA2.get(tc1));
-        assertNull(CA3.get(tc1));
-        
+        assertNull(tc1.getCustomAttribute(CA1));
+        assertNull(tc1.getCustomAttribute(CA2));
+        assertNull(tc1.getCustomAttribute(CA3));
+
         TemplateConfiguration tc4 = new TemplateConfiguration();
         tc4.setCustomAttribute("k1", "v4");
-        CA1.set("V4", tc4);
-        
+        tc4.setCustomAttribute(CA1, "V4");
+
         tc1.merge(tc4);
-        
+
         assertEquals("v4", tc1.getCustomAttribute("k1"));
         assertNull(tc1.getCustomAttribute("k2"));
         assertNull(tc1.getCustomAttribute("k3"));
-        assertEquals("V4", CA1.get(tc1));
-        assertNull(CA2.get(tc1));
-        assertNull(CA3.get(tc1));
+        assertEquals("V4", tc1.getCustomAttribute(CA1));
+        assertNull(tc1.getCustomAttribute(CA2));
+        assertNull(tc1.getCustomAttribute(CA3));
     }
-    
+
     @Test
     public void applyOrder() throws Exception {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
@@ -567,17 +570,17 @@ public class TemplateConfigurationTest {
         tc.setCustomAttribute("k4", "tc");
         tc.setCustomAttribute("k5", "tc");
         tc.setCustomAttribute("k6", "tc");
-        CA1.set("tc", tc);
-        CA2.set("tc", tc);
-        CA3.set("tc", tc);
+        tc.setCustomAttribute(CA1, "tc");
+        tc.setCustomAttribute(CA2,"tc");
+        tc.setCustomAttribute(CA3,"tc");
 
         Template t = new Template(null, "", cfg);
         t.setCustomAttribute("k5", "t");
         t.setCustomAttribute("k6", null);
         t.setCustomAttribute("k7", "t");
-        CA2.set("t", t);
-        CA3.set(null, t);
-        CA4.set("t", t);
+        t.setCustomAttribute(CA2, "t");
+        t.setCustomAttribute(CA3, null);
+        t.setCustomAttribute(CA4, "t");
         
         tc.setParentConfiguration(cfg);
         tc.apply(t);
@@ -589,10 +592,10 @@ public class TemplateConfigurationTest {
         assertEquals("t", t.getCustomAttribute("k5"));
         assertNull(t.getCustomAttribute("k6"));
         assertEquals("t", t.getCustomAttribute("k7"));
-        assertEquals("tc", CA1.get(t));
-        assertEquals("t", CA2.get(t));
-        assertNull(CA3.get(t));
-        assertEquals("t", CA4.get(t));
+        assertEquals("tc", t.getCustomAttribute(CA1));
+        assertEquals("t", t.getCustomAttribute(CA2));
+        assertNull(t.getCustomAttribute(CA3));
+        assertEquals("t", t.getCustomAttribute(CA4));
     }
     
     @Test
@@ -711,7 +714,42 @@ public class TemplateConfigurationTest {
             testedProps.add(Configuration.TEMPLATE_LANGUAGE_KEY_CAMEL_CASE);
         }
 
-        assertEquals("Check that you have tested all parser settings; ", PARSER_PROP_NAMES, testedProps);
+        {
+            // As the TemplateLanguage-based parser selection happens in the TemplateResolver, we can't use
+            // assertOutput here, as that hard-coded to create an FTL Template.
+
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setEncoding("ISO-8859-1");
+
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            cfg.setEncoding("utf-8");
+            cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher
+                    ("latin1.ftl"), tc));
+
+            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);
+
+            {
+                StringWriter out = new StringWriter();
+                cfg.getTemplate("utf8.ftl").process(null, out);
+                assertEquals("pr�ba", out.toString());
+            }
+            {
+                StringWriter out = new StringWriter();
+                cfg.getTemplate("latin1.ftl").process(null, out);
+                assertEquals("pr�ba", out.toString());
+            }
+
+            testedProps.add(Configuration.ENCODING_KEY_CAMEL_CASE);
+        }
+
+        if (!PARSER_PROP_NAMES.equals(testedProps)) {
+            Set<String> diff = new HashSet<>(PARSER_PROP_NAMES);
+            diff.removeAll(testedProps);
+            fail("Some settings weren't checked: " + diff);
+        }
     }
     
     @Test
@@ -940,7 +978,7 @@ public class TemplateConfigurationTest {
             String cfgMethodName = readMethod.getName();
             if (cfgMethodName.equals("getEncoding")) {
                 // Because Configuration has local-to-encoding map too, this has a different name there.
-                cfgMethodName = "getDefaultEncoding";
+                cfgMethodName = "getEncoding";
             }
             Method cfgMethod = DEFAULT_CFG.getClass().getMethod(cfgMethodName, readMethod.getParameterTypes());
             Object defaultSettingValue = cfgMethod.invoke(DEFAULT_CFG);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
index 700e718..34068e5 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaltTemplateResolverTest.java
@@ -37,8 +37,8 @@ public class TemplateConfigurationWithDefaltTemplateResolverTest {
 
     private static final String TEXT_WITH_ACCENTS = "pr\u00F3ba";
 
-    private static final CustomAttribute CUST_ATT_1 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
-    private static final CustomAttribute CUST_ATT_2 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
+    private static final Object CUST_ATT_1 = new Object();
+    private static final Object CUST_ATT_2 = new Object();
 
     @Test
     public void testEncoding() throws Exception {
@@ -168,12 +168,12 @@ public class TemplateConfigurationWithDefaltTemplateResolverTest {
         tc1.setCustomAttribute("a1", "a1tc1");
         tc1.setCustomAttribute("a2", "a2tc1");
         tc1.setCustomAttribute("a3", "a3tc1");
-        CUST_ATT_1.set("ca1tc1", tc1);
-        CUST_ATT_2.set("ca2tc1", tc1);
+        tc1.setCustomAttribute(CUST_ATT_1, "ca1tc1");
+        tc1.setCustomAttribute(CUST_ATT_2, "ca2tc1");
         
         TemplateConfiguration tc2 = new TemplateConfiguration();
         tc2.setCustomAttribute("a1", "a1tc2");
-        CUST_ATT_1.set("ca1tc2", tc2);
+        tc2.setCustomAttribute(CUST_ATT_1, "ca1tc2");
         
         cfg.setTemplateConfigurations(
                 new MergingTemplateConfigurationFactory(
@@ -195,32 +195,32 @@ public class TemplateConfigurationWithDefaltTemplateResolverTest {
             assertEquals("a1tc1", t.getCustomAttribute("a1"));
             assertEquals("a2tc1", t.getCustomAttribute("a2"));
             assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc1", CUST_ATT_1.get(t));
-            assertEquals("ca2tc1", CUST_ATT_2.get(t));
+            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
+            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
         }
         {
             Template t = cfg.getTemplate("(tc1)noHeader");
             assertEquals("a1tc1", t.getCustomAttribute("a1"));
             assertEquals("a2tc1", t.getCustomAttribute("a2"));
             assertEquals("a3tc1", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc1", CUST_ATT_1.get(t));
-            assertEquals("ca2tc1", CUST_ATT_2.get(t));
+            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
+            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
         }
         {
             Template t = cfg.getTemplate("(tc2)");
             assertEquals("a1tc2", t.getCustomAttribute("a1"));
             assertNull(t.getCustomAttribute("a2"));
             assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc2", CUST_ATT_1.get(t));
-            assertNull(CUST_ATT_2.get(t));
+            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
+            assertNull(t.getCustomAttribute(CUST_ATT_2));
         }
         {
             Template t = cfg.getTemplate("(tc1)(tc2)");
             assertEquals("a1tc2", t.getCustomAttribute("a1"));
             assertEquals("a2tc1", t.getCustomAttribute("a2"));
             assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc2", CUST_ATT_1.get(t));
-            assertEquals("ca2tc1", CUST_ATT_2.get(t));
+            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
+            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
         }
     }
     
@@ -232,7 +232,7 @@ public class TemplateConfigurationWithDefaltTemplateResolverTest {
 
     private Configuration createCommonEncodingTesterConfig() throws UnsupportedEncodingException {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        cfg.setDefaultEncoding("iso-8859-1");
+        cfg.setEncoding("iso-8859-1");
         cfg.setLocale(Locale.US);
         
         ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 cec01d7..3a83bca 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java
@@ -35,7 +35,7 @@ public class TemplateConstructorsTest {
     @Test
     public void test() throws IOException {
         final Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        //cfg.setDefaultEncoding("ISO-8859-1");
+        //cfg.setEncoding("ISO-8859-1");
         
         final String name = "foo/bar.ftl";
         final String sourceName = "foo/bar_de.ftl";

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 c387d36..9669369 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateGetEncodingTest.java
@@ -34,7 +34,7 @@ public class TemplateGetEncodingTest {
     public void test() throws IOException {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         {
-            cfg.setDefaultEncoding("ISO-8859-2");
+            cfg.setEncoding("ISO-8859-2");
             MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
             tl.putBinaryTemplate("bin", "test");
             tl.putBinaryTemplate("bin-static", "<#test>");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 a80305d..598df6b 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/DefaultTemplateResolverTest.java
@@ -273,7 +273,7 @@ public class DefaultTemplateResolverTest {
     public void testWrongEncodingReload() throws IOException {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         cfg.setLocale(Locale.US);
-        cfg.setDefaultEncoding("utf-8");
+        cfg.setEncoding("utf-8");
         
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putBinaryTemplate("utf-8_en.ftl", "<#ftl encoding='utf-8'>B�ka");
@@ -320,7 +320,7 @@ public class DefaultTemplateResolverTest {
     public void testNoWrongEncodingForTemplateLoader2WithReader() throws IOException {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         cfg.setLocale(Locale.US);
-        cfg.setDefaultEncoding("utf-8");
+        cfg.setEncoding("utf-8");
         
         MonitoredTemplateLoader tl = new MonitoredTemplateLoader();
         tl.putTextTemplate("foo_en.ftl", "<#ftl encoding='utf-8'>\u0151");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 04fa57d..f03e417 100644
--- a/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java
+++ b/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java
@@ -38,7 +38,7 @@ public class GettingStartedExample {
         /* Create and adjust the configuration singleton */
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         cfg.setClassForTemplateLoading(GettingStartedExample.class, "");
-        cfg.setDefaultEncoding("UTF-8");
+        cfg.setEncoding("UTF-8");
         cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
         cfg.setLogTemplateExceptions(false);
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 b2d6476..bd84755 100644
--- a/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
+++ b/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
@@ -115,7 +115,7 @@ public class TemplateConfigurationExamples extends ExamplesTest {
     @Test
     public void example3() throws Exception {
         Configuration cfg = getConfiguration();
-        cfg.setDefaultEncoding("ISO-8859-1");
+        cfg.setEncoding("ISO-8859-1");
         cfg.setSharedVariable("ts", new Date(1440431606011L));
         
         addTemplate("t.stats.html", "${ts?datetime} ${ts?date} ${ts?time}");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 28edd83..0a3da35 100644
--- a/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
@@ -412,7 +412,7 @@ public class FreemarkerServletTest {
         }
 
         MockServletConfig servletConfig = new MockServletConfig(servletContext);
-        servletConfig.addInitParameter(Configuration.DEFAULT_ENCODING_KEY, "UTF-8");
+        servletConfig.addInitParameter(Configuration.ENCODING_KEY, "UTF-8");
         if (ctInitParam != null) {
             servletConfig.addInitParameter(INIT_PARAM_CONTENT_TYPE, ctInitParam);
         }
@@ -540,7 +540,7 @@ public class FreemarkerServletTest {
 
             // Set a test runner environment independent default locale:
             cfg.setLocale(DEFAULT_LOCALE);
-            cfg.setDefaultEncoding(CFG_DEFAULT_ENCODING);
+            cfg.setEncoding(CFG_DEFAULT_ENCODING);
 
             {
                 TemplateConfiguration outUtf8TC = new TemplateConfiguration();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 d2eeb72..31ad3e0 100644
--- a/src/test/java/org/apache/freemarker/test/TemplateTest.java
+++ b/src/test/java/org/apache/freemarker/test/TemplateTest.java
@@ -78,7 +78,7 @@ public abstract class TemplateTest {
             configuration.setLocale(Locale.US);
         }
         if (!configuration.isDefaultEncodingExplicitlySet()) {
-            configuration.setDefaultEncoding(StandardCharsets.UTF_8.name());
+            configuration.setEncoding(StandardCharsets.UTF_8.name());
         }
         if (!configuration.isTimeZoneExplicitlySet()) {
             configuration.setTimeZone(TimeZone.getTimeZone("GMT+1"));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 b2f8c26..fccef43 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
@@ -147,7 +147,7 @@ public class TemplateTestCase extends FileTestCase {
             String alias = st.nextToken();
             conf.addAutoImport(alias, libname);
         } else if ("input_encoding".equals(param)) {
-            conf.setDefaultEncoding(value);
+            conf.setEncoding(value);
         // INCOMPATIBLE_IMPROVEMENTS is a list here, and was already set in the constructor.
         } else if (!Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY.equals(param)) {
             try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/web.xml b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/web.xml
index e8ce83e..e84ca81 100644
--- a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/web.xml
+++ b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/web.xml
@@ -71,7 +71,7 @@
 			<param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
 		</init-param>
 		<init-param>
-			<param-name>default_encoding</param-name>
+			<param-name>encoding</param-name>
 			<param-value>UTF-8</param-value> <!-- The encoding of the template files. -->
 		</init-param>
         <init-param>
@@ -110,7 +110,7 @@
             <param-value>en_US</param-value>
         </init-param>
         <init-param>
-            <param-name>default_encoding</param-name>
+            <param-name>encoding</param-name>
             <param-value>UTF-8</param-value> <!-- The encoding of the template files. -->
         </init-param>
         <init-param>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/multipleLoaders/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/multipleLoaders/WEB-INF/web.xml b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/multipleLoaders/WEB-INF/web.xml
index 72e1c6e..f83c942 100644
--- a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/multipleLoaders/WEB-INF/web.xml
+++ b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/multipleLoaders/WEB-INF/web.xml
@@ -51,7 +51,7 @@
 			<param-value>rethrow</param-value>
 		</init-param>
 		<init-param>
-			<param-name>default_encoding</param-name>
+			<param-name>encoding</param-name>
 			<param-value>UTF-8</param-value> <!-- The encoding of the template files. -->
 		</init-param>
         <init-param>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/tldDiscovery/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/tldDiscovery/WEB-INF/web.xml b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/tldDiscovery/WEB-INF/web.xml
index 0b6e76f..3508238 100644
--- a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/tldDiscovery/WEB-INF/web.xml
+++ b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/tldDiscovery/WEB-INF/web.xml
@@ -71,7 +71,7 @@
       <param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
     </init-param>
     <init-param>
-      <param-name>default_encoding</param-name>
+      <param-name>encoding</param-name>
       <param-value>UTF-8</param-value> <!-- The encoding of the template files. -->
     </init-param>
     <init-param>
@@ -125,7 +125,7 @@
       <param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
     </init-param>
     <init-param>
-      <param-name>default_encoding</param-name>
+      <param-name>encoding</param-name>
       <param-value>UTF-8</param-value> <!-- The encoding of the template files. -->
     </init-param>
     <init-param>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/test/resources/org/apache/freemarker/test/servlet/web.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/servlet/web.xml b/src/test/resources/org/apache/freemarker/test/servlet/web.xml
index 51eb42a..b54ea92 100644
--- a/src/test/resources/org/apache/freemarker/test/servlet/web.xml
+++ b/src/test/resources/org/apache/freemarker/test/servlet/web.xml
@@ -69,7 +69,7 @@
 			<param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
 		</init-param>
 		<init-param>
-			<param-name>default_encoding</param-name>
+			<param-name>encoding</param-name>
 			<param-value>UTF-8</param-value> <!-- The encoding of the template files. -->
 		</init-param>
 		<init-param>


[3/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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
new file mode 100644
index 0000000..eef4188
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -0,0 +1,2890 @@
+/*
+ * 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.io.IOException;
+import java.io.Writer;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
+import org.apache.freemarker.core.templateresolver.AndMatcher;
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.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;
+import org.apache.freemarker.core.util.GenericParseException;
+import org.apache.freemarker.core.util.OptInTemplateClassResolver;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._CollectionUtil;
+import org.apache.freemarker.core.util._NullArgumentException;
+import org.apache.freemarker.core.util._SortedArraySet;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+
+/**
+ * This is a common superclass of {@link org.apache.freemarker.core.Configuration},
+ * {@link org.apache.freemarker.core.Template}, and {@link Environment} classes.
+ * It provides settings that are common to each of them. FreeMarker
+ * uses a three-level setting hierarchy - the return value of every setting
+ * getter method on <code>MutableProcessingConfiguration</code> objects inherits its value from its parent
+ * <code>MutableProcessingConfiguration</code> object, unless explicitly overridden by a call to a
+ * corresponding setter method on the object itself. The parent of an 
+ * <code>Environment</code> object is a <code>Template</code> object, the
+ * parent of a <code>Template</code> object is a <code>Configuration</code>
+ * object.
+ */
+public abstract class MutableProcessingConfiguration<SelfT extends MutableProcessingConfiguration<SelfT>>
+        implements ProcessingConfiguration {
+    static final String C_TRUE_FALSE = "true,false";
+    
+    public static final String NULL_VALUE = "null";
+    public static final String DEFAULT_VALUE = "default";
+    public static final String JVM_DEFAULT_VALUE = "JVM default";
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String LOCALE_KEY_SNAKE_CASE = "locale";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String LOCALE_KEY_CAMEL_CASE = "locale";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String LOCALE_KEY = LOCALE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String NUMBER_FORMAT_KEY_SNAKE_CASE = "number_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String NUMBER_FORMAT_KEY_CAMEL_CASE = "numberFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String NUMBER_FORMAT_KEY = NUMBER_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE = "custom_number_formats";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE = "customNumberFormats";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String CUSTOM_NUMBER_FORMATS_KEY = CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String TIME_FORMAT_KEY_SNAKE_CASE = "time_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String TIME_FORMAT_KEY_CAMEL_CASE = "timeFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String TIME_FORMAT_KEY = TIME_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String DATE_FORMAT_KEY_SNAKE_CASE = "date_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String DATE_FORMAT_KEY_CAMEL_CASE = "dateFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String DATE_FORMAT_KEY = DATE_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE = "custom_date_formats";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE = "customDateFormats";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String CUSTOM_DATE_FORMATS_KEY = CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String DATETIME_FORMAT_KEY_SNAKE_CASE = "datetime_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String DATETIME_FORMAT_KEY_CAMEL_CASE = "datetimeFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String DATETIME_FORMAT_KEY = DATETIME_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String TIME_ZONE_KEY_SNAKE_CASE = "time_zone";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String TIME_ZONE_KEY_CAMEL_CASE = "timeZone";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String TIME_ZONE_KEY = TIME_ZONE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE = "sql_date_and_time_time_zone";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE = "sqlDateAndTimeTimeZone";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY = SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE = "template_exception_handler";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE = "templateExceptionHandler";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY = TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String ARITHMETIC_ENGINE_KEY_SNAKE_CASE = "arithmetic_engine";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String ARITHMETIC_ENGINE_KEY_CAMEL_CASE = "arithmeticEngine";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String ARITHMETIC_ENGINE_KEY = ARITHMETIC_ENGINE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String OBJECT_WRAPPER_KEY_SNAKE_CASE = "object_wrapper";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String OBJECT_WRAPPER_KEY_CAMEL_CASE = "objectWrapper";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String OBJECT_WRAPPER_KEY = OBJECT_WRAPPER_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String BOOLEAN_FORMAT_KEY_SNAKE_CASE = "boolean_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String BOOLEAN_FORMAT_KEY_CAMEL_CASE = "booleanFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String BOOLEAN_FORMAT_KEY = BOOLEAN_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String OUTPUT_ENCODING_KEY_SNAKE_CASE = "output_encoding";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String OUTPUT_ENCODING_KEY_CAMEL_CASE = "outputEncoding";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String OUTPUT_ENCODING_KEY = OUTPUT_ENCODING_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String URL_ESCAPING_CHARSET_KEY_SNAKE_CASE = "url_escaping_charset";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String URL_ESCAPING_CHARSET_KEY_CAMEL_CASE = "urlEscapingCharset";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String URL_ESCAPING_CHARSET_KEY = URL_ESCAPING_CHARSET_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String AUTO_FLUSH_KEY_SNAKE_CASE = "auto_flush";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String AUTO_FLUSH_KEY_CAMEL_CASE = "autoFlush";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.17 */
+    public static final String AUTO_FLUSH_KEY = AUTO_FLUSH_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE = "new_builtin_class_resolver";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE = "newBuiltinClassResolver";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.17 */
+    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY = NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String SHOW_ERROR_TIPS_KEY_SNAKE_CASE = "show_error_tips";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String SHOW_ERROR_TIPS_KEY_CAMEL_CASE = "showErrorTips";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.21 */
+    public static final String SHOW_ERROR_TIPS_KEY = SHOW_ERROR_TIPS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String API_BUILTIN_ENABLED_KEY_SNAKE_CASE = "api_builtin_enabled";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String API_BUILTIN_ENABLED_KEY_CAMEL_CASE = "apiBuiltinEnabled";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.22 */
+    public static final String API_BUILTIN_ENABLED_KEY = API_BUILTIN_ENABLED_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
+    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE = "log_template_exceptions";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
+    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE = "logTemplateExceptions";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.22 */
+    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY = LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE;
+
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
+    public static final String LAZY_IMPORTS_KEY_SNAKE_CASE = "lazy_imports";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
+    public static final String LAZY_IMPORTS_KEY_CAMEL_CASE = "lazyImports";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String LAZY_IMPORTS_KEY = LAZY_IMPORTS_KEY_SNAKE_CASE;
+
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
+    public static final String LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE = "lazy_auto_imports";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
+    public static final String LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE = "lazyAutoImports";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String LAZY_AUTO_IMPORTS_KEY = LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
+    public static final String AUTO_IMPORT_KEY_SNAKE_CASE = "auto_import";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
+    public static final String AUTO_IMPORT_KEY_CAMEL_CASE = "autoImport";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+    public static final String AUTO_IMPORT_KEY = AUTO_IMPORT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
+    public static final String AUTO_INCLUDE_KEY_SNAKE_CASE = "auto_include";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
+    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;
+    
+    private static final String[] SETTING_NAMES_SNAKE_CASE = new String[] {
+        // Must be sorted alphabetically!
+        API_BUILTIN_ENABLED_KEY_SNAKE_CASE,
+        ARITHMETIC_ENGINE_KEY_SNAKE_CASE,
+        AUTO_FLUSH_KEY_SNAKE_CASE,
+        AUTO_IMPORT_KEY_SNAKE_CASE,
+        AUTO_INCLUDE_KEY_SNAKE_CASE,
+        BOOLEAN_FORMAT_KEY_SNAKE_CASE,
+        CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
+        CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
+        DATE_FORMAT_KEY_SNAKE_CASE,
+        DATETIME_FORMAT_KEY_SNAKE_CASE,
+        LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE,
+        LAZY_IMPORTS_KEY_SNAKE_CASE,
+        LOCALE_KEY_SNAKE_CASE,
+        LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE,
+        NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE,
+        NUMBER_FORMAT_KEY_SNAKE_CASE,
+        OBJECT_WRAPPER_KEY_SNAKE_CASE,
+        OUTPUT_ENCODING_KEY_SNAKE_CASE,
+        SHOW_ERROR_TIPS_KEY_SNAKE_CASE,
+        SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE,
+        TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE,
+        TIME_FORMAT_KEY_SNAKE_CASE,
+        TIME_ZONE_KEY_SNAKE_CASE,
+        URL_ESCAPING_CHARSET_KEY_SNAKE_CASE
+    };
+    
+    private static final String[] SETTING_NAMES_CAMEL_CASE = new String[] {
+        // Must be sorted alphabetically!
+        API_BUILTIN_ENABLED_KEY_CAMEL_CASE,
+        ARITHMETIC_ENGINE_KEY_CAMEL_CASE,
+        AUTO_FLUSH_KEY_CAMEL_CASE,
+        AUTO_IMPORT_KEY_CAMEL_CASE,
+        AUTO_INCLUDE_KEY_CAMEL_CASE,
+        BOOLEAN_FORMAT_KEY_CAMEL_CASE,
+        CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
+        CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
+        DATE_FORMAT_KEY_CAMEL_CASE,
+        DATETIME_FORMAT_KEY_CAMEL_CASE,
+        LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE,
+        LAZY_IMPORTS_KEY_CAMEL_CASE,
+        LOCALE_KEY_CAMEL_CASE,
+        LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE,
+        NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE,
+        NUMBER_FORMAT_KEY_CAMEL_CASE,
+        OBJECT_WRAPPER_KEY_CAMEL_CASE,
+        OUTPUT_ENCODING_KEY_CAMEL_CASE,
+        SHOW_ERROR_TIPS_KEY_CAMEL_CASE,
+        SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
+        TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE,
+        TIME_FORMAT_KEY_CAMEL_CASE,
+        TIME_ZONE_KEY_CAMEL_CASE,
+        URL_ESCAPING_CHARSET_KEY_CAMEL_CASE
+    };
+
+    private MutableProcessingConfiguration parent;
+    private Map<Object, Object> customAttributes;
+    
+    private Locale locale;
+    private String numberFormat;
+    private String timeFormat;
+    private String dateFormat;
+    private String dateTimeFormat;
+    private TimeZone timeZone;
+    private TimeZone sqlDataAndTimeTimeZone;
+    private boolean sqlDataAndTimeTimeZoneSet;
+    private String booleanFormat;
+    private String trueStringValue;  // deduced from booleanFormat
+    private String falseStringValue;  // deduced from booleanFormat
+    private TemplateExceptionHandler templateExceptionHandler;
+    private ArithmeticEngine arithmeticEngine;
+    private ObjectWrapper objectWrapper;
+    private String outputEncoding;
+    private boolean outputEncodingSet;
+    private String urlEscapingCharset;
+    private boolean urlEscapingCharsetSet;
+    private Boolean autoFlush;
+    private TemplateClassResolver newBuiltinClassResolver;
+    private Boolean showErrorTips;
+    private Boolean apiBuiltinEnabled;
+    private Boolean logTemplateExceptions;
+    private Map<String, ? extends TemplateDateFormatFactory> customDateFormats;
+    private Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats;
+    private LinkedHashMap<String, String> autoImports;
+    private ArrayList<String> autoIncludes;
+    private Boolean lazyImports;
+    private Boolean lazyAutoImports;
+    private boolean lazyAutoImportsSet;
+    
+    /**
+     * Intended to be called from inside FreeMarker only.
+     * Creates a top-level configurable, one that doesn't inherit from a parent, and thus stores the default values.
+     * Called by the {@link Configuration} constructor.
+     */
+    protected MutableProcessingConfiguration(Version incompatibleImprovements) {
+        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+        parent = null;
+        locale = Configuration.getDefaultLocale();
+        timeZone = Configuration.getDefaultTimeZone();
+        sqlDataAndTimeTimeZone = null;
+        numberFormat = "number";
+        timeFormat = "";
+        dateFormat = "";
+        dateTimeFormat = "";
+        templateExceptionHandler = Configuration.getDefaultTemplateExceptionHandler();
+        arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE;
+        objectWrapper = Configuration.getDefaultObjectWrapper(incompatibleImprovements);
+        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(C_TRUE_FALSE);
+
+        customDateFormats = Collections.emptyMap();
+        customNumberFormats = Collections.emptyMap();
+        
+        lazyImports = false;
+        lazyAutoImportsSet = true;
+        
+        initAutoImportsMap();
+        initAutoIncludesList();
+    }
+
+    /**
+     * Creates a new instance. Normally you do not need to use this constructor,
+     * as you don't use <code>MutableProcessingConfiguration</code> directly, but its subclasses.
+     */
+    protected MutableProcessingConfiguration(MutableProcessingConfiguration parent) {
+        this.parent = parent;
+        locale = null;
+        numberFormat = null;
+        templateExceptionHandler = null;
+    }
+    
+    /**
+     * Returns the parent {@link MutableProcessingConfiguration} object of this object. The parent stores the default setting values for
+     * this {@link MutableProcessingConfiguration}. For example, the parent of a {@link org.apache.freemarker.core.Template} object is a
+     * {@link Configuration} object, so values not specified on {@link Template}-level are get from the
+     * {@link Configuration} object.
+     * 
+     * <p>
+     * Note on the parent of {@link Environment}: If you set {@link Configuration#setIncompatibleImprovements(Version)
+     * incompatible_improvements} to at least 2.3.22, it will be always the "main" {@link Template}, that is, the
+     * template for whose processing the {@link Environment} was created. With lower {@code incompatible_improvements},
+     * the current parent can temporary change <em>during template execution</em>, for example when your are inside an
+     * {@code #include}-d template (among others). Thus, don't build on which {@link Template} the parent of
+     * {@link Environment} is during template execution, unless you set {@code incompatible_improvements} to 2.3.22 or
+     * higher.
+     *
+     * @return The parent {@link MutableProcessingConfiguration} object, or {@code null} if this is the root {@link MutableProcessingConfiguration} object
+     *         (i.e, if it's the {@link Configuration} object).
+     */
+    public final MutableProcessingConfiguration getParent() {
+        return parent;
+    }
+    
+    /**
+     * Reparenting support. This is used by Environment when it includes a
+     * template - the included template becomes the parent configurable during
+     * its evaluation.
+     */
+    void setParent(MutableProcessingConfiguration parent) {
+        this.parent = parent;
+    }
+    
+    /**
+     * Sets the default locale used for number and date formatting (among others), also the locale used for searching
+     * localized template variations when no locale was explicitly requested.
+     * 
+     * @see Configuration#getTemplate(String, Locale)
+     */
+    public void setLocale(Locale locale) {
+        _NullArgumentException.check("locale", locale);
+        this.locale = locale;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setLocale(Locale)}
+     */
+    public SelfT locale(Locale value) {
+        setLocale(value);
+        return self();
+    }
+
+    @Override
+    public Locale getLocale() {
+        return locale != null ? locale : parent.getLocale();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isLocaleSet() {
+        return locale != null;
+    }
+    
+    /**
+     * Sets the time zone to use when formatting date/time values.
+     * Defaults to the system time zone ({@link TimeZone#getDefault()}), regardless of the "locale" FreeMarker setting,
+     * so in a server application you probably want to set it explicitly in the {@link Environment} to match the
+     * preferred time zone of target audience (like the Web page visitor).
+     * 
+     * <p>If you or the templates set the time zone, you should probably also set
+     * {@link #setSQLDateAndTimeTimeZone(TimeZone)}!
+     * 
+     * @see #setSQLDateAndTimeTimeZone(TimeZone)
+     */
+    public void setTimeZone(TimeZone timeZone) {
+        _NullArgumentException.check("timeZone", timeZone);
+        this.timeZone = timeZone;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTimeZone(TimeZone)}
+     */
+    public SelfT timeZone(TimeZone value) {
+        setTimeZone(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setTimeZone(TimeZone)}. 
+     */
+    @Override
+    public TimeZone getTimeZone() {
+        return timeZone != null ? timeZone : parent.getTimeZone();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isTimeZoneSet() {
+        return timeZone != null;
+    }
+    
+    /**
+     * Sets the time zone used when dealing with {@link java.sql.Date java.sql.Date} and
+     * {@link java.sql.Time java.sql.Time} values. It defaults to {@code null} for backward compatibility, but in most
+     * application this should be set to the JVM default time zone (server default time zone), because that's what
+     * most JDBC drivers will use when constructing the {@link java.sql.Date java.sql.Date} and
+     * {@link java.sql.Time java.sql.Time} values. If this setting is {@code null}, FreeMarker will use the value of
+     * ({@link #getTimeZone()}) for {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values,
+     * which often gives bad results.
+     * 
+     * <p>This setting doesn't influence the formatting of other kind of values (like of
+     * {@link java.sql.Timestamp java.sql.Timestamp} or plain {@link java.util.Date java.util.Date} values).
+     * 
+     * <p>To decide what value you need, a few things has to be understood:
+     * <ul>
+     *   <li>Date-only and time-only values in SQL-oriented databases are usually store calendar and clock field
+     *   values directly (year, month, day, or hour, minute, seconds (with decimals)), as opposed to a set of points
+     *   on the physical time line. Thus, unlike SQL timestamps, these values usually aren't meant to be shown
+     *   differently depending on the time zone of the audience.
+     *   
+     *   <li>When a JDBC query has to return a date-only or time-only value, it has to convert it to a point on the
+     *   physical time line, because that's what {@link java.util.Date} and its subclasses store (milliseconds since
+     *   the epoch). Obviously, this is impossible to do. So JDBC just chooses a physical time which, when rendered
+     *   <em>with the JVM default time zone</em>, will give the same field values as those stored
+     *   in the database. (Actually, you can give JDBC a calendar, and so it can use other time zones too, but most
+     *   application won't care using those overloads.) For example, assume that the system time zone is GMT+02:00.
+     *   Then, 2014-07-12 in the database will be translated to physical time 2014-07-11 22:00:00 UTC, because that
+     *   rendered in GMT+02:00 gives 2014-07-12 00:00:00. Similarly, 11:57:00 in the database will be translated to
+     *   physical time 1970-01-01 09:57:00 UTC. Thus, the physical time stored in the returned value depends on the
+     *   default system time zone of the JDBC client, not just on the content in the database. (This used to be the
+     *   default behavior of ORM-s, like Hibernate, too.)
+     *   
+     *   <li>The value of the {@code time_zone} FreeMarker configuration setting sets the time zone used for the
+     *   template output. For example, when a web page visitor has a preferred time zone, the web application framework
+     *   may calls {@link Environment#setTimeZone(TimeZone)} with that time zone. Thus, the visitor will
+     *   see {@link java.sql.Timestamp java.sql.Timestamp} and plain {@link java.util.Date java.util.Date} values as
+     *   they look in his own time zone. While
+     *   this is desirable for those types, as they meant to represent physical points on the time line, this is not
+     *   necessarily desirable for date-only and time-only values. When {@code sql_date_and_time_time_zone} is
+     *   {@code null}, {@code time_zone} is used for rendering all kind of date/time/dateTime values, including
+     *   {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time}, and then if, for example,
+     *   {@code time_zone} is GMT+00:00, the
+     *   values from the earlier examples will be shown as 2014-07-11 (one day off) and 09:57:00 (2 hours off). While
+     *   those are the time zone correct renderings, those values are probably meant to be shown "as is".
+     *   
+     *   <li>You may wonder why this setting isn't simply "SQL time zone", since the time zone related behavior of JDBC
+     *   applies to {@link java.sql.Timestamp java.sql.Timestamp} too. FreeMarker assumes that you have set up your
+     *   application so that time stamps coming from the database go through the necessary conversion to store the
+     *   correct distance from the epoch (1970-01-01 00:00:00 UTC), as requested by {@link java.util.Date}. In that case
+     *   the time stamp can be safely rendered in different time zones, and thus it needs no special treatment.
+     * </ul>
+     * 
+     * @param tz Maybe {@code null}, in which case {@link java.sql.Date java.sql.Date} and
+     *          {@link java.sql.Time java.sql.Time} values will be formatted in the time zone returned by
+     *          {@link #getTimeZone()}.
+     *          (Note that since {@code null} is an allowed value for this setting, it will not cause
+     *          {@link #getSQLDateAndTimeTimeZone()} to fall back to the parent configuration.)
+     * 
+     * @see #setTimeZone(TimeZone)
+     * 
+     * @since 2.3.21
+     */
+    public void setSQLDateAndTimeTimeZone(TimeZone tz) {
+        sqlDataAndTimeTimeZone = tz;
+        sqlDataAndTimeTimeZoneSet = true;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setSQLDateAndTimeTimeZone(TimeZone)}
+     */
+    public SelfT sqlDateAndTimeTimeZone(TimeZone value) {
+        setSQLDateAndTimeTimeZone(value);
+        return self();
+    }
+
+
+    /**
+     * The getter pair of {@link #setSQLDateAndTimeTimeZone(TimeZone)}.
+     * 
+     * @return {@code null} if the value of {@link #getTimeZone()} should be used for formatting
+     *     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values, otherwise the time zone
+     *     that should be used to format the values of those two types.  
+     * 
+     * @since 2.3.21
+     */
+    @Override
+    public TimeZone getSQLDateAndTimeTimeZone() {
+        return sqlDataAndTimeTimeZoneSet
+                ? sqlDataAndTimeTimeZone
+                : (parent != null ? parent.getSQLDateAndTimeTimeZone() : null);
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isSQLDateAndTimeTimeZoneSet() {
+        return sqlDataAndTimeTimeZoneSet;
+    }
+
+    /**
+     * Sets the default number format used to convert numbers to strings. Currently, this is one of these:
+     * <ul>
+     *   <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}</li>
+     *   <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
+     *   <li>{@code "percent"}: The number format returned by {@link NumberFormat#getPercentInstance(Locale)}</li>
+     *   <li>{@code "computer"}: The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
+     *   <li>{@link java.text.DecimalFormat} pattern (like {@code "0.##"}). This syntax has a FreeMarker-specific
+     *       extension, so that you can specify options like the rounding mode and the symbols used in this string. For
+     *       example, {@code ",000;; roundingMode=halfUp groupingSeparator=_"} will format numbers like {@code ",000"}
+     *       would, but with half-up rounding mode, and {@code _} as the group separator. See more about "extended Java
+     *       decimal format" in the FreeMarker Manual.
+     *       </li>
+     *   <li>If the string starts with {@code @} character followed by a letter then it's interpreted as a custom number
+     *       format, but only if either {@link Configuration#getIncompatibleImprovements()} is at least 2.3.24, or
+     *       there's any custom formats defined (even if custom date/time/dateTime format). The format of a such string
+     *       is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomNumberFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
+     *   </li>
+     * </ul>
+     * 
+     *   
+     * <p>Defaults to <tt>"number"</tt>.
+     */
+    public void setNumberFormat(String numberFormat) {
+        _NullArgumentException.check("numberFormat", numberFormat);
+        this.numberFormat = numberFormat;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setNumberFormat(String)}
+     */
+    public SelfT numberFormat(String value) {
+        setNumberFormat(value);
+        return self();
+    }
+
+    /**
+     * Getter pair of {@link #setNumberFormat(String)}. 
+     */
+    @Override
+    public String getNumberFormat() {
+        return numberFormat != null ? numberFormat : parent.getNumberFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isNumberFormatSet() {
+        return numberFormat != null;
+    }
+    
+    /**
+     * Getter pair of {@link #setCustomNumberFormats(Map)}; do not modify the returned {@link Map}! To be consistent
+     * with other setting getters, if this setting was set directly on this {@link MutableProcessingConfiguration} object, this simply
+     * returns that value, otherwise it returns the value from the parent {@link MutableProcessingConfiguration}. So beware, the returned
+     * value doesn't reflect the {@link Map} key granularity fallback logic that FreeMarker actually uses for this
+     * setting (for that, use {@link #getCustomNumberFormat(String)}). The returned value isn't a snapshot; it may or
+     * may not shows the changes later made to this setting on this {@link MutableProcessingConfiguration} level (but usually it's well
+     * defined if until what point settings are possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
+     * {@link Map}.
+     *
+     * @since 2.3.24
+     */
+    @Override
+    public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats() {
+        return customNumberFormats == null ? parent.getCustomNumberFormats() : customNumberFormats;
+    }
+
+    /**
+     * Associates names with formatter factories, which then can be referred by the {@link #setNumberFormat(String)
+     * number_format} setting with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
+     * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
+     * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
+     * {@code @} always has special meaning).
+     * 
+     * @param customNumberFormats
+     *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
+     *            letters and digits (not {@code _}).
+     * 
+     * @since 2.3.24
+     */
+    public void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats) {
+        _NullArgumentException.check("customNumberFormats", customNumberFormats);
+        validateFormatNames(customNumberFormats.keySet());
+        this.customNumberFormats = customNumberFormats;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setCustomNumberFormats(Map)}
+     */
+    public SelfT customNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> value) {
+        setCustomNumberFormats(value);
+        return self();
+    }
+
+    private void validateFormatNames(Set<String> keySet) {
+        for (String name : keySet) {
+            if (name.length() == 0) {
+                throw new IllegalArgumentException("Format names can't be 0 length");
+            }
+            char firstChar = name.charAt(0);
+            if (firstChar == '@') {
+                throw new IllegalArgumentException(
+                        "Format names can't start with '@'. '@' is only used when referring to them from format "
+                        + "strings. In: " + name);
+            }
+            if (!Character.isLetter(firstChar)) {
+                throw new IllegalArgumentException("Format name must start with letter: " + name);
+            }
+            for (int i = 1; i < name.length(); i++) {
+                // Note that we deliberately don't allow "_" here.
+                if (!Character.isLetterOrDigit(name.charAt(i))) {
+                    throw new IllegalArgumentException("Format name can only contain letters and digits: " + name);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isCustomNumberFormatsSet() {
+        return customNumberFormats != null;
+    }
+
+    /**
+     * Gets the custom name format registered for the name.
+     * 
+     * @since 2.3.24
+     */
+    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+        TemplateNumberFormatFactory r;
+        if (customNumberFormats != null) {
+            r = customNumberFormats.get(name);
+            if (r != null) {
+                return r;
+            }
+        }
+        return parent != null ? parent.getCustomNumberFormat(name) : null;
+    }
+    
+    /**
+     * Tells if this configurable object or its parent defines any custom formats.
+     * 
+     * @since 2.3.24
+     */
+    public boolean hasCustomFormats() {
+        return customNumberFormats != null && !customNumberFormats.isEmpty()
+                || customDateFormats != null && !customDateFormats.isEmpty()
+                || getParent() != null && getParent().hasCustomFormats(); 
+    }
+    
+    /**
+     * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
+     * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
+     * so {@code "yes, no"} is WRONG (unless you want that leading space before "no").
+     * 
+     * <p>For backward compatibility the default is {@code "true,false"}, but using that value is denied for automatic
+     * boolean-to-string conversion (like <code>${myBoolean}</code> will fail with it), only {@code myBool?string} will
+     * allow it, which is deprecated since FreeMarker 2.3.20.
+     * 
+     * <p>Note that automatic boolean-to-string conversion only exists since FreeMarker 2.3.20. Earlier this setting
+     * only influenced the result of {@code myBool?string}. 
+     */
+    public void setBooleanFormat(String booleanFormat) {
+        _NullArgumentException.check("booleanFormat", booleanFormat);
+        
+        int commaIdx = booleanFormat.indexOf(',');
+        if (commaIdx == -1) {
+            throw new IllegalArgumentException(
+                    "Setting value must be string that contains two comma-separated values for true and false, " +
+                    "respectively.");
+        }
+        
+        this.booleanFormat = booleanFormat; 
+        
+        if (booleanFormat.equals(C_TRUE_FALSE)) {
+            // C_TRUE_FALSE is the default for BC, but it's not a good default for human audience formatting, so we
+            // pretend that it wasn't set.
+            trueStringValue = null; 
+            falseStringValue = null;
+        } else {
+            trueStringValue = booleanFormat.substring(0, commaIdx); 
+            falseStringValue = booleanFormat.substring(commaIdx + 1);
+        }
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setBooleanFormat(String)}
+     */
+    public SelfT booleanFormat(String value) {
+        setBooleanFormat(value);
+        return self();
+    }
+    
+    /**
+     * The getter pair of {@link #setBooleanFormat(String)}.
+     */
+    @Override
+    public String getBooleanFormat() {
+        return booleanFormat != null ? booleanFormat : parent.getBooleanFormat(); 
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isBooleanFormatSet() {
+        return booleanFormat != null;
+    }
+        
+    String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
+        if (value) {
+            String s = getTrueStringValue();
+            if (s == null) {
+                if (fallbackToTrueFalse) {
+                    return MiscUtil.C_TRUE;
+                } else {
+                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
+                }
+            } else {
+                return s;
+            }
+        } else {
+            String s = getFalseStringValue();
+            if (s == null) {
+                if (fallbackToTrueFalse) {
+                    return MiscUtil.C_FALSE;
+                } else {
+                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
+                }
+            } else {
+                return s;
+            }
+        }
+    }
+
+    private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
+        return new _ErrorDescriptionBuilder(
+                "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
+                new _DelayedJQuote(getBooleanFormat()), 
+                (getBooleanFormat().equals(C_TRUE_FALSE)
+                    ? ", which is the legacy default computer-language format, and hence isn't accepted."
+                    : ".")
+                ).tips(
+                     "If you just want \"true\"/\"false\" result as you are generting computer-language output, "
+                     + "use \"?c\", like ${myBool?c}.",
+                     "You can write myBool?string('yes', 'no') and like to specify boolean formatting in place.",
+                     new Object[] {
+                         "If you need the same two values on most places, the programmers should set the \"",
+                         BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\"." }
+                 );
+    }
+
+    /**
+     * Returns the string to which {@code true} is converted to for human audience, or {@code null} if automatic
+     * coercion to string is not allowed. The default value is {@code null}.
+     * 
+     * <p>This value is deduced from the {@code "boolean_format"} setting.
+     * Confusingly, for backward compatibility (at least until 2.4) that defaults to {@code "true,false"}, yet this
+     * defaults to {@code null}. That's so because {@code "true,false"} is treated exceptionally, as that default is a
+     * historical mistake in FreeMarker, since it targets computer language output, not human writing. Thus it's
+     * ignored.
+     * 
+     * @since 2.3.20
+     */
+    String getTrueStringValue() {
+        // The first step deliberately tests booleanFormat instead of trueStringValue! 
+        return booleanFormat != null ? trueStringValue : (parent != null ? parent.getTrueStringValue() : null); 
+    }
+
+    /**
+     * Same as {@link #getTrueStringValue()} but with {@code false}. 
+     * @since 2.3.20
+     */
+    String getFalseStringValue() {
+        // The first step deliberately tests booleanFormat instead of falseStringValue! 
+        return booleanFormat != null ? falseStringValue : (parent != null ? parent.getFalseStringValue() : null); 
+    }
+
+    /**
+     * Sets the format used to convert {@link java.util.Date}-s to string-s that are time (no date part) values,
+     * also the format that {@code someString?time} will use to parse strings.
+     * 
+     * <p>For the possible values see {@link #setDateTimeFormat(String)}.
+     *   
+     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", which is currently {@code "medium"}.
+     */
+    public void setTimeFormat(String timeFormat) {
+        _NullArgumentException.check("timeFormat", timeFormat);
+        this.timeFormat = timeFormat;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTimeFormat(String)}
+     */
+    public SelfT timeFormat(String value) {
+        setTimeFormat(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setTimeFormat(String)}.
+     */
+    @Override
+    public String getTimeFormat() {
+        return timeFormat != null ? timeFormat : parent.getTimeFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isTimeFormatSet() {
+        return timeFormat != null;
+    }
+    
+    /**
+     * Sets the format used to convert {@link java.util.Date}-s to string-s that are date (no time part) values,
+     * also the format that {@code someString?date} will use to parse strings.
+     * 
+     * <p>For the possible values see {@link #setDateTimeFormat(String)}.
+     *   
+     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", which is currently {@code "medium"}.
+     */
+    public void setDateFormat(String dateFormat) {
+        _NullArgumentException.check("dateFormat", dateFormat);
+        this.dateFormat = dateFormat;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setDateFormat(String)}
+     */
+    public SelfT dateFormat(String value) {
+        setDateFormat(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setDateFormat(String)}.
+     */
+    @Override
+    public String getDateFormat() {
+        return dateFormat != null ? dateFormat : parent.getDateFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isDateFormatSet() {
+        return dateFormat != null;
+    }
+    
+    /**
+     * Sets the format used to convert {@link java.util.Date}-s to string-s that are date-time (timestamp) values,
+     * also the format that {@code someString?datetime} will use to parse strings.
+     * 
+     * <p>The possible setting values are (the quotation marks aren't part of the value itself):
+     * 
+     * <ul>
+     *   <li><p>Patterns accepted by Java's {@link SimpleDateFormat}, for example {@code "dd.MM.yyyy HH:mm:ss"} (where
+     *       {@code HH} means 24 hours format) or {@code "MM/dd/yyyy hh:mm:ss a"} (where {@code a} prints AM or PM, if
+     *       the current language is English).
+     *   
+     *   <li><p>{@code "xs"} for XML Schema format, or {@code "iso"} for ISO 8601:2004 format.
+     *       These formats allow various additional options, separated with space, like in
+     *       {@code "iso m nz"} (or with {@code _}, like in {@code "iso_m_nz"}; this is useful in a case like
+     *       {@code lastModified?string.iso_m_nz}). The options and their meanings are:
+     *       
+     *       <ul>
+     *         <li><p>Accuracy options:<br>
+     *             {@code ms} = Milliseconds, always shown with all 3 digits, even if it's all 0-s.
+     *                     Example: {@code 13:45:05.800}<br>
+     *             {@code s} = Seconds (fraction seconds are dropped even if non-0), like {@code 13:45:05}<br>
+     *             {@code m} = Minutes, like {@code 13:45}. This isn't allowed for "xs".<br>
+     *             {@code h} = Hours, like {@code 13}. This isn't allowed for "xs".<br>
+     *             Neither = Up to millisecond accuracy, but trailing millisecond 0-s are removed, also the whole
+     *                     milliseconds part if it would be 0 otherwise. Example: {@code 13:45:05.8}
+     *                     
+     *         <li><p>Time zone offset visibility options:<br>
+     *             {@code fz} = "Force Zone", always show time zone offset (even for for
+     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values).
+     *                     But, because ISO 8601 doesn't allow for dates (means date without time of the day) to
+     *                     show the zone offset, this option will have no effect in the case of {@code "iso"} with
+     *                     dates.<br>
+     *             {@code nz} = "No Zone", never show time zone offset<br>
+     *             Neither = always show time zone offset, except for {@link java.sql.Date java.sql.Date}
+     *                     and {@link java.sql.Time java.sql.Time}, and for {@code "iso"} date values.
+     *                     
+     *         <li><p>Time zone options:<br>
+     *             {@code u} = Use UTC instead of what the {@code time_zone} setting suggests. However,
+     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} aren't affected
+     *                     by this (see {@link #setSQLDateAndTimeTimeZone(TimeZone)} to understand why)<br>
+     *             {@code fu} = "Force UTC", that is, use UTC instead of what the {@code time_zone} or the
+     *                     {@code sql_date_and_time_time_zone} setting suggests. This also effects
+     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values<br>
+     *             Neither = Use the time zone suggested by the {@code time_zone} or the
+     *                     {@code sql_date_and_time_time_zone} configuration setting ({@link #setTimeZone(TimeZone)} and
+     *                     {@link #setSQLDateAndTimeTimeZone(TimeZone)}).
+     *       </ul>
+     *       
+     *       <p>The options can be specified in any order.</p>
+     *       
+     *       <p>Options from the same category are mutually exclusive, like using {@code m} and {@code s}
+     *       together is an error.
+     *       
+     *       <p>The accuracy and time zone offset visibility options don't influence parsing, only formatting.
+     *       For example, even if you use "iso m nz", "2012-01-01T15:30:05.125+01" will be parsed successfully and with
+     *       milliseconds accuracy.
+     *       The time zone options (like "u") influence what time zone is chosen only when parsing a string that doesn't
+     *       contain time zone offset.
+     *       
+     *       <p>Parsing with {@code "iso"} understands both extend format and basic format, like
+     *       {@code 20141225T235018}. It doesn't, however, support the parsing of all kind of ISO 8601 strings: if
+     *       there's a date part, it must use year, month and day of the month values (not week of the year), and the
+     *       day can't be omitted.
+     *       
+     *       <p>The output of {@code "iso"} is deliberately so that it's also a good representation of the value with
+     *       XML Schema format, except for 0 and negative years, where it's impossible. Also note that the time zone
+     *       offset is omitted for date values in the {@code "iso"} format, while it's preserved for the {@code "xs"}
+     *       format.
+     *       
+     *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code "full"}, which that has locale-dependent
+     *       meaning defined by the Java platform (see in the documentation of {@link java.text.DateFormat}).
+     *       For date-time values, you can specify the length of the date and time part independently, be separating
+     *       them with {@code _}, like {@code "short_medium"}. ({@code "medium"} means
+     *       {@code "medium_medium"} for date-time values.)
+     *       
+     *   <li><p>Anything that starts with {@code "@"} followed by a letter is interpreted as a custom
+     *       date/time/dateTime format, but only if either {@link Configuration#getIncompatibleImprovements()}
+     *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
+     *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomDateFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom number format.
+     *       
+     * </ul> 
+     * 
+     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", which is currently {@code "medium_medium"}.
+     */
+    public void setDateTimeFormat(String dateTimeFormat) {
+        _NullArgumentException.check("dateTimeFormat", dateTimeFormat);
+        this.dateTimeFormat = dateTimeFormat;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setDateTimeFormat(String)}
+     */
+    public SelfT dateTimeFormat(String value) {
+        setDateTimeFormat(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setDateTimeFormat(String)}.
+     */
+    @Override
+    public String getDateTimeFormat() {
+        return dateTimeFormat != null ? dateTimeFormat : parent.getDateTimeFormat();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isDateTimeFormatSet() {
+        return dateTimeFormat != null;
+    }
+    
+    /**
+     * Getter pair of {@link #setCustomDateFormats(Map)}; do not modify the returned {@link Map}! To be consistent with
+     * other setting getters, if this setting was set directly on this {@link MutableProcessingConfiguration} object, this simply returns
+     * that value, otherwise it returns the value from the parent {@link MutableProcessingConfiguration}. So beware, the returned value
+     * doesn't reflect the {@link Map} key granularity fallback logic that FreeMarker actually uses for this setting
+     * (for that, use {@link #getCustomDateFormat(String)}). The returned value isn't a snapshot; it may or may not
+     * shows the changes later made to this setting on this {@link MutableProcessingConfiguration} level (but usually it's well defined if
+     * until what point settings are possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
+     * {@link Map}.
+     * 
+     * @since 2.3.24
+     */
+    @Override
+    public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats() {
+        return customDateFormats == null ? parent.getCustomDateFormats() : customDateFormats;
+    }
+
+    /**
+     * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
+     * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
+     * datetime_format} settings with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
+     * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
+     * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
+     * {@code @} always has special meaning).
+     *
+     * @param customDateFormats
+     *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
+     *            letters and digits.
+     * 
+     * @since 2.3.24
+     */
+    public void setCustomDateFormats(Map<String, ? extends TemplateDateFormatFactory> customDateFormats) {
+        _NullArgumentException.check("customDateFormats", customDateFormats);
+        validateFormatNames(customDateFormats.keySet());
+        this.customDateFormats = customDateFormats;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setCustomDateFormats(Map)}
+     */
+    public SelfT customDateFormats(Map<String, ? extends TemplateDateFormatFactory> value) {
+        setCustomDateFormats(value);
+        return self();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isCustomDateFormatsSet() {
+        return customDateFormats != null;
+    }
+
+    /**
+     * Gets the custom name format registered for the name.
+     * 
+     * @since 2.3.24
+     */
+    public TemplateDateFormatFactory getCustomDateFormat(String name) {
+        TemplateDateFormatFactory r;
+        if (customDateFormats != null) {
+            r = customDateFormats.get(name);
+            if (r != null) {
+                return r;
+            }
+        }
+        return parent != null ? parent.getCustomDateFormat(name) : null;
+    }
+    
+    /**
+     * Sets the exception handler used to handle exceptions occurring inside templates.
+     * The default is {@link TemplateExceptionHandler#DEBUG_HANDLER}. The recommended values are:
+     * 
+     * <ul>
+     *   <li>In production systems: {@link TemplateExceptionHandler#RETHROW_HANDLER}
+     *   <li>During development of HTML templates: {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}
+     *   <li>During development of non-HTML templates: {@link TemplateExceptionHandler#DEBUG_HANDLER}
+     * </ul>
+     * 
+     * <p>All of these will let the exception propagate further, so that you can catch it around
+     * {@link Template#process(Object, Writer)} for example. The difference is in what they print on the output before
+     * they do that.
+     * 
+     * <p>Note that the {@link TemplateExceptionHandler} is not meant to be used for generating HTTP error pages.
+     * Neither is it meant to be used to roll back the printed output. These should be solved outside template
+     * processing when the exception raises from {@link Template#process(Object, Writer) Template.process}.
+     * {@link TemplateExceptionHandler} meant to be used if you want to include special content <em>in</em> the template
+     * output, or if you want to suppress certain exceptions. 
+     */
+    public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
+        _NullArgumentException.check("templateExceptionHandler", templateExceptionHandler);
+        this.templateExceptionHandler = templateExceptionHandler;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}
+     */
+    public SelfT templateExceptionHandler(TemplateExceptionHandler value) {
+        setTemplateExceptionHandler(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}.
+     */
+    @Override
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        return templateExceptionHandler != null
+                ? templateExceptionHandler : parent.getTemplateExceptionHandler();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isTemplateExceptionHandlerSet() {
+        return templateExceptionHandler != null;
+    }
+
+    /**
+     * Sets the arithmetic engine used to perform arithmetic operations.
+     * The default is {@link BigDecimalArithmeticEngine#INSTANCE}.
+     */
+    public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
+        _NullArgumentException.check("arithmeticEngine", arithmeticEngine);
+        this.arithmeticEngine = arithmeticEngine;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setArithmeticEngine(ArithmeticEngine)}
+     */
+    public SelfT arithmeticEngine(ArithmeticEngine value) {
+        setArithmeticEngine(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setArithmeticEngine(ArithmeticEngine)}.
+     */
+    @Override
+    public ArithmeticEngine getArithmeticEngine() {
+        return arithmeticEngine != null
+                ? arithmeticEngine : parent.getArithmeticEngine();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isArithmeticEngineSet() {
+        return arithmeticEngine != null;
+    }
+
+    /**
+     * Sets the object wrapper used to wrap objects to {@link TemplateModel}-s.
+     * The default is {@link DefaultObjectWrapper.Builder#build()}.
+     */
+    public void setObjectWrapper(ObjectWrapper objectWrapper) {
+        _NullArgumentException.check("objectWrapper", objectWrapper);
+        this.objectWrapper = objectWrapper;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setObjectWrapper(ObjectWrapper)}
+     */
+    public SelfT objectWrapper(ObjectWrapper value) {
+        setObjectWrapper(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setObjectWrapper(ObjectWrapper)}.
+     */
+    @Override
+    public ObjectWrapper getObjectWrapper() {
+        return objectWrapper != null
+                ? objectWrapper : parent.getObjectWrapper();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isObjectWrapperSet() {
+        return objectWrapper != null;
+    }
+    
+    /**
+     * Informs FreeMarker about the charset used for the output. As FreeMarker outputs character stream (not
+     * byte stream), it's not aware of the output charset unless the software that encloses it tells it
+     * with this setting. Some templates may use FreeMarker features that require this information.
+     * Setting this to {@code null} means that the output encoding is not known.
+     * 
+     * <p>Defaults to {@code null} (unknown).
+     */
+    public void setOutputEncoding(String outputEncoding) {
+        this.outputEncoding = outputEncoding;
+        outputEncodingSet = true;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setOutputEncoding(String)}
+     */
+    public SelfT outputEncoding(String value) {
+        setOutputEncoding(value);
+        return self();
+    }
+
+    @Override
+    public String getOutputEncoding() {
+        return outputEncodingSet
+                ? outputEncoding
+                : (parent != null ? parent.getOutputEncoding() : null);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isOutputEncodingSet() {
+        return outputEncodingSet;
+    }
+    
+    /**
+     * Sets the URL escaping charset. If not set ({@code null}), the output encoding
+     * ({@link #setOutputEncoding(String)}) will be used for URL escaping.
+     * 
+     * Defaults to {@code null}.
+     */
+    public void setURLEscapingCharset(String urlEscapingCharset) {
+        this.urlEscapingCharset = urlEscapingCharset;
+        urlEscapingCharsetSet = true;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setURLEscapingCharset(String)}
+     */
+    public SelfT urlEscapingCharset(String value) {
+        setURLEscapingCharset(value);
+        return self();
+    }
+
+    @Override
+    public String getURLEscapingCharset() {
+        return urlEscapingCharsetSet
+                ? urlEscapingCharset
+                : (parent != null ? parent.getURLEscapingCharset() : null);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isURLEscapingCharsetSet() {
+        return urlEscapingCharsetSet;
+    }
+
+    /**
+     * Sets the {@link TemplateClassResolver} that is used when the
+     * <code>new</code> built-in is called in a template. That is, when
+     * a template contains the <code>"com.example.SomeClassName"?new</code>
+     * expression, this object will be called to resolve the
+     * <code>"com.example.SomeClassName"</code> string to a class. The default
+     * value is {@link TemplateClassResolver#UNRESTRICTED_RESOLVER}. If you allow
+     * users to upload templates, it's important to use a custom restrictive
+     * {@link TemplateClassResolver} or {@link TemplateClassResolver#ALLOWS_NOTHING_RESOLVER}.
+     * 
+     * @since 2.3.17
+     */
+    public void setNewBuiltinClassResolver(TemplateClassResolver newBuiltinClassResolver) {
+        _NullArgumentException.check("newBuiltinClassResolver", newBuiltinClassResolver);
+        this.newBuiltinClassResolver = newBuiltinClassResolver;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setNewBuiltinClassResolver(TemplateClassResolver)}
+     */
+    public SelfT newBuiltinClassResolver(TemplateClassResolver value) {
+        setNewBuiltinClassResolver(value);
+        return self();
+    }
+
+    /**
+     * Retrieves the {@link TemplateClassResolver} used
+     * to resolve classes when "SomeClassName"?new is called in a template.
+     * 
+     * @see #setNewBuiltinClassResolver(TemplateClassResolver)
+     * 
+     * @since 2.3.17
+     */
+    @Override
+    public TemplateClassResolver getNewBuiltinClassResolver() {
+        return newBuiltinClassResolver != null
+                ? newBuiltinClassResolver : parent.getNewBuiltinClassResolver();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isNewBuiltinClassResolverSet() {
+        return newBuiltinClassResolver != null;
+    }
+    
+    /**
+     * Sets whether the output {@link Writer} is automatically flushed at
+     * the end of {@link Template#process(Object, Writer)} (and its
+     * overloads). The default is {@code true}.
+     * 
+     * <p>Using {@code false} is needed for example when a Web page is composed
+     * from several boxes (like portlets, GUI panels, etc.) that aren't inserted
+     * with <tt>#include</tt> (or with similar directives) into a master
+     * FreeMarker template, rather they are all processed with a separate
+     * {@link Template#process(Object, Writer)} call. In a such scenario the
+     * automatic flushes would commit the HTTP response after each box, hence
+     * interfering with full-page buffering, and also possibly decreasing
+     * performance with too frequent and too early response buffer flushes.
+     * 
+     * @since 2.3.17
+     */
+    public void setAutoFlush(boolean autoFlush) {
+        this.autoFlush = Boolean.valueOf(autoFlush);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setAutoFlush(boolean)}
+     */
+    public SelfT autoFlush(boolean value) {
+        setAutoFlush(value);
+        return self();
+    }
+
+    /**
+     * See {@link #setAutoFlush(boolean)}
+     * 
+     * @since 2.3.17
+     */
+    @Override
+    public boolean getAutoFlush() {
+        return autoFlush != null 
+            ? autoFlush.booleanValue()
+            : (parent != null ? parent.getAutoFlush() : true);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isAutoFlushSet() {
+        return autoFlush != null;
+    }
+    
+    /**
+     * Sets if tips should be shown in error messages of errors arising during template processing.
+     * The default is {@code true}. 
+     * 
+     * @since 2.3.21
+     */
+    public void setShowErrorTips(boolean showTips) {
+        showErrorTips = Boolean.valueOf(showTips);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setShowErrorTips(boolean)}
+     */
+    public SelfT showErrorTips(boolean value) {
+        setShowErrorTips(value);
+        return self();
+    }
+
+    /**
+     * See {@link #setShowErrorTips(boolean)}
+     * 
+     * @since 2.3.21
+     */
+    @Override
+    public boolean getShowErrorTips() {
+        return showErrorTips != null 
+            ? showErrorTips.booleanValue()
+            : (parent != null ? parent.getShowErrorTips() : true);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isShowErrorTipsSet() {
+        return showErrorTips != null;
+    }
+    
+    /**
+     * Specifies if {@code ?api} can be used in templates. Defaults to {@code false} so that updating FreeMarker won't
+     * decrease the security of existing applications.
+     * 
+     * @since 2.3.22
+     */
+    public void setAPIBuiltinEnabled(boolean value) {
+        apiBuiltinEnabled = Boolean.valueOf(value);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setAPIBuiltinEnabled(boolean)}
+     */
+    public SelfT apiBuiltinEnabled(boolean value) {
+        setAPIBuiltinEnabled(value);
+        return self();
+    }
+
+    /**
+     * See {@link #setAPIBuiltinEnabled(boolean)}
+     * 
+     * @since 2.3.22
+     */
+    public boolean isAPIBuiltinEnabled() {
+        return apiBuiltinEnabled != null 
+                ? apiBuiltinEnabled.booleanValue()
+                : (parent != null ? parent.isAPIBuiltinEnabled() : false);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isAPIBuiltinEnabledSet() {
+        return apiBuiltinEnabled != null;
+    }
+    
+    /**
+     * Specifies if {@link TemplateException}-s thrown by template processing are logged by FreeMarker or not. The
+     * default is {@code true} for backward compatibility, but that results in logging the exception twice in properly
+     * written applications, because there the {@link TemplateException} thrown by the public FreeMarker API is also
+     * logged by the caller (even if only as the cause exception of a higher level exception). Hence, in modern
+     * applications it should be set to {@code false}. Note that this setting has no effect on the logging of exceptions
+     * caught by {@code #attempt}; those are always logged, no mater what (because those exceptions won't bubble up
+     * until the API caller).
+     * 
+     * @since 2.3.22
+     */
+    public void setLogTemplateExceptions(boolean value) {
+        logTemplateExceptions = Boolean.valueOf(value);
+    }
+
+    /**
+     * See {@link #setLogTemplateExceptions(boolean)}
+     * 
+     * @since 2.3.22
+     */
+    @Override
+    public boolean getLogTemplateExceptions() {
+        return logTemplateExceptions != null 
+                ? logTemplateExceptions.booleanValue()
+                : (parent != null ? parent.getLogTemplateExceptions() : true);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    @Override
+    public boolean isLogTemplateExceptionsSet() {
+        return logTemplateExceptions != null;
+    }
+    
+    /**
+     * The getter pair of {@link #setLazyImports(boolean)}.
+     * 
+     * @since 2.3.25
+     */
+    @Override
+    public boolean getLazyImports() {
+        return lazyImports != null ? lazyImports.booleanValue() : parent.getLazyImports();
+    }
+    
+    /**
+     * Specifies if {@code <#import ...>} (and {@link Environment#importLib(String, String)}) should delay the loading
+     * and processing of the imported templates until the content of the imported namespace is actually accessed. This
+     * makes the overhead of <em>unused</em> imports negligible. A drawback is that importing a missing or otherwise
+     * broken template will be successful, and the problem will remain hidden until (and if) the namespace content is
+     * actually used. Also, you lose the strict control over when the namespace initializing code in the imported
+     * template will be executed, though it shouldn't mater for well written imported templates anyway. Note that the
+     * namespace initializing code will run with the same {@linkplain MutableProcessingConfiguration#getLocale() locale} as it was at the
+     * point of the {@code <#import ...>} call (other settings won't be handled specially like that).
+     * 
+     * <p>
+     * The default is {@code false} (and thus imports are eager) for backward compatibility, which can cause
+     * perceivable overhead if you have many imports and only a few of them is used.
+     * 
+     * <p>
+     * This setting also affects {@linkplain #setAutoImports(Map) auto-imports}, unless you have set a non-{@code null}
+     * value with {@link #setLazyAutoImports(Boolean)}.
+     * 
+     * @see #setLazyAutoImports(Boolean)
+     * 
+     * @since 2.3.25
+     */
+    public void setLazyImports(boolean lazyImports) {
+        this.lazyImports = Boolean.valueOf(lazyImports);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.25
+     */
+    @Override
+    public boolean isLazyImportsSet() {
+        return lazyImports != null;
+    }
+    
+    /**
+     * The getter pair of {@link #setLazyAutoImports(Boolean)}.
+     * 
+     * @since 2.3.25
+     */
+    @Override
+    public Boolean getLazyAutoImports() {
+        return lazyAutoImportsSet ? lazyAutoImports : parent.getLazyAutoImports();
+    }
+
+    /**
+     * Specifies if {@linkplain #setAutoImports(Map) auto-imports} will be
+     * {@link #setLazyImports(boolean) lazy imports}. This is useful to make the overhead of <em>unused</em>
+     * auto-imports negligible. If this is set to {@code null}, {@link #getLazyImports()} specifies the behavior of
+     * auto-imports too. The default value is {@code null}.
+     * 
+     * @since 2.3.25
+     */
+    public void setLazyAutoImports(Boolean lazyAutoImports) {
+        this.lazyAutoImports = lazyAutoImports;
+        lazyAutoImportsSet = true;
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.25
+     */
+    @Override
+    public boolean isLazyAutoImportsSet() {
+        return lazyAutoImportsSet;
+    }
+    
+    /**
+     * Adds an invisible <code>#import <i>templateName</i> as <i>namespaceVarName</i></code> at the beginning of the
+     * main template (that's the top-level template that wasn't included/imported from another template). While it only
+     * affects the main template directly, as the imports will invoke a global variable there, the imports will be
+     * visible from the further imported templates too (note that {@link Configuration#getIncompatibleImprovements()}
+     * set to 2.3.24 fixes a rarely surfacing bug with that).
+     * 
+     * <p>
+     * It's recommended to set the {@code auto_impots_lazy} 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.
+     * 
+     * <p>
+     * If the imports aren't lazy, the order of the imports will be the same as the order in which they were added with
+     * this method. (Calling this method with an already added {@code namespaceVarName} will move that to the end
+     * of the auto-import order.)
+     * 
+     * <p>
+     * The auto-import is added directly to the {@link MutableProcessingConfiguration} on which this method is called (not to the parents
+     * or children), but when the main template is processed, the auto-imports are collected from all the
+     * {@link MutableProcessingConfiguration} levels, in parent-to-child order: {@link Configuration}, {@link Template} (the main
+     * template), {@link Environment}. If the same {@code namespaceVarName} occurs on multiple levels, the one on the
+     * child level is used, and the clashing import from the parent level is skipped.
+     * 
+     * <p>If there are also auto-includes (see {@link #addAutoInclude(String)}), those will be executed after
+     * the auto-imports.
+     * 
+     * @see #setAutoImports(Map)
+     */
+    public void addAutoImport(String namespaceVarName, String templateName) {
+        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
+        synchronized (this) {
+            if (autoImports == null) {
+                initAutoImportsMap();
+            } else {
+                // This was a List earlier, so re-inserted items must go to the end, hence we remove() before put().
+                autoImports.remove(namespaceVarName);
+            }
+            autoImports.put(namespaceVarName, templateName);
+        }
+    }
+
+    private void initAutoImportsMap() {
+        autoImports = new LinkedHashMap<>(4);
+    }
+    
+    /**
+     * Removes an auto-import from this {@link MutableProcessingConfiguration} level (not from the parents or children);
+     * see {@link #addAutoImport(String, String)}. Does nothing if the auto-import doesn't exist.
+     */
+    public void removeAutoImport(String namespaceVarName) {
+        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
+        synchronized (this) {
+            if (autoImports != null) {
+                autoImports.remove(namespaceVarName);
+            }
+        }
+    }
+    
+    /**
+     * Removes all auto-imports, then calls {@link #addAutoImport(String, String)} for each {@link Map}-entry (the entry
+     * key is the {@code namespaceVarName}). The order of the auto-imports will be the same as {@link Map#keySet()}
+     * returns the keys (but the order of imports doesn't mater for properly designed libraries anyway).
+     * 
+     * @param map
+     *            Maps the namespace variable names to the template names; not {@code null}
+     */
+    public void setAutoImports(Map map) {
+        _NullArgumentException.check("map", map);
+        
+        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
+        synchronized (this) {
+            if (autoImports != null) {
+                autoImports.clear();
+            }
+            for (Map.Entry<?, ?> entry : ((Map<?, ?>) map).entrySet()) {
+                Object key = entry.getKey();
+                if (!(key instanceof String)) {
+                    throw new IllegalArgumentException(
+                            "Key in Map wasn't a String, but a(n) " + key.getClass().getName() + ".");
+                }
+                
+                Object value = entry.getValue();
+                if (!(value instanceof String)) {
+                    throw new IllegalArgumentException(
+                            "Value in Map wasn't a String, but a(n) " + value.getClass().getName() + ".");
+                }
+                
+                addAutoImport((String) key, (String) value);
+            }
+        }
+    }
+    
+    /**
+     * Getter pair of {@link #setAutoImports(Map)}; do not modify the returned {@link Map}! To be consistent with other
+     * setting getters, if this setting was set directly on this {@link MutableProcessingConfiguration} object, this simply returns that
+     * value, otherwise it returns the value from the parent {@link MutableProcessingConfiguration}. So beware, the returned value doesn't
+     * reflect the {@link Map} key granularity fallback logic that FreeMarker actually uses for this setting. The
+     * returned value is not the same {@link Map} object that was set with {@link #setAutoImports(Map)}, only its
+     * content is the same. The returned value isn't a snapshot; it may or may not shows the changes later made to this
+     * setting on this {@link MutableProcessingConfiguration} level (but usually it's well defined if until what point settings are
+     * possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
+     * {@link Map}.
+     *
+     * @since 2.3.25
+     */
+    @Override
+    public Map<String, String> getAutoImports() {
+        return autoImports != null ? autoImports : parent.getAutoImports();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.25
+     */
+    @Override
+    public boolean isAutoImportsSet() {
+        return autoImports != null;
+    }
+
+    /**
+     * Adds an invisible <code>#include <i>templateName</i></code> at the beginning of the main template (that's the
+     * top-level template that wasn't included/imported from another template).
+     * 
+     * <p>
+     * The order of the inclusions will be the same as the order in which they were added with this method.
+     * 
+     * <p>
+     * The auto-include is added directly to the {@link MutableProcessingConfiguration} on which this method is called (not to the parents
+     * or children), but when the main template is processed, the auto-includes are collected from all the
+     * {@link MutableProcessingConfiguration} levels, in parent-to-child order: {@link Configuration}, {@link Template} (the main
+     * template), {@link Environment}.
+     * 
+     * <p>
+     * If there are also auto-imports ({@link #addAutoImport(String, String)}), those imports will be executed before
+     * the auto-includes, hence the namespace variables are accessible for the auto-included templates.
+     * 
+     * <p>
+     * Calling {@link #addAutoInclude(String)} with an already added template name will just move that to the end of the
+     * auto-include list (within the same {@link MutableProcessingConfiguration} level). This works even if the same template name appears
+     * on different {@link MutableProcessingConfiguration} levels, in which case only the inclusion on the lowest (child) level will be
+     * executed.
+     * 
+     * @see #setAutoIncludes(List)
+     */
+    public void addAutoInclude(String templateName) {
+        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
+        synchronized (this) {
+            if (autoIncludes == null) {
+                initAutoIncludesList();
+            } else {
+                autoIncludes.remove(templateName);
+            }
+            autoIncludes.add(templateName);
+        }
+    }
+
+    private void initAutoIncludesList() {
+        autoIncludes = new ArrayList<>(4);
+    }
+    
+    /**
+     * Removes all auto-includes, then calls {@link #addAutoInclude(String)} for each {@link List} items.
+     * 
+     * <p>Before {@linkplain Configuration#Configuration(Version) incompatible improvements} 2.3.25 it doesn't filter
+     * out duplicates from the list if this method was called on a {@link Configuration} instance.
+     */
+    public void setAutoIncludes(List templateNames) {
+        _NullArgumentException.check("templateNames", templateNames);
+        

<TRUNCATED>


[2/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
index d79b545..7b4b887 100644
--- a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
@@ -18,14 +18,18 @@
  */
 package org.apache.freemarker.core;
 
+import java.io.Writer;
+
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 
 /**
  * <b>Don't implement this interface yourself</b>; use the existing implementation(s). This interface is implemented by
- * classes that hold settings that affect parsing. New parser settings can be added in new FreeMarker versions, which
- * will break your implementation.
- * 
+ * classes that hold settings that affect template parsing (as opposed to {@linkplain Template#process(Object, Writer)
+ * template processing}). New parser settings can be added in new FreeMarker versions, which will break your
+ * implementation.
+ *
+ * @see ProcessingConfiguration
  * @since 2.3.24
  */
 public interface ParserConfiguration {
@@ -38,40 +42,89 @@ public interface ParserConfiguration {
     int getTagSyntax();
 
     /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTagSyntaxSet();
+
+    /**
      * See {@link Configuration#getNamingConvention()}.
      */
     int getNamingConvention();
 
     /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNamingConventionSet();
+
+    /**
      * See {@link Configuration#getWhitespaceStripping()}.
      */
     boolean getWhitespaceStripping();
 
     /**
-     * Overlaps with {@link Configurable#getArithmeticEngine()}; the parser needs this for creating numerical literals.
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isWhitespaceStrippingSet();
+
+    /**
+     * Overlaps with {@link MutableProcessingConfiguration#getArithmeticEngine()}; the parser needs this for creating numerical literals.
      */
     ArithmeticEngine getArithmeticEngine();
-    
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isArithmeticEngineSet();
+
     /**
      * See {@link Configuration#getAutoEscapingPolicy()}.
      */
     int getAutoEscapingPolicy();
-    
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoEscapingPolicySet();
+
     /**
      * See {@link Configuration#getOutputEncoding()}.
      */
     OutputFormat getOutputFormat();
-    
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isOutputFormatSet();
+
     /**
      * See {@link Configuration#getRecognizeStandardFileExtensions()}.
      */
     boolean getRecognizeStandardFileExtensions();
-    
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isRecognizeStandardFileExtensionsSet();
+
     /**
      * See {@link Configuration#getIncompatibleImprovements()}.
      */
     Version getIncompatibleImprovements();
-    
+
     /**
      * See {@link Configuration#getTabSize()}.
      * 
@@ -79,4 +132,18 @@ public interface ParserConfiguration {
      */
     int getTabSize();
 
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    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.
+     */
+    String getEncoding();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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
new file mode 100644
index 0000000..16cbdd5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -0,0 +1,335 @@
+/*
+ * 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.io.Writer;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+
+/**
+ * <b>Don't implement this interface yourself</b>; use the existing implementation(s). This interface is implemented by
+ * classes that hold settings that affect {@linkplain Template#process(Object, Writer) template processing} (as opposed
+ * to template parsing). New parser settings can be added in new FreeMarker versions, which will break your
+ * implementation.
+ *
+ * @see ParserConfiguration
+ */
+// TODO [FM3] JavaDoc
+public interface ProcessingConfiguration {
+
+     /**
+     * Getter pair of {@link MutableProcessingConfiguration#setLocale(Locale)}.
+     */
+    Locale getLocale();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLocaleSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setTimeZone(TimeZone)}.
+     */
+    TimeZone getTimeZone();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTimeZoneSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setSQLDateAndTimeTimeZone(TimeZone)}.
+     *
+     * @return {@code null} if the value of {@link #getTimeZone()} should be used for formatting {@link java.sql.Date
+     * java.sql.Date} and {@link java.sql.Time java.sql.Time} values, otherwise the time zone that should be used to
+     * format the values of those two types.
+     */
+    TimeZone getSQLDateAndTimeTimeZone();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isSQLDateAndTimeTimeZoneSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setNumberFormat(String)}.
+     */
+    String getNumberFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNumberFormatSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setCustomNumberFormats(Map)}.
+     */
+    Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomNumberFormatsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setBooleanFormat(String)}.
+     */
+    String getBooleanFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isBooleanFormatSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setTimeFormat(String)}.
+     */
+    String getTimeFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTimeFormatSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setDateFormat(String)}.
+     */
+    String getDateFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isDateFormatSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setDateTimeFormat(String)}.
+     */
+    String getDateTimeFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isDateTimeFormatSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setCustomDateFormats(Map)}.
+     */
+    Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomDateFormatsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setTemplateExceptionHandler(TemplateExceptionHandler)}.
+     */
+    TemplateExceptionHandler getTemplateExceptionHandler();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTemplateExceptionHandlerSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setArithmeticEngine(ArithmeticEngine)}.
+     */
+    ArithmeticEngine getArithmeticEngine();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isArithmeticEngineSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setObjectWrapper(ObjectWrapper)}.
+     */
+    ObjectWrapper getObjectWrapper();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isObjectWrapperSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setOutputEncoding(String)}.
+     */
+    String getOutputEncoding();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isOutputEncodingSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setURLEscapingCharset(String)}.
+     */
+    String getURLEscapingCharset();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isURLEscapingCharsetSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setNewBuiltinClassResolver(TemplateClassResolver)}.
+     */
+    TemplateClassResolver getNewBuiltinClassResolver();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNewBuiltinClassResolverSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setAutoFlush(boolean)}.
+     */
+    boolean getAutoFlush();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoFlushSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setShowErrorTips(boolean)}.
+     */
+    boolean getShowErrorTips();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isShowErrorTipsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setLogTemplateExceptions(boolean)}.
+     */
+    boolean getLogTemplateExceptions();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLogTemplateExceptionsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setLazyImports(boolean)}.
+     */
+    boolean getLazyImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLazyImportsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setLazyAutoImports(Boolean)}.
+     */
+    Boolean getLazyAutoImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLazyAutoImportsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setAutoImports(Map)}.
+     */
+    Map<String, String> getAutoImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoImportsSet();
+
+    /**
+     * Getter pair of {@link MutableProcessingConfiguration#setAutoIncludes(List)}.
+     */
+    List<String> getAutoIncludes();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoIncludesSet();
+
+    Map<Object, Object> getCustomAttributes();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomAttributesSet();
+
+    Object getCustomAttribute(Object name);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
new file mode 100644
index 0000000..6ff7bab
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/SettingValueNotSetException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+public class SettingValueNotSetException extends IllegalStateException {
+
+    private final String settingName;
+
+    public SettingValueNotSetException(String settingName) {
+        super("Setting " + _StringUtil.jQuote(settingName)
+                + " is not set in this layer and has no default here either.");
+        this.settingName = settingName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 e253197..77b5b2d 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -38,6 +38,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.freemarker.core.debug._DebuggerService;
 import org.apache.freemarker.core.model.ObjectWrapper;
@@ -72,7 +73,7 @@ import org.apache.freemarker.core.util._NullArgumentException;
  * shared {@link Configuration}, and you are using {@link Configuration#getTemplate(String)} (or its overloads), then
  * use {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)} to achieve that.
  */
-public class Template extends Configurable {
+public class Template extends MutableProcessingConfiguration<Template> implements CustomStateScope {
     public static final String DEFAULT_NAMESPACE_PREFIX = "D";
     public static final String NO_NS_PREFIX = "N";
 
@@ -95,6 +96,9 @@ public class Template extends Configurable {
     private Map namespaceURIToPrefixLookup = new HashMap();
     private Version templateLanguageVersion;
 
+    private final Object lock = new Object();
+    private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = new ConcurrentHashMap<>(0);
+
     /**
      * A prime constructor to which all other constructors should
      * delegate directly or indirectly.
@@ -206,7 +210,7 @@ public class Template extends Configurable {
      *            practically just overrides some of the parser settings, as the others are inherited from the
      *            {@link Configuration}. Note that if this is a {@link TemplateConfiguration}, you will also want to
      *            call {@link TemplateConfiguration#apply(Template)} on the resulting {@link Template} so that
-     *            {@link Configurable} settings will be set too, because this constructor only uses it as a
+     *            {@link MutableProcessingConfiguration} settings will be set too, because this constructor only uses it as a
      *            {@link ParserConfiguration}.
      * @param encoding
      *            Same as in {@link #Template(String, String, Reader, Configuration, String)}.
@@ -425,7 +429,7 @@ public class Template extends Configurable {
     * @param dataModel the holder of the variables visible from all templates; see {@link #process(Object, Writer)} for
     *     more details.
     * @param wrapper The {@link ObjectWrapper} to use to wrap objects into {@link TemplateModel}
-    *     instances. Normally you left it {@code null}, in which case {@link Configurable#getObjectWrapper()} will be
+    *     instances. Normally you left it {@code null}, in which case {@link MutableProcessingConfiguration#getObjectWrapper()} will be
     *     used.
     * @param out The {@link Writer} where the output of the template will go; see {@link #process(Object, Writer)} for
     *     more details.
@@ -928,5 +932,25 @@ public class Template extends Configurable {
         return prefix + ":" + localName;
     }
 
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getCustomState(CustomStateKey<T> customStateKey) {
+        T customState = (T) customStateMap.get(customStateKey);
+        if (customState == null) {
+            synchronized (lock) {
+                customState = (T) customStateMap.get(customStateKey);
+                if (customState == null) {
+                    customState = customStateKey.create();
+                    if (customState == null) {
+                        throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
+                                + customStateKey + ")");
+                    }
+                    customStateMap.put(customStateKey, customState);
+                }
+            }
+        }
+        return customState;
+    }
+
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
index 0c5a9fc..c49e3fa 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java
@@ -29,7 +29,7 @@ import org.apache.freemarker.core.util._ClassUtil;
  * The implementation should be thread-safe, unless an
  * instance is always only used in a single {@link Environment} object.
  * 
- * @see Configurable#setNewBuiltinClassResolver(TemplateClassResolver)
+ * @see MutableProcessingConfiguration#setNewBuiltinClassResolver(TemplateClassResolver)
  * 
  * @since 2.3.17
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 3d1b903..3be8b3e 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -59,11 +59,11 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * you should be aware of a few more details:
  * 
  * <ul>
- * <li>This class implements both {@link Configurable} and {@link ParserConfiguration}. This means that it can influence
+ * <li>This class implements both {@link MutableProcessingConfiguration} and {@link ParserConfiguration}. This means that it can influence
  * both the template parsing phase and the runtime settings. For both aspects (i.e., {@link ParserConfiguration} and
- * {@link Configurable}) to take effect, you have first pass this object to the {@link Template} constructor
+ * {@link MutableProcessingConfiguration}) to take effect, you have first pass this object to the {@link Template} constructor
  * (this is where the {@link ParserConfiguration} interface is used), and then you have to call {@link #apply(Template)}
- * on the resulting {@link Template} object (this is where the {@link Configurable} aspect is used).
+ * on the resulting {@link Template} object (this is where the {@link MutableProcessingConfiguration} aspect is used).
  * 
  * <li>{@link #apply(Template)} only change the settings that weren't yet set on the {@link Template} (but are inherited
  * from the {@link Configuration}). This is primarily because if the template configures itself via the {@code #ftl}
@@ -77,7 +77,8 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * 
  * @since 2.3.24
  */
-public final class TemplateConfiguration extends Configurable implements ParserConfiguration {
+public final class TemplateConfiguration extends MutableProcessingConfiguration<TemplateConfiguration>
+        implements ParserConfiguration {
 
     private TemplateLanguage templateLanguage;
     private Integer tagSyntax;
@@ -102,13 +103,13 @@ public final class TemplateConfiguration extends Configurable implements ParserC
      * Same as {@link #setParentConfiguration(Configuration)}.
      */
     @Override
-    void setParent(Configurable cfg) {
+    void setParent(MutableProcessingConfiguration cfg) {
         _NullArgumentException.check("cfg", cfg);
         if (!(cfg instanceof Configuration)) {
             throw new IllegalArgumentException("The parent of a TemplateConfiguration can only be a Configuration");
         }
         
-        Configurable parent = getParent();
+        MutableProcessingConfiguration parent = getParent();
         if (parent != null) {
             if (parent != cfg) {
                 throw new IllegalStateException(
@@ -144,7 +145,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     }
 
     private Configuration getNonNullParentConfiguration() {
-        Configurable parent = getParent();
+        MutableProcessingConfiguration parent = getParent();
         if (parent == null) {
             throw new IllegalStateException("The TemplateConfiguration wasn't associated with a Configuration yet.");
         }
@@ -253,13 +254,23 @@ public final class TemplateConfiguration extends Configurable implements ParserC
             setLazyAutoImports(tc.getLazyAutoImports());
         }
         if (tc.isAutoImportsSet()) {
-            setAutoImports(mergeMaps(getAutoImportsWithoutFallback(), tc.getAutoImportsWithoutFallback(),true));
+            setAutoImports(mergeMaps(
+                    isAutoImportsSet() ? getAutoImports() : null,
+                    tc.isAutoImportsSet() ? tc.getAutoImports() : null,
+                    true));
         }
         if (tc.isAutoIncludesSet()) {
-            setAutoIncludes(mergeLists(getAutoIncludesWithoutFallback(), tc.getAutoIncludesWithoutFallback()));
+            setAutoIncludes(mergeLists(
+                    isAutoIncludesSet() ? getAutoIncludes() : null,
+                    tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null));
+        }
+
+        if (tc.isCustomAttributesSet()) {
+            setCustomAttributes(mergeMaps(
+                    isCustomAttributesSet() ? getCustomAttributes() : null,
+                    tc.isCustomAttributesSet() ? tc.getCustomAttributes() : null,
+                    true));
         }
-        
-        tc.copyDirectCustomAttributes(this, true);
     }
 
     /**
@@ -301,11 +312,17 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         }
         if (isCustomDateFormatsSet()) {
             template.setCustomDateFormats(
-                    mergeMaps(getCustomDateFormats(), template.getCustomDateFormatsWithoutFallback(), false));
+                    mergeMaps(
+                            getCustomDateFormats(),
+                            template.isCustomDateFormatsSet() ? template.getCustomDateFormats() : null,
+                            false));
         }
         if (isCustomNumberFormatsSet()) {
             template.setCustomNumberFormats(
-                    mergeMaps(getCustomNumberFormats(), template.getCustomNumberFormatsWithoutFallback(), false));
+                    mergeMaps(
+                            getCustomNumberFormats(),
+                            template.isCustomNumberFormatsSet() ? template.getCustomNumberFormats() : null,
+                            false));
         }
         if (isDateFormatSet() && !template.isDateFormatSet()) {
             template.setDateFormat(getDateFormat());
@@ -363,10 +380,15 @@ public final class TemplateConfiguration extends Configurable implements ParserC
             // - Existing template-level imports have precedence over those coming from the TC (just as with the others
             //   apply()-ed settings), thus for clashing import prefixes they must win.
             // - Template-level imports count as more specific, and so come after the more generic ones from TC.
-            template.setAutoImports(mergeMaps(getAutoImports(), template.getAutoImportsWithoutFallback(), true));
+            template.setAutoImports(mergeMaps(
+                    getAutoImports(),
+                    template.isAutoImportsSet() ? template.getAutoImports() : null,
+                    true));
         }
         if (isAutoIncludesSet()) {
-            template.setAutoIncludes(mergeLists(getAutoIncludes(), template.getAutoIncludesWithoutFallback()));
+            template.setAutoIncludes(mergeLists(
+                    getAutoIncludes(),
+                    template.isAutoIncludesSet() ? template.getAutoIncludes() : null));
         }
         
         copyDirectCustomAttributes(template, false);
@@ -388,9 +410,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         return tagSyntax != null ? tagSyntax : getNonNullParentConfiguration().getTagSyntax();
     }
 
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     */
+    @Override
     public boolean isTagSyntaxSet() {
         return tagSyntax != null;
     }
@@ -435,6 +455,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      */
+    @Override
     public boolean isNamingConventionSet() {
         return namingConvention != null;
     }
@@ -458,6 +479,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      */
+    @Override
     public boolean isWhitespaceStrippingSet() {
         return whitespaceStripping != null;
     }
@@ -483,6 +505,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      */
+    @Override
     public boolean isAutoEscapingPolicySet() {
         return autoEscapingPolicy != null;
     }
@@ -506,6 +529,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      */
+    @Override
     public boolean isOutputFormatSet() {
         return outputFormat != null;
     }
@@ -529,12 +553,14 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      */
+    @Override
     public boolean isRecognizeStandardFileExtensionsSet() {
         return recognizeStandardFileExtensions != null;
     }
 
+    @Override
     public String getEncoding() {
-        return encoding != null ? encoding : getNonNullParentConfiguration().getDefaultEncoding();
+        return encoding != null ? encoding : getNonNullParentConfiguration().getEncoding();
     }
 
     /**
@@ -580,6 +606,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
      * 
      * @since 2.3.25
      */
+    @Override
     public boolean isTabSizeSet() {
         return tabSize != null;
     }
@@ -596,8 +623,6 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         return getNonNullParentConfiguration().getIncompatibleImprovements();
     }
     
-    
-
     @Override
     public Locale getLocale() {
         try {
@@ -879,7 +904,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
     }
 
     @Override
-    public Object getCustomAttribute(String name) {
+    public Object getCustomAttribute(Object name) {
         try {
             return super.getCustomAttribute(name);
         } catch (NullPointerException e) {
@@ -888,34 +913,6 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         }
     }
 
-    private boolean hasAnyConfigurableSet() {
-        return
-                isAPIBuiltinEnabledSet()
-                || isArithmeticEngineSet()
-                || isAutoFlushSet()
-                || isAutoImportsSet()
-                || isAutoIncludesSet()
-                || isBooleanFormatSet()
-                || isCustomDateFormatsSet()
-                || isCustomNumberFormatsSet()
-                || isDateFormatSet()
-                || isDateTimeFormatSet()
-                || isLazyImportsSet()
-                || isLazyAutoImportsSet()
-                || isLocaleSet()
-                || isLogTemplateExceptionsSet()
-                || isNewBuiltinClassResolverSet()
-                || isNumberFormatSet()
-                || isObjectWrapperSet()
-                || isOutputEncodingSet()
-                || isShowErrorTipsSet()
-                || isSQLDateAndTimeTimeZoneSet()
-                || isTemplateExceptionHandlerSet()
-                || isTimeFormatSet()
-                || isTimeZoneSet()
-                || isURLEscapingCharsetSet();
-    }
-    
     private Map mergeMaps(Map m1, Map m2, boolean overwriteUpdatesOrder) {
         if (m1 == null) return m2;
         if (m2 == null) return m1;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
index 797a4c2..8270740 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java
@@ -27,7 +27,7 @@ import org.apache.freemarker.core.util._StringUtil;
 
 /**
  * Used for the {@code template_exception_handler} configuration setting;
- * see {@link Configurable#setTemplateExceptionHandler(TemplateExceptionHandler)} for more.
+ * see {@link MutableProcessingConfiguration#setTemplateExceptionHandler(TemplateExceptionHandler)} for more.
  */
 public interface TemplateExceptionHandler {
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
index 53d09ea..e709862 100644
--- a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
+++ b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
@@ -43,11 +43,21 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
     }
 
     @Override
+    public boolean isWhitespaceStrippingSet() {
+        return wrappedPCfg.isWhitespaceStrippingSet();
+    }
+
+    @Override
     public int getTagSyntax() {
         return wrappedPCfg.getTagSyntax();
     }
 
     @Override
+    public boolean isTagSyntaxSet() {
+        return wrappedPCfg.isTagSyntaxSet();
+    }
+
+    @Override
     public TemplateLanguage getTemplateLanguage() {
         return wrappedPCfg.getTemplateLanguage();
     }
@@ -58,16 +68,31 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
     }
 
     @Override
+    public boolean isOutputFormatSet() {
+        return wrappedPCfg.isOutputFormatSet();
+    }
+
+    @Override
     public boolean getRecognizeStandardFileExtensions() {
         return false;
     }
 
     @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return wrappedPCfg.isRecognizeStandardFileExtensionsSet();
+    }
+
+    @Override
     public int getNamingConvention() {
         return wrappedPCfg.getNamingConvention();
     }
 
     @Override
+    public boolean isNamingConventionSet() {
+        return wrappedPCfg.isNamingConventionSet();
+    }
+
+    @Override
     public Version getIncompatibleImprovements() {
         return wrappedPCfg.getIncompatibleImprovements();
     }
@@ -78,13 +103,33 @@ public final class _ParserConfigurationWithInheritedFormat implements ParserConf
     }
 
     @Override
+    public boolean isAutoEscapingPolicySet() {
+        return wrappedPCfg.isAutoEscapingPolicySet();
+    }
+
+    @Override
     public ArithmeticEngine getArithmeticEngine() {
         return wrappedPCfg.getArithmeticEngine();
     }
 
     @Override
+    public boolean isArithmeticEngineSet() {
+        return wrappedPCfg.isArithmeticEngineSet();
+    }
+
+    @Override
     public int getTabSize() {
         return wrappedPCfg.getTabSize();
     }
-    
+
+    @Override
+    public boolean isTabSizeSet() {
+        return wrappedPCfg.isTabSizeSet();
+    }
+
+    @Override
+    public String getEncoding() {
+        return wrappedPCfg.getEncoding();
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java b/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
index 9536ac8..dca312d 100644
--- a/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
+++ b/src/main/java/org/apache/freemarker/core/debug/DebuggedEnvironment.java
@@ -21,6 +21,8 @@ package org.apache.freemarker.core.debug;
 
 import java.rmi.RemoteException;
 
+import org.apache.freemarker.core.MutableProcessingConfiguration;
+
 /**
  * Represents the debugger-side mirror of a debugged 
  * {@link org.apache.freemarker.core.Environment} object in the remote VM. This interface
@@ -32,7 +34,7 @@ import java.rmi.RemoteException;
  * <p>The debug model for the configuration supports key "sharedVariables".
  * <p>Additionally, all of the debug models for environment, template, and 
  * configuration also support all the setting keys of 
- * {@link org.apache.freemarker.core.Configurable} objects. 
+ * {@link MutableProcessingConfiguration} objects.
 
  */
 public interface DebuggedEnvironment extends DebugModel {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 8f83eca..c42af46 100644
--- a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
@@ -32,9 +32,10 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
-import org.apache.freemarker.core.Configurable;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
+import org.apache.freemarker.core.ProcessingConfiguration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
@@ -164,17 +165,17 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
     
     private static class DebugConfigurableModel extends DebugMapModel {
         static final List KEYS = Arrays.asList(
-                Configurable.ARITHMETIC_ENGINE_KEY,
-                Configurable.BOOLEAN_FORMAT_KEY,
-                Configurable.LOCALE_KEY,
-                Configurable.NUMBER_FORMAT_KEY,
-                Configurable.OBJECT_WRAPPER_KEY,
-                Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY);
+                MutableProcessingConfiguration.ARITHMETIC_ENGINE_KEY,
+                MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY,
+                MutableProcessingConfiguration.LOCALE_KEY,
+                MutableProcessingConfiguration.NUMBER_FORMAT_KEY,
+                MutableProcessingConfiguration.OBJECT_WRAPPER_KEY,
+                MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY);
 
-        final Configurable configurable;
+        final ProcessingConfiguration ProcessingConfiguration;
         
-        DebugConfigurableModel(Configurable configurable) {
-            this.configurable = configurable;
+        DebugConfigurableModel(ProcessingConfiguration processingConfiguration) {
+            this.ProcessingConfiguration = processingConfiguration;
         }
         
         @Override
@@ -196,12 +197,12 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
         {
             @Override
             Collection keySet() {
-                return ((Configuration) configurable).getSharedVariableNames();
+                return ((Configuration) ProcessingConfiguration).getSharedVariableNames();
             }
         
             @Override
             public TemplateModel get(String key) {
-                return ((Configuration) configurable).getSharedVariable(key);
+                return ((Configuration) ProcessingConfiguration).getSharedVariable(key);
             }
         };
         
@@ -244,7 +245,7 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
         public TemplateModel get(String key) throws TemplateModelException {
             if ("configuration".equals(key)) {
                 try {
-                    return (TemplateModel) getCachedWrapperFor(((Template) configurable).getConfiguration());
+                    return (TemplateModel) getCachedWrapperFor(((Template) ProcessingConfiguration).getConfiguration());
                 } catch (RemoteException e) {
                     throw new TemplateModelException(e);
                 }
@@ -271,7 +272,7 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
             @Override
             Collection keySet() {
                 try {
-                    return ((Environment) configurable).getKnownVariableNames();
+                    return ((Environment) ProcessingConfiguration).getKnownVariableNames();
                 } catch (TemplateModelException e) {
                     throw new UndeclaredThrowableException(e);
                 }
@@ -279,7 +280,7 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
         
             @Override
             public TemplateModel get(String key) throws TemplateModelException {
-                return ((Environment) configurable).getVariable(key);
+                return ((Environment) ProcessingConfiguration).getVariable(key);
             }
         };
          
@@ -295,30 +296,30 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
         @Override
         public TemplateModel get(String key) throws TemplateModelException {
             if ("currentNamespace".equals(key)) {
-                return ((Environment) configurable).getCurrentNamespace();
+                return ((Environment) ProcessingConfiguration).getCurrentNamespace();
             }
             if ("dataModel".equals(key)) {
-                return ((Environment) configurable).getDataModel();
+                return ((Environment) ProcessingConfiguration).getDataModel();
             }
             if ("globalNamespace".equals(key)) {
-                return ((Environment) configurable).getGlobalNamespace();
+                return ((Environment) ProcessingConfiguration).getGlobalNamespace();
             }
             if ("knownVariables".equals(key)) {
                 return knownVariables;
             }
             if ("mainNamespace".equals(key)) {
-                return ((Environment) configurable).getMainNamespace();
+                return ((Environment) ProcessingConfiguration).getMainNamespace();
             }
             if ("mainTemplate".equals(key)) {
                 try {
-                    return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getMainTemplate());
+                    return (TemplateModel) getCachedWrapperFor(((Environment) ProcessingConfiguration).getMainTemplate());
                 } catch (RemoteException e) {
                     throw new TemplateModelException(e);
                 }
             }
             if ("currentTemplate".equals(key)) {
                 try {
-                    return (TemplateModel) getCachedWrapperFor(((Environment) configurable).getCurrentTemplate());
+                    return (TemplateModel) getCachedWrapperFor(((Environment) ProcessingConfiguration).getCurrentTemplate());
                 } catch (RemoteException e) {
                     throw new TemplateModelException(e);
                 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
index d630752..91fe9dc 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -125,7 +125,7 @@ public class BeanModel
      * <tt>non-void-return-type get(java.lang.String)</tt>,
      * then <tt>non-void-return-type get(java.lang.Object)</tt>, or 
      * alternatively (if the wrapped object is a resource bundle) 
-     * <tt>Object getObject(java.lang.String)</tt>.
+     * <tt>Object get(java.lang.String)</tt>.
      * @throws TemplateModelException if there was no property nor method nor
      * a generic <tt>get</tt> method to invoke.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 5bc97a1..6cde3ee 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
@@ -1275,6 +1275,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
          * Returns a {@link DefaultObjectWrapper} instance that matches the settings of this builder. This will be possibly
          * a singleton that is also in use elsewhere.
          */
+        @Override
         public DefaultObjectWrapper build() {
             return DefaultObjectWrapperTCCLSingletonUtil.getSingleton(
                     this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, ConstructorInvoker.INSTANCE);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
index c48b2e0..31af451 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
@@ -63,7 +63,7 @@ public class ResourceBundleModel
     }
 
     /**
-     * Overridden to invoke the getObject method of the resource bundle.
+     * Overridden to invoke the get method of the resource bundle.
      */
     @Override
     protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
index a4df46f..f520c3d 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
@@ -67,11 +67,11 @@ import org.apache.freemarker.core.model.WrappingTemplateModel;
  * <p>
  * It also matters if for how many times will the <em>same</em> {@link Map} entry be read from the template(s) later, on
  * average. If, on average, you read each entry for more than 4 times, {@link SimpleHash} will be most certainly faster,
- * but if for 2 times or less (and especially if not at all) then {@link DefaultMapAdapter} will be. Before choosing
- * based on performance though, pay attention to the behavioral differences; {@link SimpleHash} will shallow-copy
- * the original {@link Map} at construction time, so key order will be lost in some cases, and it won't reflect
- * {@link Map} content changes after the {@link SimpleHash} construction, also {@link SimpleHash} can't be unwrapped
- * to the original {@link Map} instance.
+ * but if for 2 times or less (and especially if not at all) then {@link DefaultMapAdapter} will be faster. Before
+ * choosing based on performance though, pay attention to the behavioral differences; {@link SimpleHash} will
+ * shallow-copy the original {@link Map} at construction time, so key order will be lost in some cases, and it won't
+ * reflect {@link Map} content changes after the {@link SimpleHash} construction, also {@link SimpleHash} can't be
+ * unwrapped to the original {@link Map} instance.
  *
  * @see DefaultMapAdapter
  * @see TemplateHashModelEx

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
index 907acb2..1b949f1 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
@@ -59,7 +59,7 @@ import org.apache.freemarker.core.model.WrappingTemplateModel;
  * It also matters if for how many times will the <em>same</em> {@link List} entry be read from the template(s) later,
  * on average. If, on average, you read each entry for more than 4 times, {@link SimpleSequence} will be most
  * certainly faster, but if for 2 times or less (and especially if not at all) then {@link DefaultMapAdapter} will
- * be. Before choosing based on performance though, pay attention to the behavioral differences;
+ * be faster. Before choosing based on performance though, pay attention to the behavioral differences;
  * {@link SimpleSequence} will shallow-copy the original {@link List} at construction time, so it won't reflect
  * {@link List} content changes after the {@link SimpleSequence} construction, also {@link SimpleSequence} can't be
  * unwrapped to the original wrapped instance.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 a5a574c..4436874 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
@@ -551,10 +551,8 @@ public class DefaultTemplateResolver extends TemplateResolver {
         if (tc != null && tc.isLocaleSet()) {
             locale = tc.getLocale();
         }
-
-        String initialEncoding = tc != null && tc.isEncodingSet() ? tc.getEncoding() : config.getDefaultEncoding();
-        TemplateLanguage templateLanguage = tc != null && tc.isTemplateLanguageSet() ? tc.getTemplateLanguage()
-                : config .getTemplateLanguage();
+        String initialEncoding = tc != null ? tc.getEncoding() : config.getEncoding();
+        TemplateLanguage templateLanguage = tc != null ? tc.getTemplateLanguage() : config .getTemplateLanguage();
 
         Template template;
         {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/util/OptInTemplateClassResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/OptInTemplateClassResolver.java b/src/main/java/org/apache/freemarker/core/util/OptInTemplateClassResolver.java
index 51da424..8fd7b38 100644
--- a/src/main/java/org/apache/freemarker/core/util/OptInTemplateClassResolver.java
+++ b/src/main/java/org/apache/freemarker/core/util/OptInTemplateClassResolver.java
@@ -26,7 +26,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
-import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateClassResolver;
@@ -104,7 +104,7 @@ public class OptInTemplateClassResolver implements TemplateClassResolver {
                 throw new _MiscTemplateException(env,
                         "Instantiating ", className, " is not allowed in the template for security reasons. (If you "
                         + "run into this problem when using ?new in a template, you may want to check the \"",
-                        Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY,
+                        MutableProcessingConfiguration.NEW_BUILTIN_CLASS_RESOLVER_KEY,
                         "\" setting in the FreeMarker configuration.)");
             } else {
                 try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java b/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
index 1b54947..5d532de 100644
--- a/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
@@ -25,8 +25,8 @@ public class _CollectionUtil {
     private _CollectionUtil() { }
 
     public static final Object[] EMPTY_OBJECT_ARRAY = new Object[] { };
-
     public static final Class[] EMPTY_CLASS_ARRAY = new Class[] { };
+    public static final String[] EMPTY_STRING_ARRAY = new String[] { };
 
     /**
      * @since 2.3.22

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/util/_LocaleUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_LocaleUtil.java b/src/main/java/org/apache/freemarker/core/util/_LocaleUtil.java
index 2ee4d53..2f09c88 100644
--- a/src/main/java/org/apache/freemarker/core/util/_LocaleUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_LocaleUtil.java
@@ -22,9 +22,7 @@ import java.util.Locale;
 
 /**
  * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
- * access things inside this package that users shouldn't. 
- */ 
+ */
 public class _LocaleUtil {
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java b/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java
new file mode 100644
index 0000000..cbd7e11
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ */
+public class _ObjectHolder<T> {
+
+    private T object;
+
+    public _ObjectHolder(T object) {
+        this.object = object;
+    }
+
+    public T get() {
+        return object;
+    }
+
+    public void set(T object) {
+        this.object = object;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        _ObjectHolder<?> that = (_ObjectHolder<?>) o;
+
+        return object != null ? object.equals(that.object) : that.object == null;
+    }
+
+    @Override
+    public int hashCode() {
+        return object != null ? object.hashCode() : 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/valueformat/TemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/TemplateDateFormatFactory.java b/src/main/java/org/apache/freemarker/core/valueformat/TemplateDateFormatFactory.java
index 5ee26e0..07c2256 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/TemplateDateFormatFactory.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/TemplateDateFormatFactory.java
@@ -23,7 +23,7 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
-import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.model.TemplateDateModel;
@@ -32,7 +32,7 @@ import org.apache.freemarker.core.model.TemplateDateModel;
  * Factory for a certain kind of date/time/dateTime formatting ({@link TemplateDateFormat}). Usually a singleton
  * (one-per-VM or one-per-{@link Configuration}), and so must be thread-safe.
  * 
- * @see Configurable#setCustomDateFormats(java.util.Map)
+ * @see MutableProcessingConfiguration#setCustomDateFormats(java.util.Map)
  * 
  * @since 2.3.24
  */
@@ -50,7 +50,7 @@ public abstract class TemplateDateFormatFactory extends TemplateValueFormatFacto
      * 
      * @param params
      *            The string that further describes how the format should look. For example, when the
-     *            {@link Configurable#getDateFormat() dateFormat} is {@code "@fooBar 1, 2"}, then it will be
+     *            {@link MutableProcessingConfiguration#getDateFormat() dateFormat} is {@code "@fooBar 1, 2"}, then it will be
      *            {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the
      *            {@link TemplateDateFormatFactory} implementation. Not {@code null}, often an empty string.
      * @param dateType

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java b/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
index 464e6ff..7e21cfe 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
@@ -20,7 +20,7 @@ package org.apache.freemarker.core.valueformat;
 
 import java.util.Locale;
 
-import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Environment;
 
@@ -28,7 +28,7 @@ import org.apache.freemarker.core.Environment;
  * Factory for a certain kind of number formatting ({@link TemplateNumberFormat}). Usually a singleton (one-per-VM or
  * one-per-{@link Configuration}), and so must be thread-safe.
  * 
- * @see Configurable#setCustomNumberFormats(java.util.Map)
+ * @see MutableProcessingConfiguration#setCustomNumberFormats(java.util.Map)
  * 
  * @since 2.3.24
  */
@@ -46,7 +46,7 @@ public abstract class TemplateNumberFormatFactory extends TemplateValueFormatFac
      * 
      * @param params
      *            The string that further describes how the format should look. For example, when the
-     *            {@link Configurable#getNumberFormat() numberFormat} is {@code "@fooBar 1, 2"}, then it will be
+     *            {@link MutableProcessingConfiguration#getNumberFormat() numberFormat} is {@code "@fooBar 1, 2"}, then it will be
      *            {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the
      *            {@link TemplateNumberFormatFactory} implementation. Not {@code null}, often an empty string.
      * @param locale

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java b/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
index fb13fad..5db8f46 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core.valueformat.impl;
 
+import org.apache.freemarker.core.CustomStateKey;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.util._DateUtil.CalendarFieldsToDateConverter;
 import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
@@ -28,27 +29,29 @@ import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 
 abstract class ISOLikeTemplateDateFormatFactory extends TemplateDateFormatFactory {
     
-    private static final Object DATE_TO_CAL_CONVERTER_KEY = new Object();
-    private static final Object CAL_TO_DATE_CONVERTER_KEY = new Object();
+    private static final CustomStateKey<TrivialDateToISO8601CalendarFactory> DATE_TO_CAL_CONVERTER_KEY
+            = new CustomStateKey<TrivialDateToISO8601CalendarFactory>() {
+        @Override
+        protected TrivialDateToISO8601CalendarFactory create() {
+            return new TrivialDateToISO8601CalendarFactory();
+        }
+    };
+    private static final CustomStateKey<TrivialCalendarFieldsToDateConverter> CAL_TO_DATE_CONVERTER_KEY
+            = new CustomStateKey<TrivialCalendarFieldsToDateConverter>() {
+        @Override
+        protected TrivialCalendarFieldsToDateConverter create() {
+            return new TrivialCalendarFieldsToDateConverter();
+        }
+    };
     
     protected ISOLikeTemplateDateFormatFactory() { }
 
     public DateToISO8601CalendarFactory getISOBuiltInCalendar(Environment env) {
-        DateToISO8601CalendarFactory r = (DateToISO8601CalendarFactory) env.getCustomState(DATE_TO_CAL_CONVERTER_KEY);
-        if (r == null) {
-            r = new TrivialDateToISO8601CalendarFactory();
-            env.setCustomState(DATE_TO_CAL_CONVERTER_KEY, r);
-        }
-        return r;
+        return (DateToISO8601CalendarFactory) env.getCustomState(DATE_TO_CAL_CONVERTER_KEY);
     }
 
     public CalendarFieldsToDateConverter getCalendarFieldsToDateCalculator(Environment env) {
-        CalendarFieldsToDateConverter r = (CalendarFieldsToDateConverter) env.getCustomState(CAL_TO_DATE_CONVERTER_KEY);
-        if (r == null) {
-            r = new TrivialCalendarFieldsToDateConverter();
-            env.setCustomState(CAL_TO_DATE_CONVERTER_KEY, r);
-        }
-        return r;
+        return (CalendarFieldsToDateConverter) env.getCustomState(CAL_TO_DATE_CONVERTER_KEY);
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java b/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
index 36773f4..f578158 100644
--- a/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
+++ b/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
@@ -31,7 +31,7 @@ import java.util.Map;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
-import org.apache.freemarker.core.CustomAttribute;
+import org.apache.freemarker.core.CustomStateKey;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateException;
@@ -42,6 +42,7 @@ import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.util.UndeclaredThrowableException;
+import org.apache.freemarker.core.util._ObjectHolder;
 import org.jaxen.BaseXPath;
 import org.jaxen.Function;
 import org.jaxen.FunctionCallException;
@@ -62,14 +63,14 @@ import org.xml.sax.SAXException;
 /**
  */
 class JaxenXPathSupport implements XPathSupport {
-    
-    private static final CustomAttribute XPATH_CACHE_ATTR = 
-        new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE) {
-            @Override
-            protected Object create() {
-                return new HashMap<String, BaseXPath>();
-            }
-        };
+
+    private static final CustomStateKey<Map<String, BaseXPath>> XPATH_CACHE_ATTR
+            = new CustomStateKey<Map<String, BaseXPath>>() {
+        @Override
+        protected Map<String, BaseXPath> create() {
+            return new HashMap<String, BaseXPath>();
+        }
+    };
 
         // [2.4] Can't we just use Collections.emptyList()? 
     private final static ArrayList EMPTY_ARRAYLIST = new ArrayList();
@@ -78,7 +79,8 @@ class JaxenXPathSupport implements XPathSupport {
     public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException {
         try {
             BaseXPath xpath;
-            Map<String, BaseXPath> xpathCache = (Map<String, BaseXPath>) XPATH_CACHE_ATTR.get();
+            Map<String, BaseXPath> xpathCache = Environment.getCurrentEnvironmentNotNull().getCurrentTemplateNotNull()
+                    .getCustomState(XPATH_CACHE_ATTR);
             synchronized (xpathCache) {
                 xpath = xpathCache.get(xpathQuery);
                 if (xpath == null) {
@@ -162,29 +164,38 @@ class JaxenXPathSupport implements XPathSupport {
     /**
      * Stores the the template parsed as {@link Document} in the template itself.
      */
-    private static final CustomAttribute FM_DOM_NAVIAGOTOR_CACHED_DOM
-            = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
+    private static final CustomStateKey<_ObjectHolder<Document>> FM_DOM_NAVIAGOTOR_CACHED_DOM
+            = new CustomStateKey<_ObjectHolder<Document>>() {
+        @Override
+        protected _ObjectHolder<Document> create() {
+            return new _ObjectHolder<>(null);
+        }
+    };
      
     private static final Navigator FM_DOM_NAVIGATOR = new DocumentNavigator() {
         @Override
         public Object getDocument(String uri) throws FunctionCallException {
             try {
                 Template raw = getTemplate(uri);
-                Document doc = (Document) FM_DOM_NAVIAGOTOR_CACHED_DOM.get(raw);
-                if (doc == null) {
-                    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                    factory.setNamespaceAware(true);
-                    DocumentBuilder builder = factory.newDocumentBuilder();
-                    FmEntityResolver er = new FmEntityResolver();
-                    builder.setEntityResolver(er);
-                    doc = builder.parse(createInputSource(null, raw));
-                    // If the entity resolver got called 0 times, the document
-                    // is standalone, so we can safely cache it
-                    if (er.getCallCount() == 0) {
-                        FM_DOM_NAVIAGOTOR_CACHED_DOM.set(doc, raw);
+                _ObjectHolder<Document> docHolder = Environment.getCurrentEnvironmentNotNull()
+                        .getCurrentTemplateNotNull().getCustomState(FM_DOM_NAVIAGOTOR_CACHED_DOM);
+                synchronized (docHolder) {
+                    Document doc = docHolder.get();
+                    if (doc == null) {
+                        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                        factory.setNamespaceAware(true);
+                        DocumentBuilder builder = factory.newDocumentBuilder();
+                        FmEntityResolver er = new FmEntityResolver();
+                        builder.setEntityResolver(er);
+                        doc = builder.parse(createInputSource(null, raw));
+                        // If the entity resolver got called 0 times, the document
+                        // is standalone, so we can safely cache it
+                        if (er.getCallCount() == 0) {
+                            docHolder.set(doc);
+                        }
                     }
+                    return doc;
                 }
-                return doc;
             } catch (Exception e) {
                 throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 0853133..48fcde5 100644
--- a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -41,7 +41,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.ConfigurationException;
 import org.apache.freemarker.core.Environment;
@@ -199,16 +199,16 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  * {@value #INIT_PARAM_VALUE_FROM_TEMPLATE} (or some of the other options) instead. {@value #INIT_PARAM_VALUE_LEGACY}
  * will use the charset of the template file to set the charset of the servlet response. Except, if the
  * {@value #INIT_PARAM_CONTENT_TYPE} init-param contains a charset, it will use that instead. A quirk of this legacy
- * mode is that it's not aware of the {@link Configurable#getOutputEncoding()} FreeMarker setting, and thus never reads
+ * mode is that it's not aware of the {@link MutableProcessingConfiguration#getOutputEncoding()} FreeMarker setting, and thus never reads
  * or writes it (though very few applications utilize that setting anyway). Also, it sets the charset of the servlet
  * response by adding it to the response content type via calling {@link HttpServletResponse#setContentType(String)} (as
  * that was the only way before Servlet 2.4), not via the more modern
  * {@link HttpServletResponse#setCharacterEncoding(String)} method. Note that the charset of a template usually comes
- * from {@link Configuration#getDefaultEncoding()} (i.e., from the {@code default_encoding} FreeMarker setting),
+ * from {@link Configuration#getEncoding()} (i.e., from the {@code encoding} FreeMarker setting),
  * or occasionally from {@link Configuration#getTemplateConfigurations()} (when FreeMarker was
  * configured to use a specific charset for certain templates).
  * <li>{@value #INIT_PARAM_VALUE_FROM_TEMPLATE}: This should be used in most applications, but it's not the default for
- * backward compatibility. It reads the {@link Configurable#getOutputEncoding()} setting of the template (note that the
+ * backward compatibility. It reads the {@link MutableProcessingConfiguration#getOutputEncoding()} setting of the template (note that the
  * template usually just inherits that from the {@link Configuration}), and if that's not set, then reads the source
  * charset of the template, just like {@value #INIT_PARAM_VALUE_LEGACY}, and if that's {@code null} (which happens if
  * the template was loaded from a non-binary source) then it will be UTF-8. Then it passes the charset acquired this way
@@ -622,16 +622,16 @@ public class FreemarkerServlet extends HttpServlet {
             
             try {
                 if (name.equals(DEPR_INITPARAM_OBJECT_WRAPPER)
-                        || name.equals(Configurable.OBJECT_WRAPPER_KEY)
+                        || name.equals(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY)
                         || name.equals(INIT_PARAM_TEMPLATE_PATH)
                         || name.equals(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY)) {
                     // ignore: we have already processed these
                 } else if (name.equals(DEPR_INITPARAM_ENCODING)) { // BC
-                    if (getInitParameter(Configuration.DEFAULT_ENCODING_KEY) != null) {
+                    if (getInitParameter(Configuration.ENCODING_KEY) != null) {
                         throw new ConflictingInitParamsException(
-                                Configuration.DEFAULT_ENCODING_KEY, DEPR_INITPARAM_ENCODING);
+                                Configuration.ENCODING_KEY, DEPR_INITPARAM_ENCODING);
                     }
-                    config.setDefaultEncoding(value);
+                    config.setEncoding(value);
                 } else if (name.equals(DEPR_INITPARAM_TEMPLATE_DELAY)) { // BC
                     if (getInitParameter(Configuration.TEMPLATE_UPDATE_DELAY_KEY) != null) {
                         throw new ConflictingInitParamsException(
@@ -643,9 +643,9 @@ public class FreemarkerServlet extends HttpServlet {
                         // Intentionally ignored
                     }
                 } else if (name.equals(DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER)) { // BC
-                    if (getInitParameter(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY) != null) {
+                    if (getInitParameter(MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY) != null) {
                         throw new ConflictingInitParamsException(
-                                Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY, DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER);
+                                MutableProcessingConfiguration.TEMPLATE_EXCEPTION_HANDLER_KEY, DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER);
                     }
     
                     if (DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW.equals(value)) {
@@ -1279,13 +1279,13 @@ public class FreemarkerServlet extends HttpServlet {
      * 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 Configurable#OBJECT_WRAPPER_KEY} as argument, and see if it
+     * 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.
      * 
      * <p>
      * The default implementation interprets the {@code object_wrapper} servlet init-param with
-     * calling {@link Configurable#setSetting(String, String)} (see valid values there), or if there's no such servlet
+     * 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
@@ -1294,9 +1294,9 @@ public class FreemarkerServlet extends HttpServlet {
     protected ObjectWrapperAndUnwrapper createObjectWrapper() {
         String wrapper = getServletConfig().getInitParameter(DEPR_INITPARAM_OBJECT_WRAPPER);
         if (wrapper != null) { // BC
-            if (getInitParameter(Configurable.OBJECT_WRAPPER_KEY) != null) {
+            if (getInitParameter(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY) != null) {
                 throw new RuntimeException("Conflicting init-params: "
-                        + Configurable.OBJECT_WRAPPER_KEY + " and "
+                        + MutableProcessingConfiguration.OBJECT_WRAPPER_KEY + " and "
                         + DEPR_INITPARAM_OBJECT_WRAPPER);
             }
             if (DEPR_INITPARAM_WRAPPER_RESTRICTED.equals(wrapper)) {
@@ -1304,7 +1304,7 @@ public class FreemarkerServlet extends HttpServlet {
             }
             return createDefaultObjectWrapper();
         } else {
-            wrapper = getInitParameter(Configurable.OBJECT_WRAPPER_KEY);
+            wrapper = getInitParameter(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY);
             if (wrapper == null) {
                 if (!config.isObjectWrapperExplicitlySet()) {
                     return createDefaultObjectWrapper();
@@ -1313,9 +1313,9 @@ public class FreemarkerServlet extends HttpServlet {
                 }
             } else {
                 try {
-                    config.setSetting(Configurable.OBJECT_WRAPPER_KEY, wrapper);
+                    config.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, wrapper);
                 } catch (ConfigurationException e) {
-                    throw new RuntimeException("Failed to set " + Configurable.OBJECT_WRAPPER_KEY, e);
+                    throw new RuntimeException("Failed to set " + MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, e);
                 }
                 return asObjectWrapperAndUnwrapper(config.getObjectWrapper());
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 5330a6a..9a149b3 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -170,4 +170,19 @@ the FreeMarer 3 changelog here:
   again, the charset of a template file is independent of how you access it.)
 - Removed Configuration.setEncoding(java.util.Locale, String) and the related other methods. Because of the new logic of template
   encodings, the locale to encoding mapping doesn't make much sense anymore.
-- Require customLookupCondition-s to be Serializable.
\ No newline at end of file
+- Require customLookupCondition-s to be Serializable.
+- Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer
+  builders to create them.
+  - Removed CustomAttribute class. Custom attribute keys can be anything at the moment (this will be certainly restricted later)
+  - As customAttributes won't be modifiable after Builder.build(), they can't be used for on-demand created data structures anymore (such as
+    Template-scoped caches) anymore. To fulfill that role, the CustomStateKey class and the CustomStateScope interface was introduced, which
+    is somewhat similar to the now removed CustomAttribute. CustomStateScope contains one method, Object getCustomState(CustomStateKey), which
+    may calls CustomStateKey.create() to lazily create the state object for the key. Configuration, Template and Environment implements
+    CustomStateScope.
+  - Added getter/setter to access custom attributes as a Map. (This is to make it less an exceptional setting.)
+  - Environment.setCustomState(Object, Object) and getCustomState(Object) was replaced with CustomStateScope.getCustomState(CustomStateKey).
+  - Added ProcessingConfiguration interface for the read-only access of template processing settings. This is similar to the
+    already existing (in FM2) ParserConfiguration interface.
+  - Renamed Configurable to MutableProcessingAndParserConfiguration. Made it abstract too.
+  - Renamed Configuration.defaultEncoding to encoding, also added encoding ParserConfiguration. Before this, defaultEncoding was exclusive
+    to Configuration, but now it's like any other ParserConfiguration setting.
\ No newline at end of file



[6/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

Posted by dd...@apache.org.
Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

- Removed CustomAttribute class. Custom attribute keys can be anything at the moment (this will be certainly restricted later)
- As customAttributes won't be modifiable after Builder.build(), they can't be used for on-demand created data structures anymore (such as Template-scoped caches) anymore. To fulfill that role, the CustomStateKey class and the CustomStateScope interface was introduced, which is somewhat similar to the now removed CustomAttribute. CustomStateScope contains one method, Object getCustomState(CustomStateKey), which may calls CustomStateKey.create() to lazily create the state object for the key. Configuration, Template and Environment implements CustomStateScope.
- Environment.setCustomState(Object, Object) and getCustomState(Object) was replaced with CustomStateScope.getCustomState(CustomStateKey).
- Added getter/setter to access custom attributes as a Map. (This is to make it less an exceptional setting.)
- Added ProcessingConfiguration interface for the read-only access of template processing settings. This is similar to the already existing (in FM2) ParserConfiguration interface.
- Renamed Configurable to MutableProcessingAndParserConfiguration. Made it abstract too.
- Renamed Configuration.defaultEncoding to encoding, also added encoding ParserConfiguration. Before this, defaultEncoding was exclusive to Configuration, but now it's like any other ParserConfiguration setting.


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

Branch: refs/heads/3
Commit: 053afbf5d8f4e551ba416fb5a99327c60f08ef1d
Parents: 1870d02
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 27 13:48:36 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 27 13:48:36 2017 +0200

----------------------------------------------------------------------
 .../Java-code-style-FreeMarker.xml              |   31 +-
 .../apache/freemarker/core/ASTDirSetting.java   |   38 +-
 .../core/BuiltInsForMultipleTypes.java          |    2 +-
 .../apache/freemarker/core/Configurable.java    | 2741 -----------------
 .../apache/freemarker/core/Configuration.java   |  155 +-
 .../apache/freemarker/core/CustomAttribute.java |  264 --
 .../apache/freemarker/core/CustomStateKey.java  |   60 +
 .../freemarker/core/CustomStateScope.java       |   34 +
 .../org/apache/freemarker/core/Environment.java |   87 +-
 .../MutableProcessingAndParseConfiguration.java |  282 ++
 .../core/MutableProcessingConfiguration.java    | 2890 ++++++++++++++++++
 .../freemarker/core/ParserConfiguration.java    |   85 +-
 .../core/ProcessingConfiguration.java           |  335 ++
 .../core/SettingValueNotSetException.java       |   33 +
 .../org/apache/freemarker/core/Template.java    |   30 +-
 .../freemarker/core/TemplateClassResolver.java  |    2 +-
 .../freemarker/core/TemplateConfiguration.java  |   97 +-
 .../core/TemplateExceptionHandler.java          |    2 +-
 ..._ParserConfigurationWithInheritedFormat.java |   47 +-
 .../core/debug/DebuggedEnvironment.java         |    4 +-
 .../core/debug/RmiDebuggedEnvironmentImpl.java  |   43 +-
 .../freemarker/core/model/impl/BeanModel.java   |    2 +-
 .../core/model/impl/DefaultObjectWrapper.java   |    1 +
 .../core/model/impl/ResourceBundleModel.java    |    2 +-
 .../freemarker/core/model/impl/SimpleHash.java  |   10 +-
 .../core/model/impl/SimpleSequence.java         |    2 +-
 .../impl/DefaultTemplateResolver.java           |    6 +-
 .../core/util/OptInTemplateClassResolver.java   |    4 +-
 .../freemarker/core/util/_CollectionUtil.java   |    2 +-
 .../freemarker/core/util/_LocaleUtil.java       |    4 +-
 .../freemarker/core/util/_ObjectHolder.java     |   55 +
 .../valueformat/TemplateDateFormatFactory.java  |    6 +-
 .../TemplateNumberFormatFactory.java            |    6 +-
 .../impl/ISOLikeTemplateDateFormatFactory.java  |   31 +-
 .../freemarker/dom/JaxenXPathSupport.java       |   61 +-
 .../freemarker/servlet/FreemarkerServlet.java   |   34 +-
 src/manual/en_US/FM3-CHANGE-LOG.txt             |   17 +-
 .../freemarker/core/ConfigurableTest.java       |   18 +-
 .../freemarker/core/ConfigurationTest.java      |   58 +-
 .../freemarker/core/CustomAttributeTest.java    |   76 +-
 .../freemarker/core/EncodingOverrideTest.java   |    2 +-
 .../core/EnvironmentCustomStateTest.java        |   47 -
 .../IncludeAndImportConfigurableLayersTest.java |    2 +-
 .../core/ObjectBuilderSettingsTest.java         |   30 +-
 .../core/TemplateConfigurationTest.java         |  148 +-
 ...igurationWithDefaltTemplateResolverTest.java |   28 +-
 .../core/TemplateConstructorsTest.java          |    2 +-
 .../core/TemplateGetEncodingTest.java           |    2 +-
 .../DefaultTemplateResolverTest.java            |    4 +-
 .../manualtest/GettingStartedExample.java       |    2 +-
 .../TemplateConfigurationExamples.java          |    2 +-
 .../servlet/FreemarkerServletTest.java          |    4 +-
 .../apache/freemarker/test/TemplateTest.java    |    2 +-
 .../test/templatesuite/TemplateTestCase.java    |    2 +-
 .../servlet/jsp/webapps/basic/WEB-INF/web.xml   |    4 +-
 .../jsp/webapps/multipleLoaders/WEB-INF/web.xml |    2 +-
 .../jsp/webapps/tldDiscovery/WEB-INF/web.xml    |    4 +-
 .../org/apache/freemarker/test/servlet/web.xml  |    2 +-
 58 files changed, 4385 insertions(+), 3561 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
----------------------------------------------------------------------
diff --git a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
index c4bbf97..d77ca2d 100644
--- a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
+++ b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
@@ -1,21 +1,3 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing,
-  software distributed under the License is distributed on an
-  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  KIND, either express or implied.  See the License for the
-  specific language governing permissions and limitations
-  under the License.
--->
 <code_scheme name="FreeMarker">
   <option name="LINE_SEPARATOR" value="&#xA;" />
   <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
@@ -45,9 +27,22 @@
   <option name="JD_PARAM_DESCRIPTION_ON_NEW_LINE" value="true" />
   <option name="WRAP_COMMENTS" value="true" />
   <JavaCodeStyleSettings>
+    <option name="ANNOTATION_PARAMETER_WRAP" value="1" />
     <option name="CLASS_NAMES_IN_JAVADOC" value="3" />
   </JavaCodeStyleSettings>
   <codeStyleSettings language="JAVA">
     <option name="RIGHT_MARGIN" value="120" />
+    <option name="CALL_PARAMETERS_WRAP" value="1" />
+    <option name="EXTENDS_LIST_WRAP" value="1" />
+    <option name="THROWS_LIST_WRAP" value="1" />
+    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+    <option name="BINARY_OPERATION_WRAP" value="1" />
+    <option name="TERNARY_OPERATION_WRAP" value="1" />
+    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+    <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+    <option name="ASSIGNMENT_WRAP" value="1" />
+    <option name="WRAP_LONG_LINES" value="true" />
+    <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
+    <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
   </codeStyleSettings>
 </code_scheme>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 0e1c31d..396d269 100644
--- a/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
+++ b/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
@@ -37,25 +37,25 @@ final class ASTDirSetting extends ASTDirective {
     
     static final String[] SETTING_NAMES = new String[] {
             // Must be sorted alphabetically!
-            Configurable.BOOLEAN_FORMAT_KEY_CAMEL_CASE,
-            Configurable.BOOLEAN_FORMAT_KEY_SNAKE_CASE,
-            Configurable.DATE_FORMAT_KEY_CAMEL_CASE,
-            Configurable.DATE_FORMAT_KEY_SNAKE_CASE,
-            Configurable.DATETIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.DATETIME_FORMAT_KEY_SNAKE_CASE,
-            Configurable.LOCALE_KEY,
-            Configurable.NUMBER_FORMAT_KEY_CAMEL_CASE,
-            Configurable.NUMBER_FORMAT_KEY_SNAKE_CASE,
-            Configurable.OUTPUT_ENCODING_KEY_CAMEL_CASE,
-            Configurable.OUTPUT_ENCODING_KEY_SNAKE_CASE,
-            Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
-            Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY,
-            Configurable.TIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.TIME_ZONE_KEY_CAMEL_CASE,
-            Configurable.TIME_FORMAT_KEY_SNAKE_CASE,
-            Configurable.TIME_ZONE_KEY_SNAKE_CASE,
-            Configurable.URL_ESCAPING_CHARSET_KEY_CAMEL_CASE,
-            Configurable.URL_ESCAPING_CHARSET_KEY_SNAKE_CASE
+            MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.DATE_FORMAT_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.DATE_FORMAT_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.DATETIME_FORMAT_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.DATETIME_FORMAT_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.LOCALE_KEY,
+            MutableProcessingConfiguration.NUMBER_FORMAT_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.NUMBER_FORMAT_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.OUTPUT_ENCODING_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.OUTPUT_ENCODING_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY,
+            MutableProcessingConfiguration.TIME_FORMAT_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.TIME_ZONE_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.TIME_FORMAT_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.TIME_ZONE_KEY_SNAKE_CASE,
+            MutableProcessingConfiguration.URL_ESCAPING_CHARSET_KEY_CAMEL_CASE,
+            MutableProcessingConfiguration.URL_ESCAPING_CHARSET_KEY_SNAKE_CASE
     };
 
     ASTDirSetting(Token keyTk, FMParserTokenManager tokenManager, ASTExpression value, Configuration cfg)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index 64899e9..e75ea9e 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -231,7 +231,7 @@ class BuiltInsForMultipleTypes {
         TemplateModel _eval(Environment env) throws TemplateException {
             if (!env.isAPIBuiltinEnabled()) {
                 throw new _MiscTemplateException(this,
-                        "Can't use ?api, because the \"", Configurable.API_BUILTIN_ENABLED_KEY,
+                        "Can't use ?api, because the \"", MutableProcessingConfiguration.API_BUILTIN_ENABLED_KEY,
                         "\" configuration setting is false. Think twice before you set it to true though. Especially, "
                         + "it shouldn't abussed for modifying Map-s and Collection-s.");
             }


[4/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 256b4c1..0e76d09 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -39,6 +39,7 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
@@ -125,7 +126,7 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
  *       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 #setDefaultEncoding(String) default_encoding}: The default value is system dependent, which makes it
+ *   <li>{@link #setEncoding(String) encoding}: 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
  *       HTML pages, the most convenient value is {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}. For production,
@@ -139,16 +140,17 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
  * 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.
  */
-public class Configuration extends Configurable implements Cloneable, ParserConfiguration {
+public final class Configuration extends MutableProcessingConfiguration<Configuration>
+        implements Cloneable, ParserConfiguration, ProcessingConfiguration, 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 DEFAULT_ENCODING_KEY_SNAKE_CASE = "default_encoding"; 
+    public static final String ENCODING_KEY_SNAKE_CASE = "encoding";
     /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String DEFAULT_ENCODING_KEY_CAMEL_CASE = "defaultEncoding"; 
+    public static final String ENCODING_KEY_CAMEL_CASE = "encoding";
     /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String DEFAULT_ENCODING_KEY = DEFAULT_ENCODING_KEY_SNAKE_CASE;
+    public static final String ENCODING_KEY = 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";
@@ -281,7 +283,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         // Must be sorted alphabetically!
         AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE,
         CACHE_STORAGE_KEY_SNAKE_CASE,
-        DEFAULT_ENCODING_KEY_SNAKE_CASE,
+            ENCODING_KEY_SNAKE_CASE,
         INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
         LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
         NAMING_CONVENTION_KEY_SNAKE_CASE,
@@ -303,7 +305,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         // Must be sorted alphabetically!
         AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE,
         CACHE_STORAGE_KEY_CAMEL_CASE,
-        DEFAULT_ENCODING_KEY_CAMEL_CASE,
+            ENCODING_KEY_CAMEL_CASE,
         INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
         LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
         NAMING_CONVENTION_KEY_CAMEL_CASE,
@@ -430,6 +432,9 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
 
     private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap();
 
+    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 #setSharedVaribles(Map)}
      * and {@link #setObjectWrapper(ObjectWrapper)}. When the user configures FreeMarker from Spring XML, he has no
@@ -437,7 +442,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      */
     private HashMap<String, Object> rewrappableSharedVariables = null;
     
-    private String defaultEncoding = getDefaultDefaultEncoding();
+    private String encoding = getDefaultEncoding();
 
     /**
      * @deprecated Use {@link #Configuration(Version)} instead. Note that the version can be still modified later with
@@ -560,7 +565,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         }
         return new DefaultSoftCacheStorage(); 
     }
-    
+
     private static class DefaultSoftCacheStorage extends SoftCacheStorage {
         // Nothing to override
     }
@@ -1236,6 +1241,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         return whitespaceStripping;
     }
 
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return true;
+    }
+
     /**
      * Sets when auto-escaping should be enabled depending on the current {@linkplain OutputFormat output format};
      * default is {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}. Note that the default output format,
@@ -1312,7 +1322,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public int getAutoEscapingPolicy() {
         return autoEscapingPolicy;
     }
-    
+
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return true;
+    }
+
     /**
      * Sets the default output format. 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"
@@ -1358,7 +1373,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public OutputFormat getOutputFormat() {
         return outputFormat;
     }
-    
+
+    @Override
+    public boolean isOutputFormatSet() {
+        return true;
+    }
+
     /**
      * Tells if {@link #setOutputFormat(OutputFormat)} (or equivalent) was already called on this instance.
      * 
@@ -1613,6 +1633,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                 : recognizeStandardFileExtensions.booleanValue();
     }
 
+    @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return true;
+    }
+
     /**
      * Getter pair of {@link #setTemplateLanguage(TemplateLanguage)}.
      */
@@ -1677,6 +1702,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         return tagSyntax;
     }
 
+    @Override
+    public boolean isTagSyntaxSet() {
+        return true;
+    }
+
     /**
      * Sets 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
@@ -1747,7 +1777,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public int getNamingConvention() {
         return namingConvention;
     }
-    
+
+    @Override
+    public boolean isNamingConventionSet() {
+        return true;
+    }
+
     /**
      * Sets 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
@@ -1780,6 +1815,31 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     public int getTabSize() {
         return tabSize;
     }
+
+    @Override
+    public boolean isTabSizeSet() {
+        return true;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getCustomState(CustomStateKey<T> customStateKey) {
+        T customState = (T) customStateMap.get(customStateKey);
+        if (customState == null) {
+            synchronized (customStateMapLock) {
+                customState = (T) customStateMap.get(customStateKey);
+                if (customState == null) {
+                    customState = customStateKey.create();
+                    if (customState == null) {
+                        throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
+                                + customStateKey + ")");
+                    }
+                    customStateMap.put(customStateKey, customState);
+                }
+            }
+        }
+        return customState;
+    }
     
     /**
      * Retrieves the template with the given name from the template templateResolver, loading it into the templateResolver first if it's
@@ -1982,18 +2042,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *
      * @param encoding The name of the charset, such as {@code "UTF-8"} or {@code "ISO-8859-1"}
      */
-    public void setDefaultEncoding(String encoding) {
-        defaultEncoding = encoding;
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
         defaultEncodingExplicitlySet = true;
     }
 
-    /**
-     * 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.
-     */
-    public String getDefaultEncoding() {
-        return defaultEncoding;
+    @Override
+    public String getEncoding() {
+        return encoding;
     }
 
     /**
@@ -2003,13 +2059,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      */
     public void unsetDefaultEncoding() {
         if (defaultEncodingExplicitlySet) {
-            setDefaultEncoding(getDefaultDefaultEncoding());
+            setEncoding(getDefaultEncoding());
             defaultEncodingExplicitlySet = false;
         }
     }
 
     /**
-     * Tells if {@link #setDefaultEncoding(String)} (or equivalent) was already called on this instance, or it just holds the
+     * Tells if {@link #setEncoding(String)} (or equivalent) was already called on this instance, or it just holds the
      * default value.
      *
      * @since 2.3.26
@@ -2018,7 +2074,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         return defaultEncodingExplicitlySet;
     }
 
-    static private String getDefaultDefaultEncoding() {
+    static private String getDefaultEncoding() {
         return getJVMDefaultEncoding();
     }
 
@@ -2063,7 +2119,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
     
     /**
-     * Adds shared variable to the configuration; It uses {@link Configurable#getObjectWrapper()} to wrap the 
+     * Adds shared variable to the configuration; It uses {@link MutableProcessingConfiguration#getObjectWrapper()} to wrap the
      * {@code value}, so it's important that the object wrapper is set before this.
      * 
      * <p>This method is <b>not</b> thread safe; use it with the same restrictions as those that modify setting values.
@@ -2245,14 +2301,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
             if ("TemplateUpdateInterval".equalsIgnoreCase(name)) {
                 name = TEMPLATE_UPDATE_DELAY_KEY;
             } else if ("DefaultEncoding".equalsIgnoreCase(name)) {
-                name = DEFAULT_ENCODING_KEY;
+                name = ENCODING_KEY;
             }
             
-            if (DEFAULT_ENCODING_KEY_SNAKE_CASE.equals(name) || DEFAULT_ENCODING_KEY_CAMEL_CASE.equals(name)) {
-                if (JVM_DEFAULT.equalsIgnoreCase(value)) {
-                    setDefaultEncoding(getJVMDefaultEncoding());
+            if (ENCODING_KEY_SNAKE_CASE.equals(name) || ENCODING_KEY_CAMEL_CASE.equals(name)) {
+                if (JVM_DEFAULT_VALUE.equalsIgnoreCase(value)) {
+                    setEncoding(getJVMDefaultEncoding());
                 } else {
-                    setDefaultEncoding(value);
+                    setEncoding(value);
                 }
             } else if (LOCALIZED_LOOKUP_KEY_SNAKE_CASE.equals(name) || LOCALIZED_LOOKUP_KEY_CAMEL_CASE.equals(name)) {
                 setLocalizedLookup(_StringUtil.getYesNo(value));
@@ -2270,7 +2326,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                     throw invalidSettingValueException(name, value);
                 }
             } else if (OUTPUT_FORMAT_KEY_SNAKE_CASE.equals(name) || OUTPUT_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equalsIgnoreCase(DEFAULT)) {
+                if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
                     unsetOutputFormat();
                 } else {
                     setOutputFormat((OutputFormat) _ObjectBuilderSettingEvaluator.eval(
@@ -2290,13 +2346,13 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                 setRegisteredCustomOutputFormats(list);
             } else if (RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE.equals(name)
                     || RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equalsIgnoreCase(DEFAULT)) {
+                if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
                     unsetRecognizeStandardFileExtensions();
                 } else {
                     setRecognizeStandardFileExtensions(_StringUtil.getYesNo(value));
                 }
             } else if (CACHE_STORAGE_KEY_SNAKE_CASE.equals(name) || CACHE_STORAGE_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equalsIgnoreCase(DEFAULT)) {
+                if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
                     unsetCacheStorage();
                 } if (value.indexOf('.') == -1) {
                     int strongSize = 0;
@@ -2397,7 +2453,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                     || INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE.equals(name)) {
                 setIncompatibleImprovements(new Version(value));
             } else if (TEMPLATE_LOADER_KEY_SNAKE_CASE.equals(name) || TEMPLATE_LOADER_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equalsIgnoreCase(DEFAULT)) {
+                if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
                     unsetTemplateLoader();
                 } else {
                     setTemplateLoader((TemplateLoader) _ObjectBuilderSettingEvaluator.eval(
@@ -2405,7 +2461,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                 }
             } else if (TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE.equals(name)
                     || TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equalsIgnoreCase(DEFAULT)) {
+                if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
                     unsetTemplateLookupStrategy();
                 } else {
                     setTemplateLookupStrategy((TemplateLookupStrategy) _ObjectBuilderSettingEvaluator.eval(
@@ -2413,7 +2469,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                 }
             } else if (TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE.equals(name)
                     || TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equalsIgnoreCase(DEFAULT)) {
+                if (value.equalsIgnoreCase(DEFAULT_VALUE)) {
                     unsetTemplateNameFormat();
                 } else if (value.equalsIgnoreCase("default_2_3_0")) {
                     setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
@@ -2424,7 +2480,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
                 }
             } else if (TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE.equals(name)
                     || TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE.equals(name)) {
-                if (value.equals(NULL)) {
+                if (value.equals(NULL_VALUE)) {
                     setTemplateConfigurations(null);
                 } else {
                     setTemplateConfigurations((TemplateConfigurationFactory) _ObjectBuilderSettingEvaluator.eval(
@@ -2450,14 +2506,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
 
     /**
-     * Returns the valid {@link Configuration} setting names. Naturally, this includes the {@link Configurable} setting
+     * Returns the valid {@link Configuration} setting names. Naturally, this includes the {@link MutableProcessingConfiguration} setting
      * names too.
      * 
      * @param camelCase
      *            If we want the setting names with camel case naming convention, or with snake case (legacy) naming
      *            convention.
      * 
-     * @see Configurable#getSettingNames(boolean)
+     * @see MutableProcessingConfiguration#getSettingNames(boolean)
      * 
      * @since 2.3.24
      */
@@ -2480,10 +2536,10 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     protected String getCorrectedNameForUnknownSetting(String name) {
         if ("encoding".equals(name) || "charset".equals(name) || "default_charset".equals(name)) {
             // [2.4] Default might changes to camel-case
-            return DEFAULT_ENCODING_KEY;
+            return ENCODING_KEY;
         }
         if ("defaultCharset".equals(name)) {
-            return DEFAULT_ENCODING_KEY_CAMEL_CASE;
+            return ENCODING_KEY_CAMEL_CASE;
         }
         if (name.equals("incompatible_enhancements")) {
             return INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE;
@@ -2503,13 +2559,12 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
 
     private void doAutoImports(Environment env, Template t) throws IOException, TemplateException {
-        Map<String, String> envAutoImports = env.getAutoImportsWithoutFallback();
-        Map<String, String> tAutoImports = t.getAutoImportsWithoutFallback();
+        Map<String, String> envAutoImports = env.isAutoImportsSet() ? env.getAutoImports() : null;
+        Map<String, String> tAutoImports = t.isAutoImportsSet() ? t.getAutoImports() : null;
         
-        boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports().booleanValue()
-                : env.getLazyImports();
+        boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports() : env.getLazyImports();
         
-        for (Map.Entry<String, String> autoImport : getAutoImportsWithoutFallback().entrySet()) {
+        for (Map.Entry<String, String> autoImport : getAutoImports().entrySet()) {
             String nsVarName = autoImport.getKey();
             if ((tAutoImports == null || !tAutoImports.containsKey(nsVarName))
                     && (envAutoImports == null || !envAutoImports.containsKey(nsVarName))) {
@@ -2536,11 +2591,11 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
         // We can't store autoIncludes in LinkedHashSet-s because setAutoIncludes(List) allows duplicates,
         // unfortunately. Yet we have to prevent duplicates among Configuration levels, with the lowest levels having
         // priority. So we build some Set-s to do that, but we avoid the most common cases where they aren't needed.
+
+        List<String> tAutoIncludes = t.isAutoIncludesSet() ? t.getAutoIncludes() : null;
+        List<String> envAutoIncludes = env.isAutoIncludesSet() ? env.getAutoIncludes() : null;
         
-        List<String> tAutoIncludes = t.getAutoIncludesWithoutFallback();
-        List<String> envAutoIncludes = env.getAutoIncludesWithoutFallback();
-        
-        for (String templateName : getAutoIncludesWithoutFallback()) {
+        for (String templateName : getAutoIncludes()) {
             if ((tAutoIncludes == null || !tAutoIncludes.contains(templateName))
                     && (envAutoIncludes == null || !envAutoIncludes.contains(templateName))) {
                 env.include(getTemplate(templateName, env.getLocale()));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/CustomAttribute.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomAttribute.java b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
deleted file mode 100644
index 37d7db9..0000000
--- a/src/main/java/org/apache/freemarker/core/CustomAttribute.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import org.apache.freemarker.core.util.BugException;
-
-/**
- * A class that allows one to associate custom data with a {@link Configuration}, a {@link Template}, or
- * {@link Environment}.
- * 
- * <p>This API has similar approach to that of {@link ThreadLocal} (which allows one to associate
- * custom data with a thread). With an example:</p>
- * 
- * <pre>
- * // The object identity itself will serve as the attribute identifier; there's no attribute name String:
- * public static final CustomAttribute MY_ATTR = new CustomAttribute(CustomAttribute.SCOPE_CONFIGURATION);
- * ...
- *     // Set the attribute in this particular Configuration object:
- *     MY_ATTR.set(myAttrValue, cfg);
- *     ...
- *     // Read the attribute from this particular Configuration object:
- *     myAttrValue = MY_ATTR.get(cfg);
- * </pre>
- */
-// [2.4] Use generics; type parameter used for the type of the stored value 
-public class CustomAttribute {
-    
-    /**
-     * Constant used in the constructor specifying that this attribute is {@link Environment}-scoped.
-     */
-    public static final int SCOPE_ENVIRONMENT = 0;
-        
-    /**
-     * Constant used in the constructor specifying that this attribute is {@link Template}-scoped.
-     */
-    public static final int SCOPE_TEMPLATE = 1;
-        
-    /**
-     * Constant used in the constructor specifying that this attribute is {@link Configuration}-scoped.
-     */
-    public static final int SCOPE_CONFIGURATION = 2;
-
-    // We use an internal key instead of 'this' so that malicious subclasses 
-    // overriding equals() and hashCode() can't gain access to other attribute
-    // values. That's also the reason why get() and set() are marked final.
-    private final Object key = new Object();
-    private final int scope;
-    
-    /**
-     * Creates a new custom attribute with the specified scope
-     * @param scope one of <tt>SCOPE_</tt> constants. 
-     */
-    public CustomAttribute(int scope) {
-        if (scope != SCOPE_ENVIRONMENT && 
-           scope != SCOPE_TEMPLATE && 
-           scope != SCOPE_CONFIGURATION) {
-                throw new IllegalArgumentException();
-            }
-        this.scope = scope;
-    }
-    
-    /**
-     * This method is invoked when {@link #get()} is invoked without 
-     * {@link #set(Object)} being invoked before it to define the value in the 
-     * current scope. Override it to invoke the attribute value on-demand.
-     * @return the initial value for the custom attribute. By default returns null.
-     */
-    protected Object create() {
-        return null;
-    }
-    
-    /**
-     * Gets the attribute from the appropriate scope that's accessible through the specified {@link Environment}. If
-     * the attribute has {@link #SCOPE_ENVIRONMENT} scope, it will be get from the given {@link Environment} directly.
-     * If the attribute has {@link #SCOPE_TEMPLATE} scope, it will be get from the parent of the given
-     * {@link Environment} (that is, in {@link Environment#getParent()}) directly). If the attribute has
-     * {@link #SCOPE_CONFIGURATION} scope, it will be get from {@link Environment#getConfiguration()}.
-     * 
-     * @throws NullPointerException
-     *             If {@code env} is null
-     * 
-     * @return The new value of the attribute (possibly {@code null}), or {@code null} if the attribute doesn't exist.
-     * 
-     * @since 2.3.22
-     */
-    public final Object get(Environment env) {
-        return getScopeConfigurable(env).getCustomAttribute(key, this);
-    }
-
-    /**
-     * Same as {@link #get(Environment)}, but uses {@link Environment#getCurrentEnvironment()} to fill the 2nd argument.
-     * 
-     * @throws IllegalStateException
-     *             If there is no current {@link Environment}, which is usually the case when the current thread isn't
-     *             processing a template.
-     */
-    public final Object get() {
-        return getScopeConfigurable(getRequiredCurrentEnvironment()).getCustomAttribute(key, this);
-    }
-    
-    /**
-     * Gets the value of a {@link Template}-scope attribute from the given {@link Template}.
-     * 
-     * @throws UnsupportedOperationException
-     *             If this custom attribute has different scope than {@link #SCOPE_TEMPLATE}.
-     * @throws NullPointerException
-     *             If {@code template} is null
-     */
-    public final Object get(Template template) {
-        if (scope != SCOPE_TEMPLATE) {
-            throw new UnsupportedOperationException("This is not a template-scope attribute");
-        }
-        return template.getCustomAttribute(key, this);
-    }
-    
-    /**
-     * Same as {@link #get(Template)}, but applies to a {@link TemplateConfiguration}.  
-     * 
-     * @since 2.3.24
-     */
-    public Object get(TemplateConfiguration templateConfiguration) {
-        if (scope != SCOPE_TEMPLATE) {
-            throw new UnsupportedOperationException("This is not a template-scope attribute");
-        }
-        return templateConfiguration.getCustomAttribute(key, this);
-    }
-    
-    /**
-     * Gets the value of a {@link Configuration}-scope attribute from the given {@link Configuration}.
-     * 
-     * @throws UnsupportedOperationException
-     *             If this custom attribute has different scope than {@link #SCOPE_CONFIGURATION}.
-     * @throws NullPointerException
-     *             If {@code cfg} is null
-     * 
-     * @since 2.3.22
-     */
-    public final Object get(Configuration cfg) {
-        if (scope != SCOPE_CONFIGURATION) {
-            throw new UnsupportedOperationException("This is not a template-scope attribute");
-        }
-        return cfg.getCustomAttribute(key, this);
-    }
-    
-    /**
-     * Sets the attribute inside the appropriate scope that's accessible through the specified {@link Environment}. If
-     * the attribute has {@link #SCOPE_ENVIRONMENT} scope, it will be set in the given {@link Environment} directly. If
-     * the attribute has {@link #SCOPE_TEMPLATE} scope, it will be set in the parent of the given {@link Environment}
-     * (that is, in {@link Environment#getParent()}) directly). If the attribute has {@link #SCOPE_CONFIGURATION} scope,
-     * it will be set in {@link Environment#getConfiguration()}.
-     * 
-     * @param value
-     *            The new value of the attribute. Can be {@code null}.
-     * 
-     * @throws NullPointerException
-     *             If {@code env} is null
-     * 
-     * @since 2.3.22
-     */
-    public final void set(Object value, Environment env) {
-        getScopeConfigurable(env).setCustomAttribute(key, value);
-    }
-
-    /**
-     * Same as {@link #set(Object, Environment)}, but uses {@link Environment#getCurrentEnvironment()} to fill the 2nd
-     * argument.
-     * 
-     * @throws IllegalStateException
-     *             If there is no current {@link Environment}, which is usually the case when the current thread isn't
-     *             processing a template.
-     */
-    public final void set(Object value) {
-        getScopeConfigurable(getRequiredCurrentEnvironment()).setCustomAttribute(key, value);
-    }
-
-    /**
-     * Sets the value of a {@link Template}-scope attribute in the given {@link Template}.
-     * 
-     * @param value
-     *            The new value of the attribute. Can be {@code null}.
-     * 
-     * @throws UnsupportedOperationException
-     *             If this custom attribute has different scope than {@link #SCOPE_TEMPLATE}.
-     * @throws NullPointerException
-     *             If {@code template} is null
-     */
-    public final void set(Object value, Template template) {
-        if (scope != SCOPE_TEMPLATE) {
-            throw new UnsupportedOperationException("This is not a template-scope attribute");
-        }
-        template.setCustomAttribute(key, value);
-    }
-
-    /**
-     * Same as {@link #set(Object, Template)}, but applicable to a {@link TemplateConfiguration}. 
-     * 
-     * @since 2.3.24
-     */
-    public final void set(Object value, TemplateConfiguration templateConfiguration) {
-        if (scope != SCOPE_TEMPLATE) {
-            throw new UnsupportedOperationException("This is not a template-scope attribute");
-        }
-        templateConfiguration.setCustomAttribute(key, value);
-    }
-    
-    /**
-     * Sets the value of a {@link Configuration}-scope attribute in the given {@link Configuration}.
-     * 
-     * @param value
-     *            The new value of the attribute. Can be {@code null}.
-     * 
-     * @throws UnsupportedOperationException
-     *             If this custom attribute has different scope than {@link #SCOPE_CONFIGURATION}.
-     * @throws NullPointerException
-     *             If {@code cfg} is null
-     * 
-     * @since 2.3.22
-     */
-    public final void set(Object value, Configuration cfg) {
-        if (scope != SCOPE_CONFIGURATION) {
-            throw new UnsupportedOperationException("This is not a configuration-scope attribute");
-        }
-        cfg.setCustomAttribute(key, value);
-    }
-    
-    private Environment getRequiredCurrentEnvironment() {
-        Environment c = Environment.getCurrentEnvironment();
-        if (c == null) {
-            throw new IllegalStateException("No current environment");
-        }
-        return c;
-    }
-
-    private Configurable getScopeConfigurable(Environment env) throws Error {
-        switch (scope) {
-        case SCOPE_ENVIRONMENT:
-            return env;
-        case SCOPE_TEMPLATE:
-            return env.getParent();
-        case SCOPE_CONFIGURATION:
-            return env.getParent().getParent();
-        default:
-            throw new BugException();
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/CustomStateKey.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomStateKey.java b/src/main/java/org/apache/freemarker/core/CustomStateKey.java
new file mode 100644
index 0000000..fd6f4d5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CustomStateKey.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/**
+ * Used with {@link CustomStateScope}-s; each subclass must have exactly one instance, which should be stored in
+ * a static final field. So the usual usage is like this:
+ *
+ * <pre>
+ *     static final CustomStateKey MY_STATE = new CustomStateKey() {
+ *         @Override
+ *         protected Object create() {
+ *             return new ...;
+ *         }
+ *     };
+ * </pre>
+ */
+public abstract class CustomStateKey<T> {
+
+    /**
+     * This will be invoked when the state for this {@link CustomStateKey} is get via {@link
+     * CustomStateScope#getCustomState(CustomStateKey)}, but it doesn't yet exists in the given scope. Then the created
+     * object will be stored in the scope and then it's returned. Must not return {@code null}.
+     */
+    protected abstract T create();
+
+    /**
+     * Does identity comparison (like operator {@code ==}).
+     */
+    @Override
+    final public boolean equals(Object o) {
+        return o == this;
+    }
+
+    /**
+     * Returns {@link Object#hashCode()}.
+     */
+    @Override
+    final public int hashCode() {
+        return super.hashCode();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/CustomStateScope.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomStateScope.java b/src/main/java/org/apache/freemarker/core/CustomStateScope.java
new file mode 100644
index 0000000..4067823
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CustomStateScope.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * An object that's a scope that can store custom state objects.
+ */
+public interface CustomStateScope {
+
+    /**
+     * Gets the custom state belonging to the key, automatically creating it if it doesn't yet exists in the scope.
+     * If the scope is {@link Configuration} or {@link Template}, then this method is thread safe. If the scope is
+     * {@link Environment}, then this method is not thread safe ({@link Environment} is not thread safe either).
+     */
+    <T> T getCustomState(CustomStateKey<T> customStateKey);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/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 7eac791..ce95b15 100644
--- a/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/src/main/java/org/apache/freemarker/core/Environment.java
@@ -99,9 +99,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  * If you need to modify or read this object before or after the <tt>process</tt> call, use
  * {@link Template#createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)}
  */
-public final class Environment extends Configurable {
+public final class Environment extends MutableProcessingConfiguration<Environment> implements CustomStateScope {
     
-    private static final ThreadLocal threadEnv = new ThreadLocal();
+    private static final ThreadLocal<Environment> TLS_ENVIRONMENT = new ThreadLocal();
 
     private static final Logger LOG = _CoreLogs.RUNTIME;
     private static final Logger LOG_ATTEMPT = _CoreLogs.ATTEMPT;
@@ -125,6 +125,7 @@ public final class Environment extends Configurable {
 
     private TemplateNumberFormat cachedTemplateNumberFormat;
     private Map<String, TemplateNumberFormat> cachedTemplateNumberFormats;
+    private Map<CustomStateKey, Object> customStateMap;
 
     /**
      * Stores the date/time/date-time formatters that are used when no format is explicitly given at the place of
@@ -196,11 +197,19 @@ public final class Environment extends Configurable {
      * current thread.
      */
     public static Environment getCurrentEnvironment() {
-        return (Environment) threadEnv.get();
+        return TLS_ENVIRONMENT.get();
+    }
+
+    public static Environment getCurrentEnvironmentNotNull() {
+        Environment currentEnvironment = getCurrentEnvironment();
+        if (currentEnvironment == null) {
+            throw new IllegalStateException("There's no FreeMarker Environemnt in this this thread.");
+        }
+        return currentEnvironment;
     }
 
     static void setCurrentEnvironment(Environment env) {
-        threadEnv.set(env);
+        TLS_ENVIRONMENT.set(env);
     }
 
     public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) {
@@ -242,6 +251,14 @@ public final class Environment extends Configurable {
         return ln == 0 ? getMainTemplate() : instructionStack[ln - 1].getTemplate();
     }
 
+    public Template getCurrentTemplateNotNull() {
+        Template currentTemplate = getCurrentTemplate();
+        if (currentTemplate == null) {
+            throw new IllegalStateException("There's no current template at the moment.");
+        }
+        return currentTemplate;
+    }
+
     /**
      * Gets the currently executing <em>custom</em> directive's call place information, or {@code null} if there's no
      * executing custom directive. This currently only works for calls made from templates with the {@code <@...>}
@@ -281,8 +298,8 @@ public final class Environment extends Configurable {
      * Processes the template to which this environment belongs to.
      */
     public void process() throws TemplateException, IOException {
-        Object savedEnv = threadEnv.get();
-        threadEnv.set(this);
+        Environment savedEnv = TLS_ENVIRONMENT.get();
+        TLS_ENVIRONMENT.set(this);
         try {
             // Cached values from a previous execution are possibly outdated.
             clearCachedValues();
@@ -298,7 +315,7 @@ public final class Environment extends Configurable {
                 clearCachedValues();
             }
         } finally {
-            threadEnv.set(savedEnv);
+            TLS_ENVIRONMENT.set(savedEnv);
         }
     }
 
@@ -1534,15 +1551,15 @@ public final class Environment extends Configurable {
             String settingValue;
             switch (dateType) {
             case TemplateDateModel.TIME:
-                settingName = Configurable.TIME_FORMAT_KEY;
+                settingName = MutableProcessingConfiguration.TIME_FORMAT_KEY;
                 settingValue = getTimeFormat();
                 break;
             case TemplateDateModel.DATE:
-                settingName = Configurable.DATE_FORMAT_KEY;
+                settingName = MutableProcessingConfiguration.DATE_FORMAT_KEY;
                 settingValue = getDateFormat();
                 break;
             case TemplateDateModel.DATETIME:
-                settingName = Configurable.DATETIME_FORMAT_KEY;
+                settingName = MutableProcessingConfiguration.DATETIME_FORMAT_KEY;
                 settingValue = getDateTimeFormat();
                 break;
             default:
@@ -2647,44 +2664,22 @@ public final class Environment extends Configurable {
         return currentNamespace.getTemplate().getDefaultNS();
     }
 
-    private IdentityHashMap<Object, Object> customStateVariables;
-
-    /**
-     * Returns the value of a custom state variable, or {@code null} if it's missing; see
-     * {@link #setCustomState(Object, Object)} for more.
-     * 
-     * @since 2.3.24
-     */
-    public Object getCustomState(Object identityKey) {
-        if (customStateVariables == null) {
-            return null;
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getCustomState(CustomStateKey<T> customStateKey) {
+        if (customStateMap == null) {
+            customStateMap = new IdentityHashMap<>();
         }
-        return customStateVariables.get(identityKey);
-    }
-
-    /**
-     * Sets the value of a custom state variable. Custom state variables meant to be used by
-     * {@link TemplateNumberFormatFactory}-es, {@link TemplateDateFormatFactory}-es, and similar user-implementable,
-     * pluggable objects, which want to maintain an {@link Environment}-scoped state (such as a cache).
-     * 
-     * @param identityKey
-     *            The key that identifies the variable, by its object identity (not by {@link Object#equals(Object)}).
-     *            This should be something like a {@code private static final Object CUSTOM_STATE_KEY = new Object();}
-     *            in the class that needs this state variable.
-     * @param value
-     *            The value of the variable. Can be anything, even {@code null}.
-     * 
-     * @return The previous value of the variable, or {@code null} if the variable didn't exist.
-     * 
-     * @since 2.3.24
-     */
-    public Object setCustomState(Object identityKey, Object value) {
-        IdentityHashMap<Object, Object> customStateVariables = this.customStateVariables;
-        if (customStateVariables == null) {
-            customStateVariables = new IdentityHashMap<>();
-            this.customStateVariables = customStateVariables;
+        T customState = (T) customStateMap.get(customStateKey);
+        if (customState == null) {
+            customState = customStateKey.create();
+            if (customState == null) {
+                throw new IllegalStateException("CustomStateKey.create() must not return null (for key: "
+                        + customStateKey + ")");
+            }
+            customStateMap.put(customStateKey, customState);
         }
-        return customStateVariables.put(identityKey, value);
+        return customState;
     }
 
     final class NestedElementTemplateDirectiveBody implements TemplateDirectiveBody {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
new file mode 100644
index 0000000..2324dce
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingAndParseConfiguration.java
@@ -0,0 +1,282 @@
+/*
+ * 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.io.InputStream;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+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 MutableProcessingAndParseConfiguration<
+        SelfT extends MutableProcessingAndParseConfiguration<SelfT>>
+        extends MutableProcessingConfiguration<SelfT>
+        implements ParserConfiguration {
+
+    private TemplateLanguage templateLanguage;
+    private Integer tagSyntax;
+    private Integer namingConvention;
+    private Boolean whitespaceStripping;
+    private Integer autoEscapingPolicy;
+    private Boolean recognizeStandardFileExtensions;
+    private OutputFormat outputFormat;
+    private String encoding;
+    private Integer tabSize;
+
+    protected MutableProcessingAndParseConfiguration(Version incompatibleImprovements) {
+        super(incompatibleImprovements);
+    }
+
+    protected MutableProcessingAndParseConfiguration(MutableProcessingConfiguration parent) {
+        super(parent);
+    }
+
+    /**
+     * See {@link Configuration#setTagSyntax(int)}.
+     */
+    public void setTagSyntax(int tagSyntax) {
+        Configuration.valideTagSyntaxValue(tagSyntax);
+        this.tagSyntax = tagSyntax;
+    }
+
+    /**
+     * The getter pair of {@link #setTagSyntax(int)}.
+     */
+    @Override
+    public int getTagSyntax() {
+        return tagSyntax != null ? tagSyntax : getDefaultTagSyntax();
+    }
+
+    protected abstract int getDefaultTagSyntax();
+
+    @Override
+    public boolean isTagSyntaxSet() {
+        return tagSyntax != null;
+    }
+
+    /**
+     * See {@link Configuration#getTemplateLanguage()}
+     */
+    @Override
+    public TemplateLanguage getTemplateLanguage() {
+        return templateLanguage != null ? templateLanguage : getDefaultTemplateLanguage();
+    }
+
+    protected abstract TemplateLanguage getDefaultTemplateLanguage();
+
+    /**
+     * See {@link Configuration#setTemplateLanguage(TemplateLanguage)}
+     */
+    public void setTemplateLanguage(TemplateLanguage templateLanguage) {
+        _NullArgumentException.check("templateLanguage", templateLanguage);
+        this.templateLanguage = templateLanguage;
+    }
+
+    public boolean isTemplateLanguageSet() {
+        return templateLanguage != null;
+    }
+
+    /**
+     * See {@link Configuration#setNamingConvention(int)}.
+     */
+    public void setNamingConvention(int namingConvention) {
+        Configuration.validateNamingConventionValue(namingConvention);
+        this.namingConvention = namingConvention;
+    }
+
+    /**
+     * The getter pair of {@link #setNamingConvention(int)}.
+     */
+    @Override
+    public int getNamingConvention() {
+        return namingConvention != null ? namingConvention
+                : getDefaultNamingConvention();
+    }
+
+    protected abstract int getDefaultNamingConvention();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     */
+    @Override
+    public boolean isNamingConventionSet() {
+        return namingConvention != null;
+    }
+
+    /**
+     * See {@link Configuration#setWhitespaceStripping(boolean)}.
+     */
+    public void setWhitespaceStripping(boolean whitespaceStripping) {
+        this.whitespaceStripping = Boolean.valueOf(whitespaceStripping);
+    }
+
+    /**
+     * The getter pair of {@link #getWhitespaceStripping()}.
+     */
+    @Override
+    public boolean getWhitespaceStripping() {
+        return whitespaceStripping != null ? whitespaceStripping.booleanValue()
+                : getDefaultWhitespaceStripping();
+    }
+
+    protected abstract boolean getDefaultWhitespaceStripping();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     */
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return whitespaceStripping != null;
+    }
+
+    /**
+     * Sets the output format of the template; see {@link Configuration#setAutoEscapingPolicy(int)} for more.
+     */
+    public void setAutoEscapingPolicy(int autoEscapingPolicy) {
+        Configuration.validateAutoEscapingPolicyValue(autoEscapingPolicy);
+
+        this.autoEscapingPolicy = Integer.valueOf(autoEscapingPolicy);
+    }
+
+    /**
+     * The getter pair of {@link #setAutoEscapingPolicy(int)}.
+     */
+    @Override
+    public int getAutoEscapingPolicy() {
+        return autoEscapingPolicy != null ? autoEscapingPolicy.intValue()
+                : getDefaultAutoEscapingPolicy();
+    }
+
+    protected abstract int getDefaultAutoEscapingPolicy();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     */
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return autoEscapingPolicy != null;
+    }
+
+    /**
+     * Sets the output format of the template; see {@link Configuration#setOutputFormat(OutputFormat)} for more.
+     */
+    public void setOutputFormat(OutputFormat outputFormat) {
+        _NullArgumentException.check("outputFormat", outputFormat);
+        this.outputFormat = outputFormat;
+    }
+
+    /**
+     * The getter pair of {@link #setOutputFormat(OutputFormat)}.
+     */
+    @Override
+    public OutputFormat getOutputFormat() {
+        return outputFormat != null ? outputFormat : getDefaultOutputFormat();
+    }
+
+    protected abstract OutputFormat getDefaultOutputFormat();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     */
+    @Override
+    public boolean isOutputFormatSet() {
+        return outputFormat != null;
+    }
+
+    /**
+     * See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}.
+     */
+    public void setRecognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
+        this.recognizeStandardFileExtensions = Boolean.valueOf(recognizeStandardFileExtensions);
+    }
+
+    /**
+     * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
+     */
+    @Override
+    public boolean getRecognizeStandardFileExtensions() {
+        return recognizeStandardFileExtensions != null ? recognizeStandardFileExtensions.booleanValue()
+                : getDefaultRecognizeStandardFileExtensions();
+    }
+
+    protected abstract boolean getDefaultRecognizeStandardFileExtensions();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     */
+    @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return recognizeStandardFileExtensions != null;
+    }
+
+    @Override
+    public String getEncoding() {
+        return encoding != null ? encoding : getDefaultEncoding();
+    }
+
+    protected abstract String getDefaultEncoding();
+
+    /**
+     * The charset to be used when reading the template "file" that the {@link TemplateLoader} returns as binary
+     * ({@link InputStream}). If the {@code #ftl} header sepcifies an encoding, that will override this.
+     */
+    public void setEncoding(String encoding) {
+        _NullArgumentException.check("encoding", encoding);
+        this.encoding = encoding;
+    }
+
+    public boolean isEncodingSet() {
+        return encoding != null;
+    }
+
+    /**
+     * See {@link Configuration#setTabSize(int)}.
+     *
+     * @since 2.3.25
+     */
+    public void setTabSize(int tabSize) {
+        this.tabSize = Integer.valueOf(tabSize);
+    }
+
+    /**
+     * Getter pair of {@link #setTabSize(int)}.
+     *
+     * @since 2.3.25
+     */
+    @Override
+    public int getTabSize() {
+        return tabSize != null ? tabSize.intValue()
+                : getDefailtTabSize();
+    }
+
+    protected abstract int getDefailtTabSize();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.25
+     */
+    @Override
+    public boolean isTabSizeSet() {
+        return tabSize != null;
+    }
+
+}


[5/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configurable.java b/src/main/java/org/apache/freemarker/core/Configurable.java
deleted file mode 100644
index b6c73eb..0000000
--- a/src/main/java/org/apache/freemarker/core/Configurable.java
+++ /dev/null
@@ -1,2741 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.text.NumberFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TimeZone;
-
-import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
-import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
-import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
-import org.apache.freemarker.core.outputformat.OutputFormat;
-import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
-import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
-import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
-import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
-import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
-import org.apache.freemarker.core.templateresolver.AndMatcher;
-import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
-import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
-import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
-import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
-import org.apache.freemarker.core.templateresolver.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;
-import org.apache.freemarker.core.util.GenericParseException;
-import org.apache.freemarker.core.util.OptInTemplateClassResolver;
-import org.apache.freemarker.core.util._ClassUtil;
-import org.apache.freemarker.core.util._NullArgumentException;
-import org.apache.freemarker.core.util._SortedArraySet;
-import org.apache.freemarker.core.util._StringUtil;
-import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
-import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
-import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
-
-/**
- * This is a common superclass of {@link org.apache.freemarker.core.Configuration},
- * {@link org.apache.freemarker.core.Template}, and {@link Environment} classes.
- * It provides settings that are common to each of them. FreeMarker
- * uses a three-level setting hierarchy - the return value of every setting
- * getter method on <code>Configurable</code> objects inherits its value from its parent 
- * <code>Configurable</code> object, unless explicitly overridden by a call to a 
- * corresponding setter method on the object itself. The parent of an 
- * <code>Environment</code> object is a <code>Template</code> object, the
- * parent of a <code>Template</code> object is a <code>Configuration</code>
- * object.
- */
-public class Configurable {
-    static final String C_TRUE_FALSE = "true,false";
-    
-    static final String NULL = "null";
-    static final String DEFAULT = "default";
-    static final String JVM_DEFAULT = "JVM default";
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String LOCALE_KEY_SNAKE_CASE = "locale";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String LOCALE_KEY_CAMEL_CASE = "locale";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String LOCALE_KEY = LOCALE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String NUMBER_FORMAT_KEY_SNAKE_CASE = "number_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String NUMBER_FORMAT_KEY_CAMEL_CASE = "numberFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String NUMBER_FORMAT_KEY = NUMBER_FORMAT_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE = "custom_number_formats";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE = "customNumberFormats";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String CUSTOM_NUMBER_FORMATS_KEY = CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TIME_FORMAT_KEY_SNAKE_CASE = "time_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TIME_FORMAT_KEY_CAMEL_CASE = "timeFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TIME_FORMAT_KEY = TIME_FORMAT_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String DATE_FORMAT_KEY_SNAKE_CASE = "date_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String DATE_FORMAT_KEY_CAMEL_CASE = "dateFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String DATE_FORMAT_KEY = DATE_FORMAT_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE = "custom_date_formats";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE = "customDateFormats";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String CUSTOM_DATE_FORMATS_KEY = CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String DATETIME_FORMAT_KEY_SNAKE_CASE = "datetime_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String DATETIME_FORMAT_KEY_CAMEL_CASE = "datetimeFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String DATETIME_FORMAT_KEY = DATETIME_FORMAT_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TIME_ZONE_KEY_SNAKE_CASE = "time_zone";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TIME_ZONE_KEY_CAMEL_CASE = "timeZone";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TIME_ZONE_KEY = TIME_ZONE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE = "sql_date_and_time_time_zone";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE = "sqlDateAndTimeTimeZone";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY = SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE = "template_exception_handler";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE = "templateExceptionHandler";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY = TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String ARITHMETIC_ENGINE_KEY_SNAKE_CASE = "arithmetic_engine";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String ARITHMETIC_ENGINE_KEY_CAMEL_CASE = "arithmeticEngine";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String ARITHMETIC_ENGINE_KEY = ARITHMETIC_ENGINE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String OBJECT_WRAPPER_KEY_SNAKE_CASE = "object_wrapper";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String OBJECT_WRAPPER_KEY_CAMEL_CASE = "objectWrapper";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String OBJECT_WRAPPER_KEY = OBJECT_WRAPPER_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String BOOLEAN_FORMAT_KEY_SNAKE_CASE = "boolean_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String BOOLEAN_FORMAT_KEY_CAMEL_CASE = "booleanFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String BOOLEAN_FORMAT_KEY = BOOLEAN_FORMAT_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String OUTPUT_ENCODING_KEY_SNAKE_CASE = "output_encoding";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String OUTPUT_ENCODING_KEY_CAMEL_CASE = "outputEncoding";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String OUTPUT_ENCODING_KEY = OUTPUT_ENCODING_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String URL_ESCAPING_CHARSET_KEY_SNAKE_CASE = "url_escaping_charset";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String URL_ESCAPING_CHARSET_KEY_CAMEL_CASE = "urlEscapingCharset";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String URL_ESCAPING_CHARSET_KEY = URL_ESCAPING_CHARSET_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String AUTO_FLUSH_KEY_SNAKE_CASE = "auto_flush";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String AUTO_FLUSH_KEY_CAMEL_CASE = "autoFlush";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.17 */
-    public static final String AUTO_FLUSH_KEY = AUTO_FLUSH_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE = "new_builtin_class_resolver";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE = "newBuiltinClassResolver";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.17 */
-    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY = NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String SHOW_ERROR_TIPS_KEY_SNAKE_CASE = "show_error_tips";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String SHOW_ERROR_TIPS_KEY_CAMEL_CASE = "showErrorTips";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.21 */
-    public static final String SHOW_ERROR_TIPS_KEY = SHOW_ERROR_TIPS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String API_BUILTIN_ENABLED_KEY_SNAKE_CASE = "api_builtin_enabled";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String API_BUILTIN_ENABLED_KEY_CAMEL_CASE = "apiBuiltinEnabled";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.22 */
-    public static final String API_BUILTIN_ENABLED_KEY = API_BUILTIN_ENABLED_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
-    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE = "log_template_exceptions";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
-    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE = "logTemplateExceptions";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. @since 2.3.22 */
-    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY = LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
-    public static final String LAZY_IMPORTS_KEY_SNAKE_CASE = "lazy_imports";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
-    public static final String LAZY_IMPORTS_KEY_CAMEL_CASE = "lazyImports";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String LAZY_IMPORTS_KEY = LAZY_IMPORTS_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
-    public static final String LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE = "lazy_auto_imports";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
-    public static final String LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE = "lazyAutoImports";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String LAZY_AUTO_IMPORTS_KEY = LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
-    public static final String AUTO_IMPORT_KEY_SNAKE_CASE = "auto_import";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
-    public static final String AUTO_IMPORT_KEY_CAMEL_CASE = "autoImport";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
-    public static final String AUTO_IMPORT_KEY = AUTO_IMPORT_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.25 */
-    public static final String AUTO_INCLUDE_KEY_SNAKE_CASE = "auto_include";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.25 */
-    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;
-    
-    private static final String[] SETTING_NAMES_SNAKE_CASE = new String[] {
-        // Must be sorted alphabetically!
-        API_BUILTIN_ENABLED_KEY_SNAKE_CASE,
-        ARITHMETIC_ENGINE_KEY_SNAKE_CASE,
-        AUTO_FLUSH_KEY_SNAKE_CASE,
-        AUTO_IMPORT_KEY_SNAKE_CASE,
-        AUTO_INCLUDE_KEY_SNAKE_CASE,
-        BOOLEAN_FORMAT_KEY_SNAKE_CASE,
-        CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
-        CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
-        DATE_FORMAT_KEY_SNAKE_CASE,
-        DATETIME_FORMAT_KEY_SNAKE_CASE,
-        LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE,
-        LAZY_IMPORTS_KEY_SNAKE_CASE,
-        LOCALE_KEY_SNAKE_CASE,
-        LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE,
-        NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE,
-        NUMBER_FORMAT_KEY_SNAKE_CASE,
-        OBJECT_WRAPPER_KEY_SNAKE_CASE,
-        OUTPUT_ENCODING_KEY_SNAKE_CASE,
-        SHOW_ERROR_TIPS_KEY_SNAKE_CASE,
-        SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE,
-        TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE,
-        TIME_FORMAT_KEY_SNAKE_CASE,
-        TIME_ZONE_KEY_SNAKE_CASE,
-        URL_ESCAPING_CHARSET_KEY_SNAKE_CASE
-    };
-    
-    private static final String[] SETTING_NAMES_CAMEL_CASE = new String[] {
-        // Must be sorted alphabetically!
-        API_BUILTIN_ENABLED_KEY_CAMEL_CASE,
-        ARITHMETIC_ENGINE_KEY_CAMEL_CASE,
-        AUTO_FLUSH_KEY_CAMEL_CASE,
-        AUTO_IMPORT_KEY_CAMEL_CASE,
-        AUTO_INCLUDE_KEY_CAMEL_CASE,
-        BOOLEAN_FORMAT_KEY_CAMEL_CASE,
-        CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
-        CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
-        DATE_FORMAT_KEY_CAMEL_CASE,
-        DATETIME_FORMAT_KEY_CAMEL_CASE,
-        LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE,
-        LAZY_IMPORTS_KEY_CAMEL_CASE,
-        LOCALE_KEY_CAMEL_CASE,
-        LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE,
-        NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE,
-        NUMBER_FORMAT_KEY_CAMEL_CASE,
-        OBJECT_WRAPPER_KEY_CAMEL_CASE,
-        OUTPUT_ENCODING_KEY_CAMEL_CASE,
-        SHOW_ERROR_TIPS_KEY_CAMEL_CASE,
-        SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
-        TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE,
-        TIME_FORMAT_KEY_CAMEL_CASE,
-        TIME_ZONE_KEY_CAMEL_CASE,
-        URL_ESCAPING_CHARSET_KEY_CAMEL_CASE
-    };
-
-    private Configurable parent;
-    private HashMap<Object, Object> customAttributes;
-    
-    private Locale locale;
-    private String numberFormat;
-    private String timeFormat;
-    private String dateFormat;
-    private String dateTimeFormat;
-    private TimeZone timeZone;
-    private TimeZone sqlDataAndTimeTimeZone;
-    private boolean sqlDataAndTimeTimeZoneSet;
-    private String booleanFormat;
-    private String trueStringValue;  // deduced from booleanFormat
-    private String falseStringValue;  // deduced from booleanFormat
-    private TemplateExceptionHandler templateExceptionHandler;
-    private ArithmeticEngine arithmeticEngine;
-    private ObjectWrapper objectWrapper;
-    private String outputEncoding;
-    private boolean outputEncodingSet;
-    private String urlEscapingCharset;
-    private boolean urlEscapingCharsetSet;
-    private Boolean autoFlush;
-    private TemplateClassResolver newBuiltinClassResolver;
-    private Boolean showErrorTips;
-    private Boolean apiBuiltinEnabled;
-    private Boolean logTemplateExceptions;
-    private Map<String, ? extends TemplateDateFormatFactory> customDateFormats;
-    private Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats;
-    private LinkedHashMap<String, String> autoImports;
-    private ArrayList<String> autoIncludes;
-    private Boolean lazyImports;
-    private Boolean lazyAutoImports;
-    private boolean lazyAutoImportsSet;
-    
-    /**
-     * Intended to be called from inside FreeMarker only.
-     * Creates a top-level configurable, one that doesn't inherit from a parent, and thus stores the default values.
-     * Called by the {@link Configuration} constructor.
-     */
-    protected Configurable(Version incompatibleImprovements) {
-        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        parent = null;
-        locale = Configuration.getDefaultLocale();
-        timeZone = Configuration.getDefaultTimeZone();
-        sqlDataAndTimeTimeZone = null;
-        numberFormat = "number";
-        timeFormat = "";
-        dateFormat = "";
-        dateTimeFormat = "";
-        templateExceptionHandler = Configuration.getDefaultTemplateExceptionHandler();
-        arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE;
-        objectWrapper = Configuration.getDefaultObjectWrapper(incompatibleImprovements);
-        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(C_TRUE_FALSE);
-        
-        customAttributes = new HashMap();
-        
-        customDateFormats = Collections.emptyMap();
-        customNumberFormats = Collections.emptyMap();
-        
-        lazyImports = false;
-        lazyAutoImportsSet = true;
-        
-        initAutoImportsMap();
-        initAutoIncludesList();
-    }
-
-    /**
-     * Creates a new instance. Normally you do not need to use this constructor,
-     * as you don't use <code>Configurable</code> directly, but its subclasses.
-     */
-    protected Configurable(Configurable parent) {
-        this.parent = parent;
-        locale = null;
-        numberFormat = null;
-        templateExceptionHandler = null;
-        customAttributes = new HashMap(0);
-    }
-    
-    /**
-     * Returns the parent {@link Configurable} object of this object. The parent stores the default setting values for
-     * this {@link Configurable}. For example, the parent of a {@link org.apache.freemarker.core.Template} object is a
-     * {@link Configuration} object, so values not specified on {@link Template}-level are get from the
-     * {@link Configuration} object.
-     * 
-     * <p>
-     * Note on the parent of {@link Environment}: If you set {@link Configuration#setIncompatibleImprovements(Version)
-     * incompatible_improvements} to at least 2.3.22, it will be always the "main" {@link Template}, that is, the
-     * template for whose processing the {@link Environment} was created. With lower {@code incompatible_improvements},
-     * the current parent can temporary change <em>during template execution</em>, for example when your are inside an
-     * {@code #include}-d template (among others). Thus, don't build on which {@link Template} the parent of
-     * {@link Environment} is during template execution, unless you set {@code incompatible_improvements} to 2.3.22 or
-     * higher.
-     *
-     * @return The parent {@link Configurable} object, or {@code null} if this is the root {@link Configurable} object
-     *         (i.e, if it's the {@link Configuration} object).
-     */
-    public final Configurable getParent() {
-        return parent;
-    }
-    
-    /**
-     * Reparenting support. This is used by Environment when it includes a
-     * template - the included template becomes the parent configurable during
-     * its evaluation.
-     */
-    void setParent(Configurable parent) {
-        this.parent = parent;
-    }
-    
-    /**
-     * Sets the default locale used for number and date formatting (among others), also the locale used for searching
-     * localized template variations when no locale was explicitly requested.
-     * 
-     * @see Configuration#getTemplate(String, Locale)
-     */
-    public void setLocale(Locale locale) {
-        _NullArgumentException.check("locale", locale);
-        this.locale = locale;
-    }
-
-    /**
-     * Returns the assumed locale when searching for template files with no
-     * explicit requested locale. Defaults to system locale.
-     */
-    public Locale getLocale() {
-        return locale != null ? locale : parent.getLocale();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isLocaleSet() {
-        return locale != null;
-    }
-    
-    /**
-     * Sets the time zone to use when formatting date/time values.
-     * Defaults to the system time zone ({@link TimeZone#getDefault()}), regardless of the "locale" FreeMarker setting,
-     * so in a server application you probably want to set it explicitly in the {@link Environment} to match the
-     * preferred time zone of target audience (like the Web page visitor).
-     * 
-     * <p>If you or the templates set the time zone, you should probably also set
-     * {@link #setSQLDateAndTimeTimeZone(TimeZone)}!
-     * 
-     * @see #setSQLDateAndTimeTimeZone(TimeZone)
-     */
-    public void setTimeZone(TimeZone timeZone) {
-        _NullArgumentException.check("timeZone", timeZone);
-        this.timeZone = timeZone;
-    }
-
-    /**
-     * The getter pair of {@link #setTimeZone(TimeZone)}. 
-     */
-    public TimeZone getTimeZone() {
-        return timeZone != null ? timeZone : parent.getTimeZone();
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isTimeZoneSet() {
-        return timeZone != null;
-    }
-    
-    /**
-     * Sets the time zone used when dealing with {@link java.sql.Date java.sql.Date} and
-     * {@link java.sql.Time java.sql.Time} values. It defaults to {@code null} for backward compatibility, but in most
-     * application this should be set to the JVM default time zone (server default time zone), because that's what
-     * most JDBC drivers will use when constructing the {@link java.sql.Date java.sql.Date} and
-     * {@link java.sql.Time java.sql.Time} values. If this setting is {@code null}, FreeMarker will use the value of
-     * ({@link #getTimeZone()}) for {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values,
-     * which often gives bad results.
-     * 
-     * <p>This setting doesn't influence the formatting of other kind of values (like of
-     * {@link java.sql.Timestamp java.sql.Timestamp} or plain {@link java.util.Date java.util.Date} values).
-     * 
-     * <p>To decide what value you need, a few things has to be understood:
-     * <ul>
-     *   <li>Date-only and time-only values in SQL-oriented databases are usually store calendar and clock field
-     *   values directly (year, month, day, or hour, minute, seconds (with decimals)), as opposed to a set of points
-     *   on the physical time line. Thus, unlike SQL timestamps, these values usually aren't meant to be shown
-     *   differently depending on the time zone of the audience.
-     *   
-     *   <li>When a JDBC query has to return a date-only or time-only value, it has to convert it to a point on the
-     *   physical time line, because that's what {@link java.util.Date} and its subclasses store (milliseconds since
-     *   the epoch). Obviously, this is impossible to do. So JDBC just chooses a physical time which, when rendered
-     *   <em>with the JVM default time zone</em>, will give the same field values as those stored
-     *   in the database. (Actually, you can give JDBC a calendar, and so it can use other time zones too, but most
-     *   application won't care using those overloads.) For example, assume that the system time zone is GMT+02:00.
-     *   Then, 2014-07-12 in the database will be translated to physical time 2014-07-11 22:00:00 UTC, because that
-     *   rendered in GMT+02:00 gives 2014-07-12 00:00:00. Similarly, 11:57:00 in the database will be translated to
-     *   physical time 1970-01-01 09:57:00 UTC. Thus, the physical time stored in the returned value depends on the
-     *   default system time zone of the JDBC client, not just on the content in the database. (This used to be the
-     *   default behavior of ORM-s, like Hibernate, too.)
-     *   
-     *   <li>The value of the {@code time_zone} FreeMarker configuration setting sets the time zone used for the
-     *   template output. For example, when a web page visitor has a preferred time zone, the web application framework
-     *   may calls {@link Environment#setTimeZone(TimeZone)} with that time zone. Thus, the visitor will
-     *   see {@link java.sql.Timestamp java.sql.Timestamp} and plain {@link java.util.Date java.util.Date} values as
-     *   they look in his own time zone. While
-     *   this is desirable for those types, as they meant to represent physical points on the time line, this is not
-     *   necessarily desirable for date-only and time-only values. When {@code sql_date_and_time_time_zone} is
-     *   {@code null}, {@code time_zone} is used for rendering all kind of date/time/dateTime values, including
-     *   {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time}, and then if, for example,
-     *   {@code time_zone} is GMT+00:00, the
-     *   values from the earlier examples will be shown as 2014-07-11 (one day off) and 09:57:00 (2 hours off). While
-     *   those are the time zone correct renderings, those values are probably meant to be shown "as is".
-     *   
-     *   <li>You may wonder why this setting isn't simply "SQL time zone", since the time zone related behavior of JDBC
-     *   applies to {@link java.sql.Timestamp java.sql.Timestamp} too. FreeMarker assumes that you have set up your
-     *   application so that time stamps coming from the database go through the necessary conversion to store the
-     *   correct distance from the epoch (1970-01-01 00:00:00 UTC), as requested by {@link java.util.Date}. In that case
-     *   the time stamp can be safely rendered in different time zones, and thus it needs no special treatment.
-     * </ul>
-     * 
-     * @param tz Maybe {@code null}, in which case {@link java.sql.Date java.sql.Date} and
-     *          {@link java.sql.Time java.sql.Time} values will be formatted in the time zone returned by
-     *          {@link #getTimeZone()}.
-     *          (Note that since {@code null} is an allowed value for this setting, it will not cause
-     *          {@link #getSQLDateAndTimeTimeZone()} to fall back to the parent configuration.)
-     * 
-     * @see #setTimeZone(TimeZone)
-     * 
-     * @since 2.3.21
-     */
-    public void setSQLDateAndTimeTimeZone(TimeZone tz) {
-        sqlDataAndTimeTimeZone = tz;
-        sqlDataAndTimeTimeZoneSet = true;
-    }
-    
-    /**
-     * The getter pair of {@link #setSQLDateAndTimeTimeZone(TimeZone)}.
-     * 
-     * @return {@code null} if the value of {@link #getTimeZone()} should be used for formatting
-     *     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values, otherwise the time zone
-     *     that should be used to format the values of those two types.  
-     * 
-     * @since 2.3.21
-     */
-    public TimeZone getSQLDateAndTimeTimeZone() {
-        return sqlDataAndTimeTimeZoneSet
-                ? sqlDataAndTimeTimeZone
-                : (parent != null ? parent.getSQLDateAndTimeTimeZone() : null);
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isSQLDateAndTimeTimeZoneSet() {
-        return sqlDataAndTimeTimeZoneSet;
-    }
-
-    /**
-     * Sets the default number format used to convert numbers to strings. Currently, this is one of these:
-     * <ul>
-     *   <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}</li>
-     *   <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
-     *   <li>{@code "percent"}: The number format returned by {@link NumberFormat#getPercentInstance(Locale)}</li>
-     *   <li>{@code "computer"}: The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
-     *   <li>{@link java.text.DecimalFormat} pattern (like {@code "0.##"}). This syntax has a FreeMarker-specific
-     *       extension, so that you can specify options like the rounding mode and the symbols used in this string. For
-     *       example, {@code ",000;; roundingMode=halfUp groupingSeparator=_"} will format numbers like {@code ",000"}
-     *       would, but with half-up rounding mode, and {@code _} as the group separator. See more about "extended Java
-     *       decimal format" in the FreeMarker Manual.
-     *       </li>
-     *   <li>If the string starts with {@code @} character followed by a letter then it's interpreted as a custom number
-     *       format, but only if either {@link Configuration#getIncompatibleImprovements()} is at least 2.3.24, or
-     *       there's any custom formats defined (even if custom date/time/dateTime format). The format of a such string
-     *       is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
-     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomNumberFormats(Map)}, and
-     *       <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
-     *   </li>
-     * </ul>
-     * 
-     *   
-     * <p>Defaults to <tt>"number"</tt>.
-     */
-    public void setNumberFormat(String numberFormat) {
-        _NullArgumentException.check("numberFormat", numberFormat);
-        this.numberFormat = numberFormat;
-    }
-    
-    /**
-     * Getter pair of {@link #setNumberFormat(String)}. 
-     */
-    public String getNumberFormat() {
-        return numberFormat != null ? numberFormat : parent.getNumberFormat();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isNumberFormatSet() {
-        return numberFormat != null;
-    }
-    
-    /**
-     * Getter pair of {@link #setCustomNumberFormats(Map)}; do not modify the returned {@link Map}! To be consistent
-     * with other setting getters, if this setting was set directly on this {@link Configurable} object, this simply
-     * returns that value, otherwise it returns the value from the parent {@link Configurable}. So beware, the returned
-     * value doesn't reflect the {@link Map} key granularity fallback logic that FreeMarker actually uses for this
-     * setting (for that, use {@link #getCustomNumberFormat(String)}). The returned value isn't a snapshot; it may or
-     * may not shows the changes later made to this setting on this {@link Configurable} level (but usually it's well
-     * defined if until what point settings are possibly modified).
-     * 
-     * <p>
-     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
-     * {@link Map}.
-     * 
-     * @see #getCustomNumberFormatsWithoutFallback()
-     * 
-     * @since 2.3.24
-     */
-    public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats() {
-        return customNumberFormats == null ? parent.getCustomNumberFormats() : customNumberFormats;
-    }
-
-    /**
-     * Like {@link #getCustomNumberFormats()}, but doesn't fall back to the parent {@link Configurable}, nor does it
-     * provide a non-{@code null} default when called as the method of a {@link Configuration}.
-     * 
-     * @since 2.3.25
-     */
-    public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormatsWithoutFallback() {
-        return customNumberFormats;
-    }
-    
-    /**
-     * Associates names with formatter factories, which then can be referred by the {@link #setNumberFormat(String)
-     * number_format} setting with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
-     * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
-     * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
-     * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
-     * {@code @} always has special meaning).
-     * 
-     * @param customNumberFormats
-     *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
-     *            letters and digits (not {@code _}).
-     * 
-     * @since 2.3.24
-     */
-    public void setCustomNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> customNumberFormats) {
-        _NullArgumentException.check("customNumberFormats", customNumberFormats);
-        validateFormatNames(customNumberFormats.keySet());
-        this.customNumberFormats = customNumberFormats;
-    }
-    
-    private void validateFormatNames(Set<String> keySet) {
-        for (String name : keySet) {
-            if (name.length() == 0) {
-                throw new IllegalArgumentException("Format names can't be 0 length");
-            }
-            char firstChar = name.charAt(0);
-            if (firstChar == '@') {
-                throw new IllegalArgumentException(
-                        "Format names can't start with '@'. '@' is only used when referring to them from format "
-                        + "strings. In: " + name);
-            }
-            if (!Character.isLetter(firstChar)) {
-                throw new IllegalArgumentException("Format name must start with letter: " + name);
-            }
-            for (int i = 1; i < name.length(); i++) {
-                // Note that we deliberately don't allow "_" here.
-                if (!Character.isLetterOrDigit(name.charAt(i))) {
-                    throw new IllegalArgumentException("Format name can only contain letters and digits: " + name);
-                }
-            }
-        }
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isCustomNumberFormatsSet() {
-        return customNumberFormats != null;
-    }
-
-    /**
-     * Gets the custom name format registered for the name.
-     * 
-     * @since 2.3.24
-     */
-    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
-        TemplateNumberFormatFactory r;
-        if (customNumberFormats != null) {
-            r = customNumberFormats.get(name);
-            if (r != null) {
-                return r;
-            }
-        }
-        return parent != null ? parent.getCustomNumberFormat(name) : null;
-    }
-    
-    /**
-     * Tells if this configurable object or its parent defines any custom formats.
-     * 
-     * @since 2.3.24
-     */
-    public boolean hasCustomFormats() {
-        return customNumberFormats != null && !customNumberFormats.isEmpty()
-                || customDateFormats != null && !customDateFormats.isEmpty()
-                || getParent() != null && getParent().hasCustomFormats(); 
-    }
-    
-    /**
-     * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
-     * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
-     * so {@code "yes, no"} is WRONG (unless you want that leading space before "no").
-     * 
-     * <p>For backward compatibility the default is {@code "true,false"}, but using that value is denied for automatic
-     * boolean-to-string conversion (like <code>${myBoolean}</code> will fail with it), only {@code myBool?string} will
-     * allow it, which is deprecated since FreeMarker 2.3.20.
-     * 
-     * <p>Note that automatic boolean-to-string conversion only exists since FreeMarker 2.3.20. Earlier this setting
-     * only influenced the result of {@code myBool?string}. 
-     */
-    public void setBooleanFormat(String booleanFormat) {
-        _NullArgumentException.check("booleanFormat", booleanFormat);
-        
-        int commaIdx = booleanFormat.indexOf(',');
-        if (commaIdx == -1) {
-            throw new IllegalArgumentException(
-                    "Setting value must be string that contains two comma-separated values for true and false, " +
-                    "respectively.");
-        }
-        
-        this.booleanFormat = booleanFormat; 
-        
-        if (booleanFormat.equals(C_TRUE_FALSE)) {
-            // C_TRUE_FALSE is the default for BC, but it's not a good default for human audience formatting, so we
-            // pretend that it wasn't set.
-            trueStringValue = null; 
-            falseStringValue = null;
-        } else {
-            trueStringValue = booleanFormat.substring(0, commaIdx); 
-            falseStringValue = booleanFormat.substring(commaIdx + 1);
-        }
-    }
-    
-    /**
-     * The getter pair of {@link #setBooleanFormat(String)}.
-     */
-    public String getBooleanFormat() {
-        return booleanFormat != null ? booleanFormat : parent.getBooleanFormat(); 
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isBooleanFormatSet() {
-        return booleanFormat != null;
-    }
-        
-    String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException {
-        if (value) {
-            String s = getTrueStringValue();
-            if (s == null) {
-                if (fallbackToTrueFalse) {
-                    return MiscUtil.C_TRUE;
-                } else {
-                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
-                }
-            } else {
-                return s;
-            }
-        } else {
-            String s = getFalseStringValue();
-            if (s == null) {
-                if (fallbackToTrueFalse) {
-                    return MiscUtil.C_FALSE;
-                } else {
-                    throw new _MiscTemplateException(getNullBooleanFormatErrorDescription());
-                }
-            } else {
-                return s;
-            }
-        }
-    }
-
-    private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
-        return new _ErrorDescriptionBuilder(
-                "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
-                new _DelayedJQuote(getBooleanFormat()), 
-                (getBooleanFormat().equals(C_TRUE_FALSE)
-                    ? ", which is the legacy default computer-language format, and hence isn't accepted."
-                    : ".")
-                ).tips(
-                     "If you just want \"true\"/\"false\" result as you are generting computer-language output, "
-                     + "use \"?c\", like ${myBool?c}.",
-                     "You can write myBool?string('yes', 'no') and like to specify boolean formatting in place.",
-                     new Object[] {
-                         "If you need the same two values on most places, the programmers should set the \"",
-                         BOOLEAN_FORMAT_KEY ,"\" setting to something like \"yes,no\"." }
-                 );
-    }
-
-    /**
-     * Returns the string to which {@code true} is converted to for human audience, or {@code null} if automatic
-     * coercion to string is not allowed. The default value is {@code null}.
-     * 
-     * <p>This value is deduced from the {@code "boolean_format"} setting.
-     * Confusingly, for backward compatibility (at least until 2.4) that defaults to {@code "true,false"}, yet this
-     * defaults to {@code null}. That's so because {@code "true,false"} is treated exceptionally, as that default is a
-     * historical mistake in FreeMarker, since it targets computer language output, not human writing. Thus it's
-     * ignored.
-     * 
-     * @since 2.3.20
-     */
-    String getTrueStringValue() {
-        // The first step deliberately tests booleanFormat instead of trueStringValue! 
-        return booleanFormat != null ? trueStringValue : (parent != null ? parent.getTrueStringValue() : null); 
-    }
-
-    /**
-     * Same as {@link #getTrueStringValue()} but with {@code false}. 
-     * @since 2.3.20
-     */
-    String getFalseStringValue() {
-        // The first step deliberately tests booleanFormat instead of falseStringValue! 
-        return booleanFormat != null ? falseStringValue : (parent != null ? parent.getFalseStringValue() : null); 
-    }
-
-    /**
-     * Sets the format used to convert {@link java.util.Date}-s to string-s that are time (no date part) values,
-     * also the format that {@code someString?time} will use to parse strings.
-     * 
-     * <p>For the possible values see {@link #setDateTimeFormat(String)}.
-     *   
-     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", which is currently {@code "medium"}.
-     */
-    public void setTimeFormat(String timeFormat) {
-        _NullArgumentException.check("timeFormat", timeFormat);
-        this.timeFormat = timeFormat;
-    }
-
-    /**
-     * The getter pair of {@link #setTimeFormat(String)}.
-     */
-    public String getTimeFormat() {
-        return timeFormat != null ? timeFormat : parent.getTimeFormat();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isTimeFormatSet() {
-        return timeFormat != null;
-    }
-    
-    /**
-     * Sets the format used to convert {@link java.util.Date}-s to string-s that are date (no time part) values,
-     * also the format that {@code someString?date} will use to parse strings.
-     * 
-     * <p>For the possible values see {@link #setDateTimeFormat(String)}.
-     *   
-     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", which is currently {@code "medium"}.
-     */
-    public void setDateFormat(String dateFormat) {
-        _NullArgumentException.check("dateFormat", dateFormat);
-        this.dateFormat = dateFormat;
-    }
-
-    /**
-     * The getter pair of {@link #setDateFormat(String)}.
-     */
-    public String getDateFormat() {
-        return dateFormat != null ? dateFormat : parent.getDateFormat();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isDateFormatSet() {
-        return dateFormat != null;
-    }
-    
-    /**
-     * Sets the format used to convert {@link java.util.Date}-s to string-s that are date-time (timestamp) values,
-     * also the format that {@code someString?datetime} will use to parse strings.
-     * 
-     * <p>The possible setting values are (the quotation marks aren't part of the value itself):
-     * 
-     * <ul>
-     *   <li><p>Patterns accepted by Java's {@link SimpleDateFormat}, for example {@code "dd.MM.yyyy HH:mm:ss"} (where
-     *       {@code HH} means 24 hours format) or {@code "MM/dd/yyyy hh:mm:ss a"} (where {@code a} prints AM or PM, if
-     *       the current language is English).
-     *   
-     *   <li><p>{@code "xs"} for XML Schema format, or {@code "iso"} for ISO 8601:2004 format.
-     *       These formats allow various additional options, separated with space, like in
-     *       {@code "iso m nz"} (or with {@code _}, like in {@code "iso_m_nz"}; this is useful in a case like
-     *       {@code lastModified?string.iso_m_nz}). The options and their meanings are:
-     *       
-     *       <ul>
-     *         <li><p>Accuracy options:<br>
-     *             {@code ms} = Milliseconds, always shown with all 3 digits, even if it's all 0-s.
-     *                     Example: {@code 13:45:05.800}<br>
-     *             {@code s} = Seconds (fraction seconds are dropped even if non-0), like {@code 13:45:05}<br>
-     *             {@code m} = Minutes, like {@code 13:45}. This isn't allowed for "xs".<br>
-     *             {@code h} = Hours, like {@code 13}. This isn't allowed for "xs".<br>
-     *             Neither = Up to millisecond accuracy, but trailing millisecond 0-s are removed, also the whole
-     *                     milliseconds part if it would be 0 otherwise. Example: {@code 13:45:05.8}
-     *                     
-     *         <li><p>Time zone offset visibility options:<br>
-     *             {@code fz} = "Force Zone", always show time zone offset (even for for
-     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values).
-     *                     But, because ISO 8601 doesn't allow for dates (means date without time of the day) to
-     *                     show the zone offset, this option will have no effect in the case of {@code "iso"} with
-     *                     dates.<br>
-     *             {@code nz} = "No Zone", never show time zone offset<br>
-     *             Neither = always show time zone offset, except for {@link java.sql.Date java.sql.Date}
-     *                     and {@link java.sql.Time java.sql.Time}, and for {@code "iso"} date values.
-     *                     
-     *         <li><p>Time zone options:<br>
-     *             {@code u} = Use UTC instead of what the {@code time_zone} setting suggests. However,
-     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} aren't affected
-     *                     by this (see {@link #setSQLDateAndTimeTimeZone(TimeZone)} to understand why)<br>
-     *             {@code fu} = "Force UTC", that is, use UTC instead of what the {@code time_zone} or the
-     *                     {@code sql_date_and_time_time_zone} setting suggests. This also effects
-     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values<br>
-     *             Neither = Use the time zone suggested by the {@code time_zone} or the
-     *                     {@code sql_date_and_time_time_zone} configuration setting ({@link #setTimeZone(TimeZone)} and
-     *                     {@link #setSQLDateAndTimeTimeZone(TimeZone)}).
-     *       </ul>
-     *       
-     *       <p>The options can be specified in any order.</p>
-     *       
-     *       <p>Options from the same category are mutually exclusive, like using {@code m} and {@code s}
-     *       together is an error.
-     *       
-     *       <p>The accuracy and time zone offset visibility options don't influence parsing, only formatting.
-     *       For example, even if you use "iso m nz", "2012-01-01T15:30:05.125+01" will be parsed successfully and with
-     *       milliseconds accuracy.
-     *       The time zone options (like "u") influence what time zone is chosen only when parsing a string that doesn't
-     *       contain time zone offset.
-     *       
-     *       <p>Parsing with {@code "iso"} understands both extend format and basic format, like
-     *       {@code 20141225T235018}. It doesn't, however, support the parsing of all kind of ISO 8601 strings: if
-     *       there's a date part, it must use year, month and day of the month values (not week of the year), and the
-     *       day can't be omitted.
-     *       
-     *       <p>The output of {@code "iso"} is deliberately so that it's also a good representation of the value with
-     *       XML Schema format, except for 0 and negative years, where it's impossible. Also note that the time zone
-     *       offset is omitted for date values in the {@code "iso"} format, while it's preserved for the {@code "xs"}
-     *       format.
-     *       
-     *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code "full"}, which that has locale-dependent
-     *       meaning defined by the Java platform (see in the documentation of {@link java.text.DateFormat}).
-     *       For date-time values, you can specify the length of the date and time part independently, be separating
-     *       them with {@code _}, like {@code "short_medium"}. ({@code "medium"} means
-     *       {@code "medium_medium"} for date-time values.)
-     *       
-     *   <li><p>Anything that starts with {@code "@"} followed by a letter is interpreted as a custom
-     *       date/time/dateTime format, but only if either {@link Configuration#getIncompatibleImprovements()}
-     *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
-     *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
-     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomDateFormats(Map)}, and
-     *       <code><i>parameters</i></code> is parsed by the custom number format.
-     *       
-     * </ul> 
-     * 
-     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", which is currently {@code "medium_medium"}.
-     */
-    public void setDateTimeFormat(String dateTimeFormat) {
-        _NullArgumentException.check("dateTimeFormat", dateTimeFormat);
-        this.dateTimeFormat = dateTimeFormat;
-    }
-
-    /**
-     * The getter pair of {@link #setDateTimeFormat(String)}.
-     */
-    public String getDateTimeFormat() {
-        return dateTimeFormat != null ? dateTimeFormat : parent.getDateTimeFormat();
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isDateTimeFormatSet() {
-        return dateTimeFormat != null;
-    }
-    
-    /**
-     * Getter pair of {@link #setCustomDateFormats(Map)}; do not modify the returned {@link Map}! To be consistent with
-     * other setting getters, if this setting was set directly on this {@link Configurable} object, this simply returns
-     * that value, otherwise it returns the value from the parent {@link Configurable}. So beware, the returned value
-     * doesn't reflect the {@link Map} key granularity fallback logic that FreeMarker actually uses for this setting
-     * (for that, use {@link #getCustomDateFormat(String)}). The returned value isn't a snapshot; it may or may not
-     * shows the changes later made to this setting on this {@link Configurable} level (but usually it's well defined if
-     * until what point settings are possibly modified).
-     * 
-     * <p>
-     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
-     * {@link Map}.
-     * 
-     * @see #getCustomDateFormatsWithoutFallback()
-     * 
-     * @since 2.3.24
-     */
-    public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats() {
-        return customDateFormats == null ? parent.getCustomDateFormats() : customDateFormats;
-    }
-
-    /**
-     * Like {@link #getCustomDateFormats()}, but doesn't fall back to the parent {@link Configurable}, nor does it
-     * provide a non-{@code null} default when called as the method of a {@link Configuration}.
-     * 
-     * @since 2.3.25
-     */
-    public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormatsWithoutFallback() {
-        return customDateFormats;
-    }
-    
-    /**
-     * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
-     * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
-     * datetime_format} settings with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
-     * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
-     * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
-     * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
-     * {@code @} always has special meaning).
-     *
-     * @param customDateFormats
-     *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
-     *            letters and digits.
-     * 
-     * @since 2.3.24
-     */
-    public void setCustomDateFormats(Map<String, ? extends TemplateDateFormatFactory> customDateFormats) {
-        _NullArgumentException.check("customDateFormats", customDateFormats);
-        validateFormatNames(customDateFormats.keySet());
-        this.customDateFormats = customDateFormats;
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     * 
-     * @since 2.3.24
-     */
-    public boolean isCustomDateFormatsSet() {
-        return customDateFormats != null;
-    }
-
-    /**
-     * Gets the custom name format registered for the name.
-     * 
-     * @since 2.3.24
-     */
-    public TemplateDateFormatFactory getCustomDateFormat(String name) {
-        TemplateDateFormatFactory r;
-        if (customDateFormats != null) {
-            r = customDateFormats.get(name);
-            if (r != null) {
-                return r;
-            }
-        }
-        return parent != null ? parent.getCustomDateFormat(name) : null;
-    }
-    
-    /**
-     * Sets the exception handler used to handle exceptions occurring inside templates.
-     * The default is {@link TemplateExceptionHandler#DEBUG_HANDLER}. The recommended values are:
-     * 
-     * <ul>
-     *   <li>In production systems: {@link TemplateExceptionHandler#RETHROW_HANDLER}
-     *   <li>During development of HTML templates: {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}
-     *   <li>During development of non-HTML templates: {@link TemplateExceptionHandler#DEBUG_HANDLER}
-     * </ul>
-     * 
-     * <p>All of these will let the exception propagate further, so that you can catch it around
-     * {@link Template#process(Object, Writer)} for example. The difference is in what they print on the output before
-     * they do that.
-     * 
-     * <p>Note that the {@link TemplateExceptionHandler} is not meant to be used for generating HTTP error pages.
-     * Neither is it meant to be used to roll back the printed output. These should be solved outside template
-     * processing when the exception raises from {@link Template#process(Object, Writer) Template.process}.
-     * {@link TemplateExceptionHandler} meant to be used if you want to include special content <em>in</em> the template
-     * output, or if you want to suppress certain exceptions. 
-     */
-    public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
-        _NullArgumentException.check("templateExceptionHandler", templateExceptionHandler);
-        this.templateExceptionHandler = templateExceptionHandler;
-    }
-
-    /**
-     * The getter pair of {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}.
-     */
-    public TemplateExceptionHandler getTemplateExceptionHandler() {
-        return templateExceptionHandler != null
-                ? templateExceptionHandler : parent.getTemplateExceptionHandler();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isTemplateExceptionHandlerSet() {
-        return templateExceptionHandler != null;
-    }
-
-    /**
-     * Sets the arithmetic engine used to perform arithmetic operations.
-     * The default is {@link BigDecimalArithmeticEngine#INSTANCE}.
-     */
-    public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
-        _NullArgumentException.check("arithmeticEngine", arithmeticEngine);
-        this.arithmeticEngine = arithmeticEngine;
-    }
-
-    /**
-     * The getter pair of {@link #setArithmeticEngine(ArithmeticEngine)}.
-     */
-    public ArithmeticEngine getArithmeticEngine() {
-        return arithmeticEngine != null
-                ? arithmeticEngine : parent.getArithmeticEngine();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isArithmeticEngineSet() {
-        return arithmeticEngine != null;
-    }
-
-    /**
-     * Sets the object wrapper used to wrap objects to {@link TemplateModel}-s.
-     * The default is {@link DefaultObjectWrapper.Builder#build()}.
-     */
-    public void setObjectWrapper(ObjectWrapper objectWrapper) {
-        _NullArgumentException.check("objectWrapper", objectWrapper);
-        this.objectWrapper = objectWrapper;
-    }
-
-    /**
-     * The getter pair of {@link #setObjectWrapper(ObjectWrapper)}.
-     */
-    public ObjectWrapper getObjectWrapper() {
-        return objectWrapper != null
-                ? objectWrapper : parent.getObjectWrapper();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isObjectWrapperSet() {
-        return objectWrapper != null;
-    }
-    
-    /**
-     * Informs FreeMarker about the charset used for the output. As FreeMarker outputs character stream (not
-     * byte stream), it's not aware of the output charset unless the software that encloses it tells it
-     * with this setting. Some templates may use FreeMarker features that require this information.
-     * Setting this to {@code null} means that the output encoding is not known.
-     * 
-     * <p>Defaults to {@code null} (unknown).
-     */
-    public void setOutputEncoding(String outputEncoding) {
-        this.outputEncoding = outputEncoding;
-        outputEncodingSet = true;
-    }
-    
-    public String getOutputEncoding() {
-        return outputEncodingSet
-                ? outputEncoding
-                : (parent != null ? parent.getOutputEncoding() : null);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isOutputEncodingSet() {
-        return outputEncodingSet;
-    }
-    
-    /**
-     * Sets the URL escaping charset. If not set ({@code null}), the output encoding
-     * ({@link #setOutputEncoding(String)}) will be used for URL escaping.
-     * 
-     * Defaults to {@code null}.
-     */
-    public void setURLEscapingCharset(String urlEscapingCharset) {
-        this.urlEscapingCharset = urlEscapingCharset;
-        urlEscapingCharsetSet = true;
-    }
-    
-    public String getURLEscapingCharset() {
-        return urlEscapingCharsetSet
-                ? urlEscapingCharset
-                : (parent != null ? parent.getURLEscapingCharset() : null);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isURLEscapingCharsetSet() {
-        return urlEscapingCharsetSet;
-    }
-
-    /**
-     * Sets the {@link TemplateClassResolver} that is used when the
-     * <code>new</code> built-in is called in a template. That is, when
-     * a template contains the <code>"com.example.SomeClassName"?new</code>
-     * expression, this object will be called to resolve the
-     * <code>"com.example.SomeClassName"</code> string to a class. The default
-     * value is {@link TemplateClassResolver#UNRESTRICTED_RESOLVER}. If you allow
-     * users to upload templates, it's important to use a custom restrictive
-     * {@link TemplateClassResolver} or {@link TemplateClassResolver#ALLOWS_NOTHING_RESOLVER}.
-     * 
-     * @since 2.3.17
-     */
-    public void setNewBuiltinClassResolver(TemplateClassResolver newBuiltinClassResolver) {
-        _NullArgumentException.check("newBuiltinClassResolver", newBuiltinClassResolver);
-        this.newBuiltinClassResolver = newBuiltinClassResolver;
-    }
-
-    /**
-     * Retrieves the {@link TemplateClassResolver} used
-     * to resolve classes when "SomeClassName"?new is called in a template.
-     * 
-     * @see #setNewBuiltinClassResolver(TemplateClassResolver)
-     * 
-     * @since 2.3.17
-     */
-    public TemplateClassResolver getNewBuiltinClassResolver() {
-        return newBuiltinClassResolver != null
-                ? newBuiltinClassResolver : parent.getNewBuiltinClassResolver();
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isNewBuiltinClassResolverSet() {
-        return newBuiltinClassResolver != null;
-    }
-    
-    /**
-     * Sets whether the output {@link Writer} is automatically flushed at
-     * the end of {@link Template#process(Object, Writer)} (and its
-     * overloads). The default is {@code true}.
-     * 
-     * <p>Using {@code false} is needed for example when a Web page is composed
-     * from several boxes (like portlets, GUI panels, etc.) that aren't inserted
-     * with <tt>#include</tt> (or with similar directives) into a master
-     * FreeMarker template, rather they are all processed with a separate
-     * {@link Template#process(Object, Writer)} call. In a such scenario the
-     * automatic flushes would commit the HTTP response after each box, hence
-     * interfering with full-page buffering, and also possibly decreasing
-     * performance with too frequent and too early response buffer flushes.
-     * 
-     * @since 2.3.17
-     */
-    public void setAutoFlush(boolean autoFlush) {
-        this.autoFlush = Boolean.valueOf(autoFlush);
-    }
-    
-    /**
-     * See {@link #setAutoFlush(boolean)}
-     * 
-     * @since 2.3.17
-     */
-    public boolean getAutoFlush() {
-        return autoFlush != null 
-            ? autoFlush.booleanValue()
-            : (parent != null ? parent.getAutoFlush() : true);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isAutoFlushSet() {
-        return autoFlush != null;
-    }
-    
-    /**
-     * Sets if tips should be shown in error messages of errors arising during template processing.
-     * The default is {@code true}. 
-     * 
-     * @since 2.3.21
-     */
-    public void setShowErrorTips(boolean showTips) {
-        showErrorTips = Boolean.valueOf(showTips);
-    }
-    
-    /**
-     * See {@link #setShowErrorTips(boolean)}
-     * 
-     * @since 2.3.21
-     */
-    public boolean getShowErrorTips() {
-        return showErrorTips != null 
-            ? showErrorTips.booleanValue()
-            : (parent != null ? parent.getShowErrorTips() : true);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isShowErrorTipsSet() {
-        return showErrorTips != null;
-    }
-    
-    /**
-     * Specifies if {@code ?api} can be used in templates. Defaults to {@code false} so that updating FreeMarker won't
-     * decrease the security of existing applications.
-     * 
-     * @since 2.3.22
-     */
-    public void setAPIBuiltinEnabled(boolean value) {
-        apiBuiltinEnabled = Boolean.valueOf(value);
-    }
-
-    /**
-     * See {@link #setAPIBuiltinEnabled(boolean)}
-     * 
-     * @since 2.3.22
-     */
-    public boolean isAPIBuiltinEnabled() {
-        return apiBuiltinEnabled != null 
-                ? apiBuiltinEnabled.booleanValue()
-                : (parent != null ? parent.isAPIBuiltinEnabled() : false);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isAPIBuiltinEnabledSet() {
-        return apiBuiltinEnabled != null;
-    }
-    
-    /**
-     * Specifies if {@link TemplateException}-s thrown by template processing are logged by FreeMarker or not. The
-     * default is {@code true} for backward compatibility, but that results in logging the exception twice in properly
-     * written applications, because there the {@link TemplateException} thrown by the public FreeMarker API is also
-     * logged by the caller (even if only as the cause exception of a higher level exception). Hence, in modern
-     * applications it should be set to {@code false}. Note that this setting has no effect on the logging of exceptions
-     * caught by {@code #attempt}; those are always logged, no mater what (because those exceptions won't bubble up
-     * until the API caller).
-     * 
-     * @since 2.3.22
-     */
-    public void setLogTemplateExceptions(boolean value) {
-        logTemplateExceptions = Boolean.valueOf(value);
-    }
-
-    /**
-     * See {@link #setLogTemplateExceptions(boolean)}
-     * 
-     * @since 2.3.22
-     */
-    public boolean getLogTemplateExceptions() {
-        return logTemplateExceptions != null 
-                ? logTemplateExceptions.booleanValue()
-                : (parent != null ? parent.getLogTemplateExceptions() : true);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.24
-     */
-    public boolean isLogTemplateExceptionsSet() {
-        return logTemplateExceptions != null;
-    }
-    
-    /**
-     * The getter pair of {@link #setLazyImports(boolean)}.
-     * 
-     * @since 2.3.25
-     */
-    public boolean getLazyImports() {
-        return lazyImports != null ? lazyImports.booleanValue() : parent.getLazyImports();
-    }
-    
-    /**
-     * Specifies if {@code <#import ...>} (and {@link Environment#importLib(String, String)}) should delay the loading
-     * and processing of the imported templates until the content of the imported namespace is actually accessed. This
-     * makes the overhead of <em>unused</em> imports negligible. A drawback is that importing a missing or otherwise
-     * broken template will be successful, and the problem will remain hidden until (and if) the namespace content is
-     * actually used. Also, you lose the strict control over when the namespace initializing code in the imported
-     * template will be executed, though it shouldn't mater for well written imported templates anyway. Note that the
-     * namespace initializing code will run with the same {@linkplain Configurable#getLocale() locale} as it was at the
-     * point of the {@code <#import ...>} call (other settings won't be handled specially like that).
-     * 
-     * <p>
-     * The default is {@code false} (and thus imports are eager) for backward compatibility, which can cause
-     * perceivable overhead if you have many imports and only a few of them is used.
-     * 
-     * <p>
-     * This setting also affects {@linkplain #setAutoImports(Map) auto-imports}, unless you have set a non-{@code null}
-     * value with {@link #setLazyAutoImports(Boolean)}.
-     * 
-     * @see #setLazyAutoImports(Boolean)
-     * 
-     * @since 2.3.25
-     */
-    public void setLazyImports(boolean lazyImports) {
-        this.lazyImports = Boolean.valueOf(lazyImports);
-    }
-
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.25
-     */
-    public boolean isLazyImportsSet() {
-        return lazyImports != null;
-    }
-    
-    /**
-     * The getter pair of {@link #setLazyAutoImports(Boolean)}.
-     * 
-     * @since 2.3.25
-     */
-    public Boolean getLazyAutoImports() {
-        return lazyAutoImportsSet ? lazyAutoImports : parent.getLazyAutoImports();
-    }
-
-    /**
-     * Specifies if {@linkplain #setAutoImports(Map) auto-imports} will be
-     * {@link #setLazyImports(boolean) lazy imports}. This is useful to make the overhead of <em>unused</em>
-     * auto-imports negligible. If this is set to {@code null}, {@link #getLazyImports()} specifies the behavior of
-     * auto-imports too. The default value is {@code null}.
-     * 
-     * @since 2.3.25
-     */
-    public void setLazyAutoImports(Boolean lazyAutoImports) {
-        this.lazyAutoImports = lazyAutoImports;
-        lazyAutoImportsSet = true;
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     *  
-     * @since 2.3.25
-     */
-    public boolean isLazyAutoImportsSet() {
-        return lazyAutoImportsSet;
-    }
-    
-    /**
-     * Adds an invisible <code>#import <i>templateName</i> as <i>namespaceVarName</i></code> at the beginning of the
-     * main template (that's the top-level template that wasn't included/imported from another template). While it only
-     * affects the main template directly, as the imports will invoke a global variable there, the imports will be
-     * visible from the further imported templates too (note that {@link Configuration#getIncompatibleImprovements()}
-     * set to 2.3.24 fixes a rarely surfacing bug with that).
-     * 
-     * <p>
-     * It's recommended to set the {@code auto_impots_lazy} 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.
-     * 
-     * <p>
-     * If the imports aren't lazy, the order of the imports will be the same as the order in which they were added with
-     * this method. (Calling this method with an already added {@code namespaceVarName} will move that to the end
-     * of the auto-import order.)
-     * 
-     * <p>
-     * The auto-import is added directly to the {@link Configurable} on which this method is called (not to the parents
-     * or children), but when the main template is processed, the auto-imports are collected from all the
-     * {@link Configurable} levels, in parent-to-child order: {@link Configuration}, {@link Template} (the main
-     * template), {@link Environment}. If the same {@code namespaceVarName} occurs on multiple levels, the one on the
-     * child level is used, and the clashing import from the parent level is skipped.
-     * 
-     * <p>If there are also auto-includes (see {@link #addAutoInclude(String)}), those will be executed after
-     * the auto-imports.
-     * 
-     * @see #setAutoImports(Map)
-     */
-    public void addAutoImport(String namespaceVarName, String templateName) {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (autoImports == null) {
-                initAutoImportsMap();
-            } else {
-                // This was a List earlier, so re-inserted items must go to the end, hence we remove() before put().
-                autoImports.remove(namespaceVarName);
-            }
-            autoImports.put(namespaceVarName, templateName);
-        }
-    }
-
-    private void initAutoImportsMap() {
-        autoImports = new LinkedHashMap<>(4);
-    }
-    
-    /**
-     * Removes an auto-import from this {@link Configurable} level (not from the parents or children);
-     * see {@link #addAutoImport(String, String)}. Does nothing if the auto-import doesn't exist.
-     */
-    public void removeAutoImport(String namespaceVarName) {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (autoImports != null) {
-                autoImports.remove(namespaceVarName);
-            }
-        }
-    }
-    
-    /**
-     * Removes all auto-imports, then calls {@link #addAutoImport(String, String)} for each {@link Map}-entry (the entry
-     * key is the {@code namespaceVarName}). The order of the auto-imports will be the same as {@link Map#keySet()}
-     * returns the keys (but the order of imports doesn't mater for properly designed libraries anyway).
-     * 
-     * @param map
-     *            Maps the namespace variable names to the template names; not {@code null}
-     */
-    public void setAutoImports(Map map) {
-        _NullArgumentException.check("map", map);
-        
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (autoImports != null) {
-                autoImports.clear();
-            }
-            for (Map.Entry<?, ?> entry : ((Map<?, ?>) map).entrySet()) {
-                Object key = entry.getKey();
-                if (!(key instanceof String)) {
-                    throw new IllegalArgumentException(
-                            "Key in Map wasn't a String, but a(n) " + key.getClass().getName() + ".");
-                }
-                
-                Object value = entry.getValue();
-                if (!(value instanceof String)) {
-                    throw new IllegalArgumentException(
-                            "Value in Map wasn't a String, but a(n) " + key.getClass().getName() + ".");
-                }
-                
-                addAutoImport((String) key, (String) value);
-            }
-        }
-    }
-    
-    /**
-     * Getter pair of {@link #setAutoImports(Map)}; do not modify the returned {@link Map}! To be consistent with other
-     * setting getters, if this setting was set directly on this {@link Configurable} object, this simply returns that
-     * value, otherwise it returns the value from the parent {@link Configurable}. So beware, the returned value doesn't
-     * reflect the {@link Map} key granularity fallback logic that FreeMarker actually uses for this setting. The
-     * returned value is not the same {@link Map} object that was set with {@link #setAutoImports(Map)}, only its
-     * content is the same. The returned value isn't a snapshot; it may or may not shows the changes later made to this
-     * setting on this {@link Configurable} level (but usually it's well defined if until what point settings are
-     * possibly modified).
-     * 
-     * <p>
-     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
-     * {@link Map}.
-     * 
-     * @see #getAutoImportsWithoutFallback()
-     * 
-     * @since 2.3.25
-     */
-    public Map<String, String> getAutoImports() {
-        return autoImports != null ? autoImports : parent.getAutoImports();
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     * 
-     * @since 2.3.25
-     */
-    public boolean isAutoImportsSet() {
-        return autoImports != null;
-    }
-
-    /**
-     * Like {@link #getAutoImports()}, but doesn't fall back to the parent {@link Configurable} (and so it can be
-     * {@code null}).
-     *  
-     * @since 2.3.25
-     */
-    public Map<String, String> getAutoImportsWithoutFallback() {
-        return autoImports;
-    }
-    
-    /**
-     * Adds an invisible <code>#include <i>templateName</i></code> at the beginning of the main template (that's the
-     * top-level template that wasn't included/imported from another template).
-     * 
-     * <p>
-     * The order of the inclusions will be the same as the order in which they were added with this method.
-     * 
-     * <p>
-     * The auto-include is added directly to the {@link Configurable} on which this method is called (not to the parents
-     * or children), but when the main template is processed, the auto-includes are collected from all the
-     * {@link Configurable} levels, in parent-to-child order: {@link Configuration}, {@link Template} (the main
-     * template), {@link Environment}.
-     * 
-     * <p>
-     * If there are also auto-imports ({@link #addAutoImport(String, String)}), those imports will be executed before
-     * the auto-includes, hence the namespace variables are accessible for the auto-included templates.
-     * 
-     * <p>
-     * Calling {@link #addAutoInclude(String)} with an already added template name will just move that to the end of the
-     * auto-include list (within the same {@link Configurable} level). This works even if the same template name appears
-     * on different {@link Configurable} levels, in which case only the inclusion on the lowest (child) level will be
-     * executed.
-     * 
-     * @see #setAutoIncludes(List)
-     */
-    public void addAutoInclude(String templateName) {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (autoIncludes == null) {
-                initAutoIncludesList();
-            } else {
-                autoIncludes.remove(templateName);
-            }
-            autoIncludes.add(templateName);
-        }
-    }
-
-    private void initAutoIncludesList() {
-        autoIncludes = new ArrayList<>(4);
-    }
-    
-    /**
-     * Removes all auto-includes, then calls {@link #addAutoInclude(String)} for each {@link List} items.
-     * 
-     * <p>Before {@linkplain Configuration#Configuration(Version) incompatible improvements} 2.3.25 it doesn't filter
-     * out duplicates from the list if this method was called on a {@link Configuration} instance.
-     */
-    public void setAutoIncludes(List templateNames) {
-        _NullArgumentException.check("templateNames", templateNames);
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (autoIncludes != null) {
-                autoIncludes.clear();
-            }
-            for (Object templateName : templateNames) {
-                if (!(templateName instanceof String)) {
-                    throw new IllegalArgumentException("List items must be String-s.");
-                }
-                addAutoInclude((String) templateName);
-            }
-        }
-    }
-    
-    /**
-     * Getter pair of {@link #setAutoIncludes(List)}; do not modify the returned {@link List}! To be consistent with
-     * other setting getters, if this setting was set directly on this {@link Configurable} object, this simply returns
-     * that value, otherwise it returns the value from the parent {@link Configurable}. So beware, the returned value
-     * doesn't reflect the {@link List} concatenation logic that FreeMarker actually uses for this setting. The returned
-     * value is not the same {@link List} object that was set with {@link #setAutoIncludes(List)}, only its content is
-     * the same (except that duplicate are removed). The returned value isn't a snapshot; it may or may not shows the
-     * changes later made to this setting on this {@link Configurable} level (but usually it's well defined if until
-     * what point settings are possibly modified).
-     * 
-     * <p>
-     * The return value is never {@code null}; called on the {@link Configuration} (top) level, it defaults to an empty
-     * {@link List}.
-     * 
-     * @see #getAutoIncludesWithoutFallback()
-     * 
-     * @since 2.3.25
-     */
-    public List<String> getAutoIncludes() {
-        return autoIncludes != null ? autoIncludes : parent.getAutoIncludes();
-    }
-    
-    /**
-     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
-     * 
-     * @since 2.3.25
-     */
-    public boolean isAutoIncludesSet() {
-        return autoIncludes != null;
-    }
-    
-    /**
-     * Like {@link #getAutoIncludes()}, but doesn't fall back to the parent {@link Configurable} (and so it can be
-     * {@code null}).
-     *  
-     * @since 2.3.25
-     */
-    public List<String> getAutoIncludesWithoutFallback() {
-        return autoIncludes;
-    }
-    
-    /**
-     * Removes the auto-include from this {@link Configurable} level (not from the parents or children); see
-     * {@link #addAutoInclude(String)}. Does nothing if the template is not there.
-     */
-    public void removeAutoInclude(String templateName) {
-        // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration
-        synchronized (this) {
-            if (autoIncludes != null) {
-                autoIncludes.remove(templateName);
-            }
-        }
-    }
-    
-    private static final String ALLOWED_CLASSES = "allowed_classes";
-    private static final String TRUSTED_TEMPLATES = "trusted_templates";
-    
-    /**
-     * Sets a FreeMarker setting by a name and string value. If you can configure FreeMarker directly with Java (or
-     * other programming language), you should use the dedicated setter methods instead (like
-     * {@link #setObjectWrapper(ObjectWrapper)}. This meant to be used only when you get settings from somewhere
-     * as {@link String}-{@link String} name-value pairs (typically, as a {@link Properties} object). Below you find an
-     * overview of the settings available.
-     * 
-     * <p>Note: As of FreeMarker 2.3.23, setting names can be written in camel case too. For example, instead of
-     * {@code date_format} you can also use {@code dateFormat}. It's likely that camel case will become to the
-     * recommended convention in the future.
-     * 
-     * <p>The list of settings commonly supported in all {@link Configura

<TRUNCATED>