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 2015/12/19 17:43:03 UTC

[4/7] incubator-freemarker git commit: The new (in 2.3.24-pre01) TemplateConfigurer class was renamed to TemplateConfiguration, and the related configuration setting from template_configurers to template_configurations. Also, the TemplateConfigurer.confi

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/cache/TemplateConfigurationFactoryTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/cache/TemplateConfigurationFactoryTest.java b/src/test/java/freemarker/cache/TemplateConfigurationFactoryTest.java
new file mode 100644
index 0000000..663eed2
--- /dev/null
+++ b/src/test/java/freemarker/cache/TemplateConfigurationFactoryTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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 freemarker.cache;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+import freemarker.core.TemplateConfiguration;
+import freemarker.template.Configuration;
+
+public class TemplateConfigurationFactoryTest {
+    
+    private Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
+
+    @Test
+    public void testCondition1() throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration tc = newTemplateConfiguration(1);
+        
+        TemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.ftlx"), tc);
+        tcf.setConfiguration(cfg);
+        
+        assertNotApplicable(tcf, "x.ftl");
+        assertApplicable(tcf, "x.ftlx", tc);
+    }
+
+    @Test
+    public void testCondition2() throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration tc = newTemplateConfiguration(1);
+        
+        TemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(
+                new FileNameGlobMatcher("*.ftlx"),
+                new ConditionalTemplateConfigurationFactory(
+                        new FileNameGlobMatcher("x.*"), tc));
+        tcf.setConfiguration(cfg);
+        
+        assertNotApplicable(tcf, "x.ftl");
+        assertNotApplicable(tcf, "y.ftlx");
+        assertApplicable(tcf, "x.ftlx", tc);
+    }
+
+    @Test
+    public void testMerging() throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration tc1 = newTemplateConfiguration(1);
+        TemplateConfiguration tc2 = newTemplateConfiguration(2);
+        TemplateConfiguration tc3 = newTemplateConfiguration(3);
+        
+        TemplateConfigurationFactory tcf = new MergingTemplateConfigurationFactory(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*a*.*"), tc2),
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*b*.*"), tc3));
+        tcf.setConfiguration(cfg);
+        
+        assertNotApplicable(tcf, "x.ftl");
+        assertApplicable(tcf, "x.ftlx", tc1);
+        assertApplicable(tcf, "a.ftl", tc2);
+        assertApplicable(tcf, "b.ftl", tc3);
+        assertApplicable(tcf, "a.ftlx", tc1, tc2);
+        assertApplicable(tcf, "b.ftlx", tc1, tc3);
+        assertApplicable(tcf, "ab.ftl", tc2, tc3);
+        assertApplicable(tcf, "ab.ftlx", tc1, tc2, tc3);
+        
+        assertNotApplicable(new MergingTemplateConfigurationFactory(), "x.ftl");
+    }
+
+    @Test
+    public void testFirstMatch() throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration tc1 = newTemplateConfiguration(1);
+        TemplateConfiguration tc2 = newTemplateConfiguration(2);
+        TemplateConfiguration tc3 = newTemplateConfiguration(3);
+        
+        FirstMatchTemplateConfigurationFactory tcf = new FirstMatchTemplateConfigurationFactory(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*a*.*"), tc2),
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*b*.*"), tc3));
+        tcf.setConfiguration(cfg);
+
+        try {
+            assertNotApplicable(tcf, "x.ftl");
+        } catch (TemplateConfigurationFactoryException e) {
+            assertThat(e.getMessage(), containsString("x.ftl"));
+        }
+        tcf.setNoMatchErrorDetails("Test details");
+        try {
+            assertNotApplicable(tcf, "x.ftl");
+        } catch (TemplateConfigurationFactoryException e) {
+            assertThat(e.getMessage(), containsString("Test details"));
+        }
+        
+        tcf.setAllowNoMatch(true);
+        
+        assertNotApplicable(tcf, "x.ftl");
+        assertApplicable(tcf, "x.ftlx", tc1);
+        assertApplicable(tcf, "a.ftl", tc2);
+        assertApplicable(tcf, "b.ftl", tc3);
+        assertApplicable(tcf, "a.ftlx", tc1);
+        assertApplicable(tcf, "b.ftlx", tc1);
+        assertApplicable(tcf, "ab.ftl", tc2);
+        assertApplicable(tcf, "ab.ftlx", tc1);
+        
+        assertNotApplicable(new FirstMatchTemplateConfigurationFactory().allowNoMatch(true), "x.ftl");
+    }
+
+    @Test
+    public void testComplex() throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration tcA = newTemplateConfiguration(1);
+        TemplateConfiguration tcBSpec = newTemplateConfiguration(2);
+        TemplateConfiguration tcBCommon = newTemplateConfiguration(3);
+        TemplateConfiguration tcHH = newTemplateConfiguration(4);
+        TemplateConfiguration tcHtml = newTemplateConfiguration(5);
+        TemplateConfiguration tcXml = newTemplateConfiguration(6);
+        TemplateConfiguration tcNWS = newTemplateConfiguration(7);
+        
+        TemplateConfigurationFactory tcf = new MergingTemplateConfigurationFactory(
+                new FirstMatchTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new PathGlobMatcher("a/**"), tcA),
+                        new ConditionalTemplateConfigurationFactory(new PathGlobMatcher("b/**"),
+                                new MergingTemplateConfigurationFactory(
+                                    new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*"), tcBCommon),
+                                    new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.s.*"), tcBSpec))))
+                        .allowNoMatch(true),
+                new FirstMatchTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.hh"), tcHH),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.*h"), tcHtml),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.*x"), tcXml))
+                        .allowNoMatch(true),
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.nws.*"), tcNWS));
+        tcf.setConfiguration(cfg);
+        
+        assertNotApplicable(tcf, "x.ftl");
+        assertApplicable(tcf, "b/x.ftl", tcBCommon);
+        assertApplicable(tcf, "b/x.s.ftl", tcBCommon, tcBSpec);
+        assertApplicable(tcf, "b/x.s.ftlh", tcBCommon, tcBSpec, tcHtml);
+        assertApplicable(tcf, "b/x.s.nws.ftlx", tcBCommon, tcBSpec, tcXml, tcNWS);
+        assertApplicable(tcf, "a/x.s.nws.ftlx", tcA, tcXml, tcNWS);
+        assertApplicable(tcf, "a.hh", tcHH);
+        assertApplicable(tcf, "a.nws.hh", tcHH, tcNWS);
+    }
+
+    @Test
+    public void testSetConfiguration() {
+        TemplateConfiguration tc = new TemplateConfiguration();
+        ConditionalTemplateConfigurationFactory tcf = new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*"), tc);
+        assertNull(tcf.getConfiguration());
+        assertNull(tc.getParentConfiguration());
+        
+        tcf.setConfiguration(cfg);
+        assertEquals(cfg, tcf.getConfiguration());
+        assertEquals(cfg, tc.getParentConfiguration());
+        
+        // Ignored:
+        tcf.setConfiguration(cfg);
+        
+        try {
+            tcf.setConfiguration(Configuration.getDefaultConfiguration());
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), containsString("TemplateConfigurationFactory"));
+        }
+    }
+
+    @SuppressWarnings("boxing")
+    private TemplateConfiguration newTemplateConfiguration(int id) {
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.setCustomAttribute("id", id);
+        tc.setCustomAttribute("contains" + id, true);
+        return tc;
+    }
+
+    private void assertNotApplicable(TemplateConfigurationFactory tcf, String sourceName)
+            throws IOException, TemplateConfigurationFactoryException {
+        assertNull(tcf.get(sourceName, "dummy"));
+    }
+
+    private void assertApplicable(TemplateConfigurationFactory tcf, String sourceName, TemplateConfiguration... expectedTCs)
+            throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration mergedTC = tcf.get(sourceName, "dummy");
+        assertNotNull("TC should have its parents Configuration set", mergedTC.getParentConfiguration());
+        List<String> mergedTCAttNames = Arrays.asList(mergedTC.getCustomAttributeNames());
+
+        for (TemplateConfiguration expectedTC : expectedTCs) {
+            Integer tcId = (Integer) expectedTC.getCustomAttribute("id");
+            if (tcId == null) {
+                fail("TemplateConfiguration-s must be created with newTemplateConfiguration(id) in this test");
+            }
+            if (!mergedTCAttNames.contains("contains" + tcId)) {
+                fail("TemplateConfiguration with ID " + tcId + " is missing from the asserted value");
+            }
+        }
+        
+        for (String attName: mergedTCAttNames) {
+            if (!containsCustomAttr(attName, expectedTCs)) {
+                fail("The asserted TemplateConfiguration contains an unexpected custom attribute: " + attName);
+            }
+        }
+        
+        assertEquals(expectedTCs[expectedTCs.length - 1].getCustomAttribute("id"), mergedTC.getCustomAttribute("id"));
+    }
+
+    private boolean containsCustomAttr(String attName, TemplateConfiguration... expectedTCs) {
+        for (TemplateConfiguration expectedTC : expectedTCs) {
+            if (expectedTC.getCustomAttribute(attName) != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/cache/TemplateConfigurerFactoryTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/cache/TemplateConfigurerFactoryTest.java b/src/test/java/freemarker/cache/TemplateConfigurerFactoryTest.java
deleted file mode 100644
index 500d088..0000000
--- a/src/test/java/freemarker/cache/TemplateConfigurerFactoryTest.java
+++ /dev/null
@@ -1,230 +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 freemarker.cache;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-import org.junit.Test;
-
-import freemarker.core.TemplateConfigurer;
-import freemarker.template.Configuration;
-
-public class TemplateConfigurerFactoryTest {
-    
-    private Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
-
-    @Test
-    public void testCondition1() throws IOException, TemplateConfigurerFactoryException {
-        TemplateConfigurer tc = newTemplateConfigurer(1);
-        
-        TemplateConfigurerFactory tcf = new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.ftlx"), tc);
-        tcf.setConfiguration(cfg);
-        
-        assertNotApplicable(tcf, "x.ftl");
-        assertApplicable(tcf, "x.ftlx", tc);
-    }
-
-    @Test
-    public void testCondition2() throws IOException, TemplateConfigurerFactoryException {
-        TemplateConfigurer tc = newTemplateConfigurer(1);
-        
-        TemplateConfigurerFactory tcf = new ConditionalTemplateConfigurerFactory(
-                new FileNameGlobMatcher("*.ftlx"),
-                new ConditionalTemplateConfigurerFactory(
-                        new FileNameGlobMatcher("x.*"), tc));
-        tcf.setConfiguration(cfg);
-        
-        assertNotApplicable(tcf, "x.ftl");
-        assertNotApplicable(tcf, "y.ftlx");
-        assertApplicable(tcf, "x.ftlx", tc);
-    }
-
-    @Test
-    public void testMerging() throws IOException, TemplateConfigurerFactoryException {
-        TemplateConfigurer tc1 = newTemplateConfigurer(1);
-        TemplateConfigurer tc2 = newTemplateConfigurer(2);
-        TemplateConfigurer tc3 = newTemplateConfigurer(3);
-        
-        TemplateConfigurerFactory tcf = new MergingTemplateConfigurerFactory(
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*a*.*"), tc2),
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*b*.*"), tc3));
-        tcf.setConfiguration(cfg);
-        
-        assertNotApplicable(tcf, "x.ftl");
-        assertApplicable(tcf, "x.ftlx", tc1);
-        assertApplicable(tcf, "a.ftl", tc2);
-        assertApplicable(tcf, "b.ftl", tc3);
-        assertApplicable(tcf, "a.ftlx", tc1, tc2);
-        assertApplicable(tcf, "b.ftlx", tc1, tc3);
-        assertApplicable(tcf, "ab.ftl", tc2, tc3);
-        assertApplicable(tcf, "ab.ftlx", tc1, tc2, tc3);
-        
-        assertNotApplicable(new MergingTemplateConfigurerFactory(), "x.ftl");
-    }
-
-    @Test
-    public void testFirstMatch() throws IOException, TemplateConfigurerFactoryException {
-        TemplateConfigurer tc1 = newTemplateConfigurer(1);
-        TemplateConfigurer tc2 = newTemplateConfigurer(2);
-        TemplateConfigurer tc3 = newTemplateConfigurer(3);
-        
-        FirstMatchTemplateConfigurerFactory tcf = new FirstMatchTemplateConfigurerFactory(
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*a*.*"), tc2),
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*b*.*"), tc3));
-        tcf.setConfiguration(cfg);
-
-        try {
-            assertNotApplicable(tcf, "x.ftl");
-        } catch (TemplateConfigurerFactoryException e) {
-            assertThat(e.getMessage(), containsString("x.ftl"));
-        }
-        tcf.setNoMatchErrorDetails("Test details");
-        try {
-            assertNotApplicable(tcf, "x.ftl");
-        } catch (TemplateConfigurerFactoryException e) {
-            assertThat(e.getMessage(), containsString("Test details"));
-        }
-        
-        tcf.setAllowNoMatch(true);
-        
-        assertNotApplicable(tcf, "x.ftl");
-        assertApplicable(tcf, "x.ftlx", tc1);
-        assertApplicable(tcf, "a.ftl", tc2);
-        assertApplicable(tcf, "b.ftl", tc3);
-        assertApplicable(tcf, "a.ftlx", tc1);
-        assertApplicable(tcf, "b.ftlx", tc1);
-        assertApplicable(tcf, "ab.ftl", tc2);
-        assertApplicable(tcf, "ab.ftlx", tc1);
-        
-        assertNotApplicable(new FirstMatchTemplateConfigurerFactory().allowNoMatch(true), "x.ftl");
-    }
-
-    @Test
-    public void testComplex() throws IOException, TemplateConfigurerFactoryException {
-        TemplateConfigurer tcA = newTemplateConfigurer(1);
-        TemplateConfigurer tcBSpec = newTemplateConfigurer(2);
-        TemplateConfigurer tcBCommon = newTemplateConfigurer(3);
-        TemplateConfigurer tcHH = newTemplateConfigurer(4);
-        TemplateConfigurer tcHtml = newTemplateConfigurer(5);
-        TemplateConfigurer tcXml = newTemplateConfigurer(6);
-        TemplateConfigurer tcNWS = newTemplateConfigurer(7);
-        
-        TemplateConfigurerFactory tcf = new MergingTemplateConfigurerFactory(
-                new FirstMatchTemplateConfigurerFactory(
-                        new ConditionalTemplateConfigurerFactory(new PathGlobMatcher("a/**"), tcA),
-                        new ConditionalTemplateConfigurerFactory(new PathGlobMatcher("b/**"),
-                                new MergingTemplateConfigurerFactory(
-                                    new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*"), tcBCommon),
-                                    new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.s.*"), tcBSpec))))
-                        .allowNoMatch(true),
-                new FirstMatchTemplateConfigurerFactory(
-                        new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.hh"), tcHH),
-                        new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.*h"), tcHtml),
-                        new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.*x"), tcXml))
-                        .allowNoMatch(true),
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.nws.*"), tcNWS));
-        tcf.setConfiguration(cfg);
-        
-        assertNotApplicable(tcf, "x.ftl");
-        assertApplicable(tcf, "b/x.ftl", tcBCommon);
-        assertApplicable(tcf, "b/x.s.ftl", tcBCommon, tcBSpec);
-        assertApplicable(tcf, "b/x.s.ftlh", tcBCommon, tcBSpec, tcHtml);
-        assertApplicable(tcf, "b/x.s.nws.ftlx", tcBCommon, tcBSpec, tcXml, tcNWS);
-        assertApplicable(tcf, "a/x.s.nws.ftlx", tcA, tcXml, tcNWS);
-        assertApplicable(tcf, "a.hh", tcHH);
-        assertApplicable(tcf, "a.nws.hh", tcHH, tcNWS);
-    }
-
-    @Test
-    public void testSetConfiguration() {
-        TemplateConfigurer tc = new TemplateConfigurer();
-        ConditionalTemplateConfigurerFactory tcf = new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*"), tc);
-        assertNull(tcf.getConfiguration());
-        assertNull(tc.getParentConfiguration());
-        
-        tcf.setConfiguration(cfg);
-        assertEquals(cfg, tcf.getConfiguration());
-        assertEquals(cfg, tc.getParentConfiguration());
-        
-        // Ignored:
-        tcf.setConfiguration(cfg);
-        
-        try {
-            tcf.setConfiguration(Configuration.getDefaultConfiguration());
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), containsString("TemplateConfigurerFactory"));
-        }
-    }
-
-    @SuppressWarnings("boxing")
-    private TemplateConfigurer newTemplateConfigurer(int id) {
-        TemplateConfigurer tc = new TemplateConfigurer();
-        tc.setCustomAttribute("id", id);
-        tc.setCustomAttribute("contains" + id, true);
-        return tc;
-    }
-
-    private void assertNotApplicable(TemplateConfigurerFactory tcf, String sourceName)
-            throws IOException, TemplateConfigurerFactoryException {
-        assertNull(tcf.get(sourceName, "dummy"));
-    }
-
-    private void assertApplicable(TemplateConfigurerFactory tcf, String sourceName, TemplateConfigurer... expectedTCs)
-            throws IOException, TemplateConfigurerFactoryException {
-        TemplateConfigurer mergedTC = tcf.get(sourceName, "dummy");
-        assertNotNull("TC should have its parents Configuration set", mergedTC.getParentConfiguration());
-        List<String> mergedTCAttNames = Arrays.asList(mergedTC.getCustomAttributeNames());
-
-        for (TemplateConfigurer expectedTC : expectedTCs) {
-            Integer tcId = (Integer) expectedTC.getCustomAttribute("id");
-            if (tcId == null) {
-                fail("TemplateConfigurer-s must be created with newTemplateConfigurer(id) in this test");
-            }
-            if (!mergedTCAttNames.contains("contains" + tcId)) {
-                fail("TemplateConfigurer with ID " + tcId + " is missing from the asserted value");
-            }
-        }
-        
-        for (String attName: mergedTCAttNames) {
-            if (!containsCustomAttr(attName, expectedTCs)) {
-                fail("The asserted TemplateConfigurer contains an unexpected custom attribute: " + attName);
-            }
-        }
-        
-        assertEquals(expectedTCs[expectedTCs.length - 1].getCustomAttribute("id"), mergedTC.getCustomAttribute("id"));
-    }
-
-    private boolean containsCustomAttr(String attName, TemplateConfigurer... expectedTCs) {
-        for (TemplateConfigurer expectedTC : expectedTCs) {
-            if (expectedTC.getCustomAttribute(attName) != null) {
-                return true;
-            }
-        }
-        return false;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index 20b2cc9..792b9b2 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -34,7 +34,7 @@ import org.junit.Test;
 
 import com.google.common.collect.ImmutableMap;
 
-import freemarker.cache.ConditionalTemplateConfigurerFactory;
+import freemarker.cache.ConditionalTemplateConfigurationFactory;
 import freemarker.cache.FileNameGlobMatcher;
 import freemarker.template.Configuration;
 import freemarker.template.SimpleDate;
@@ -347,11 +347,11 @@ public class DateFormatTest extends TemplateTest {
                 "m", new AliasTemplateDateFormatFactory("yyyy-MMM"),
                 "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE));
         
-        TemplateConfigurer tc = new TemplateConfigurer();
+        TemplateConfiguration tc = new TemplateConfiguration();
         tc.setCustomDateFormats(ImmutableMap.of(
                 "m", new AliasTemplateDateFormatFactory("yyyy-MMMM"),
                 "i", new AliasTemplateDateFormatFactory("@epoch")));
-        cfg.setTemplateConfigurers(new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*2*"), tc));
+        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tc));
         
         addToDataModel("d", TM);
         String commonFtl = "${d?string.@d} ${d?string.@m} "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index 19d2cba..dee402f 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -33,7 +33,7 @@ import org.junit.Test;
 
 import com.google.common.collect.ImmutableMap;
 
-import freemarker.cache.ConditionalTemplateConfigurerFactory;
+import freemarker.cache.ConditionalTemplateConfigurationFactory;
 import freemarker.cache.FileNameGlobMatcher;
 import freemarker.template.Configuration;
 import freemarker.template.SimpleNumber;
@@ -251,11 +251,11 @@ public class NumberFormatTest extends TemplateTest {
                 "d", new AliasTemplateNumberFormatFactory("0.0#"),
                 "hex", HexTemplateNumberFormatFactory.INSTANCE));
         
-        TemplateConfigurer tc = new TemplateConfigurer();
+        TemplateConfiguration tc = new TemplateConfiguration();
         tc.setCustomNumberFormats(ImmutableMap.of(
                 "d", new AliasTemplateNumberFormatFactory("0.#'d'"),
                 "i", new AliasTemplateNumberFormatFactory("@hex")));
-        cfg.setTemplateConfigurers(new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*2*"), tc));
+        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tc));
         
         String commonFtl = "${1?string.@f} ${1?string.@d} "
                 + "<#setting locale='fr_FR'>${1.5?string.@d} "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/core/OutputFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/OutputFormatTest.java b/src/test/java/freemarker/core/OutputFormatTest.java
index b596437..cc31920 100644
--- a/src/test/java/freemarker/core/OutputFormatTest.java
+++ b/src/test/java/freemarker/core/OutputFormatTest.java
@@ -29,7 +29,7 @@ import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
 
-import freemarker.cache.ConditionalTemplateConfigurerFactory;
+import freemarker.cache.ConditionalTemplateConfigurationFactory;
 import freemarker.cache.FileNameGlobMatcher;
 import freemarker.cache.OrMatcher;
 import freemarker.template.Configuration;
@@ -111,10 +111,10 @@ public class OutputFormatTest extends TemplateTest {
             case 3:
                 cfgOutputFormat = UndefinedOutputFormat.INSTANCE;
                 cfg.unsetOutputFormat();
-                TemplateConfigurer tcXml = new TemplateConfigurer();
+                TemplateConfiguration tcXml = new TemplateConfiguration();
                 tcXml.setOutputFormat(XMLOutputFormat.INSTANCE);
-                cfg.setTemplateConfigurers(
-                        new ConditionalTemplateConfigurerFactory(
+                cfg.setTemplateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
                                 new OrMatcher(
                                         new FileNameGlobMatcher("*.ftlh"),
                                         new FileNameGlobMatcher("*.FTLH"),
@@ -130,7 +130,7 @@ public class OutputFormatTest extends TemplateTest {
                 ftlxOutputFormat = UndefinedOutputFormat.INSTANCE;
                 break;
             case 5:
-                cfg.setTemplateConfigurers(null);
+                cfg.setTemplateConfigurations(null);
                 cfgOutputFormat = UndefinedOutputFormat.INSTANCE;
                 ftlhOutputFormat = UndefinedOutputFormat.INSTANCE;
                 ftlxOutputFormat = UndefinedOutputFormat.INSTANCE;
@@ -182,25 +182,25 @@ public class OutputFormatTest extends TemplateTest {
         addTemplate("t.ftl",
                 "${'{}'} ${'{}'?esc} ${'{}'?noEsc}");
         
-        TemplateConfigurer tcHTML = new TemplateConfigurer();
+        TemplateConfiguration tcHTML = new TemplateConfiguration();
         tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE);
-        ConditionalTemplateConfigurerFactory tcfHTML = new ConditionalTemplateConfigurerFactory(
+        ConditionalTemplateConfigurationFactory tcfHTML = new ConditionalTemplateConfigurationFactory(
                 new FileNameGlobMatcher("t.*"), tcHTML);
 
-        TemplateConfigurer tcNoAutoEsc = new TemplateConfigurer();
+        TemplateConfiguration tcNoAutoEsc = new TemplateConfiguration();
         tcNoAutoEsc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
-        ConditionalTemplateConfigurerFactory tcfNoAutoEsc = new ConditionalTemplateConfigurerFactory(
+        ConditionalTemplateConfigurationFactory tcfNoAutoEsc = new ConditionalTemplateConfigurationFactory(
                 new FileNameGlobMatcher("t.*"), tcNoAutoEsc);
 
         Configuration cfg = getConfiguration();
         cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setTemplateConfigurers(tcfHTML);
+        cfg.setTemplateConfigurations(tcfHTML);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setTemplateConfigurers(tcfNoAutoEsc);
+        cfg.setTemplateConfigurations(tcfNoAutoEsc);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
         
-        cfg.setTemplateConfigurers(null);
+        cfg.setTemplateConfigurations(null);
         cfg.unsetOutputFormat();
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_23);  // Extensions has no effect
         assertErrorContainsForNamed("t.ftlx", UndefinedOutputFormat.INSTANCE.getName());
@@ -208,21 +208,21 @@ public class OutputFormatTest extends TemplateTest {
         assertOutputForNamed("t.ftlx", "&#39; &#39; '");
         cfg.setOutputFormat(XMLOutputFormat.INSTANCE);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");
-        cfg.setTemplateConfigurers(tcfHTML);
+        cfg.setTemplateConfigurations(tcfHTML);
         assertOutputForNamed("t.ftlx", "&#39; &#39; '");
-        cfg.setTemplateConfigurers(tcfNoAutoEsc);
+        cfg.setTemplateConfigurations(tcfNoAutoEsc);
         assertOutputForNamed("t.ftlx", "' &apos; '");
         
         cfg.setRecognizeStandardFileExtensions(true);
-        cfg.setTemplateConfigurers(tcfHTML);
+        cfg.setTemplateConfigurations(tcfHTML);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
-        cfg.setTemplateConfigurers(tcfNoAutoEsc);
+        cfg.setTemplateConfigurations(tcfNoAutoEsc);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
         
-        cfg.setTemplateConfigurers(null);
+        cfg.setTemplateConfigurations(null);
         cfg.unsetOutputFormat();
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
-        cfg.setTemplateConfigurers(tcfHTML);
+        cfg.setTemplateConfigurations(tcfHTML);
         assertOutputForNamed("t.ftlx", "&apos; &apos; '");  // Can't override it
         cfg.setRecognizeStandardFileExtensions(false);
         assertOutputForNamed("t.ftlx", "&#39; &#39; '");
@@ -1005,10 +1005,10 @@ public class OutputFormatTest extends TemplateTest {
     protected Configuration createConfiguration() throws TemplateModelException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_24);
         
-        TemplateConfigurer xmlTC = new TemplateConfigurer();
+        TemplateConfiguration xmlTC = new TemplateConfiguration();
         xmlTC.setOutputFormat(XMLOutputFormat.INSTANCE);
-        cfg.setTemplateConfigurers(
-                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.xml"), xmlTC));
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.xml"), xmlTC));
 
         cfg.setSharedVariable("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b"));
         cfg.setSharedVariable("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c"));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/TemplateConfigurationTest.java b/src/test/java/freemarker/core/TemplateConfigurationTest.java
new file mode 100644
index 0000000..99b1b7a
--- /dev/null
+++ b/src/test/java/freemarker/core/TemplateConfigurationTest.java
@@ -0,0 +1,846 @@
+/*
+ * 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 freemarker.core;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import freemarker.template.Configuration;
+import freemarker.template.SimpleObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+import freemarker.template.Version;
+import freemarker.template.utility.NullArgumentException;
+
+@SuppressWarnings("boxing")
+public class TemplateConfigurationTest {
+
+    private final class DummyArithmeticEngine extends ArithmeticEngine {
+
+        @Override
+        public int compareNumbers(Number first, Number second) throws TemplateException {
+            return 0;
+        }
+
+        @Override
+        public Number add(Number first, Number second) throws TemplateException {
+            return 22;
+        }
+
+        @Override
+        public Number subtract(Number first, Number second) throws TemplateException {
+            return null;
+        }
+
+        @Override
+        public Number multiply(Number first, Number second) throws TemplateException {
+            return 33;
+        }
+
+        @Override
+        public Number divide(Number first, Number second) throws TemplateException {
+            return null;
+        }
+
+        @Override
+        public Number modulus(Number first, Number second) throws TemplateException {
+            return null;
+        }
+
+        @Override
+        public Number toNumber(String s) {
+            return 11;
+        }
+    }
+
+    private static final Version ICI = Configuration.VERSION_2_3_22;
+
+    private static final Configuration DEFAULT_CFG = new Configuration(ICI);
+
+    private static final TimeZone NON_DEFAULT_TZ;
+    static {
+        TimeZone defaultTZ = DEFAULT_CFG.getTimeZone();
+        TimeZone tz = TimeZone.getTimeZone("UTC");
+        if (tz.equals(defaultTZ)) {
+            tz = TimeZone.getTimeZone("GMT+01");
+            if (tz.equals(defaultTZ)) {
+                throw new AssertionError("Couldn't chose a non-default time zone");
+            }
+        }
+        NON_DEFAULT_TZ = tz;
+    }
+
+    private static final Locale NON_DEFAULT_LOCALE;
+    static {
+        Locale defaultLocale = DEFAULT_CFG.getLocale();
+        Locale locale = Locale.GERMAN;
+        if (locale.equals(defaultLocale)) {
+            locale = Locale.US;
+            if (locale.equals(defaultLocale)) {
+                throw new AssertionError("Couldn't chose a non-default locale");
+            }
+        }
+        NON_DEFAULT_LOCALE = locale;
+    }
+
+    private static final String NON_DEFAULT_ENCODING;
+
+    static {
+        String defaultEncoding = DEFAULT_CFG.getDefaultEncoding();
+        String encoding = "UTF-16";
+        if (encoding.equals(defaultEncoding)) {
+            encoding = "UTF-8";
+            if (encoding.equals(defaultEncoding)) {
+                throw new AssertionError("Couldn't chose a non-default locale");
+            }
+        }
+        NON_DEFAULT_ENCODING = encoding;
+    }
+    
+    private static final Map<String, Object> SETTING_ASSIGNMENTS;
+
+    static {
+        SETTING_ASSIGNMENTS = new HashMap<String, Object>();
+
+        // "Configurable" settings:
+        SETTING_ASSIGNMENTS.put("APIBuiltinEnabled", true);
+        SETTING_ASSIGNMENTS.put("SQLDateAndTimeTimeZone", NON_DEFAULT_TZ);
+        SETTING_ASSIGNMENTS.put("URLEscapingCharset", "utf-16");
+        SETTING_ASSIGNMENTS.put("autoFlush", false);
+        SETTING_ASSIGNMENTS.put("booleanFormat", "J,N");
+        SETTING_ASSIGNMENTS.put("classicCompatibleAsInt", 2);
+        SETTING_ASSIGNMENTS.put("dateFormat", "yyyy-#DDD");
+        SETTING_ASSIGNMENTS.put("dateTimeFormat", "yyyy-#DDD-@HH:mm");
+        SETTING_ASSIGNMENTS.put("locale", NON_DEFAULT_LOCALE);
+        SETTING_ASSIGNMENTS.put("logTemplateExceptions", false);
+        SETTING_ASSIGNMENTS.put("newBuiltinClassResolver", TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
+        SETTING_ASSIGNMENTS.put("numberFormat", "0.0000");
+        SETTING_ASSIGNMENTS.put("objectWrapper", new SimpleObjectWrapper(ICI));
+        SETTING_ASSIGNMENTS.put("outputEncoding", "utf-16");
+        SETTING_ASSIGNMENTS.put("showErrorTips", false);
+        SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE_HANDLER);
+        SETTING_ASSIGNMENTS.put("timeFormat", "@HH:mm");
+        SETTING_ASSIGNMENTS.put("timeZone", NON_DEFAULT_TZ);
+        SETTING_ASSIGNMENTS.put("arithmeticEngine", ArithmeticEngine.CONSERVATIVE_ENGINE);
+        SETTING_ASSIGNMENTS.put("customNumberFormats",
+                ImmutableMap.of("dummy", HexTemplateNumberFormatFactory.INSTANCE));
+        SETTING_ASSIGNMENTS.put("customDateFormats",
+                ImmutableMap.of("dummy", EpochMillisTemplateDateFormatFactory.INSTANCE));
+
+        // Parser-only settings:
+        SETTING_ASSIGNMENTS.put("tagSyntax", Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        SETTING_ASSIGNMENTS.put("namingConvention", Configuration.LEGACY_NAMING_CONVENTION);
+        SETTING_ASSIGNMENTS.put("whitespaceStripping", false);
+        SETTING_ASSIGNMENTS.put("strictSyntaxMode", false);
+        SETTING_ASSIGNMENTS.put("autoEscapingPolicy", Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+        SETTING_ASSIGNMENTS.put("outputFormat", HTMLOutputFormat.INSTANCE);
+        SETTING_ASSIGNMENTS.put("recognizeStandardFileExtensions", true);
+        
+        // Special settings:
+        SETTING_ASSIGNMENTS.put("encoding", NON_DEFAULT_ENCODING);
+    }
+    
+    public static String getIsSetMethodName(String readMethodName) {
+        String isSetMethodName = (readMethodName.startsWith("get") ? "is" + readMethodName.substring(3)
+                : readMethodName)
+                + "Set";
+        if (isSetMethodName.equals("isClassicCompatibleAsIntSet")) {
+            isSetMethodName = "isClassicCompatibleSet";
+        }
+        return isSetMethodName;
+    }
+
+    public static List<PropertyDescriptor> getTemplateConfigurationSettingPropDescs(
+            boolean includeCompilerSettings, boolean includeSpecialSettings)
+            throws IntrospectionException {
+        List<PropertyDescriptor> settingPropDescs = new ArrayList<PropertyDescriptor>();
+
+        BeanInfo beanInfo = Introspector.getBeanInfo(TemplateConfiguration.class);
+        for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
+            String name = pd.getName();
+            if (pd.getWriteMethod() != null && !IGNORED_PROP_NAMES.contains(name)
+                    && (includeCompilerSettings
+                            || (CONFIGURABLE_PROP_NAMES.contains(name) || !PARSER_PROP_NAMES.contains(name)))
+                    && (includeSpecialSettings
+                            || !SPECIAL_PROP_NAMES.contains(name))) {
+                if (pd.getReadMethod() == null) {
+                    throw new AssertionError("Property has no read method: " + pd);
+                }
+                settingPropDescs.add(pd);
+            }
+        }
+
+        Collections.sort(settingPropDescs, new Comparator<PropertyDescriptor>() {
+
+            public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+                return o1.getName().compareToIgnoreCase(o2.getName());
+            }
+        });
+
+        return settingPropDescs;
+    }
+
+    private static final Set<String> IGNORED_PROP_NAMES;
+
+    static {
+        IGNORED_PROP_NAMES = new HashSet();
+        IGNORED_PROP_NAMES.add("class");
+        IGNORED_PROP_NAMES.add("strictBeanModels");
+        IGNORED_PROP_NAMES.add("parentConfiguration");
+        IGNORED_PROP_NAMES.add("settings");
+        IGNORED_PROP_NAMES.add("classicCompatible");
+    }
+
+    private static final Set<String> CONFIGURABLE_PROP_NAMES;
+    static {
+        CONFIGURABLE_PROP_NAMES = new HashSet<String>();
+        try {
+            for (PropertyDescriptor propDesc : Introspector.getBeanInfo(Configurable.class).getPropertyDescriptors()) {
+                String propName = propDesc.getName();
+                if (!IGNORED_PROP_NAMES.contains(propName)) {
+                    CONFIGURABLE_PROP_NAMES.add(propName);
+                }
+            }
+        } catch (IntrospectionException e) {
+            throw new IllegalStateException("Failed to init static field", e);
+        }
+    }
+    
+    private static final Set<String> PARSER_PROP_NAMES;
+    static {
+        PARSER_PROP_NAMES = new HashSet<String>();
+        // 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);
+            } else {
+                propertyName = null;
+            }
+            if (propertyName != null) {
+                if (!Character.isUpperCase(propertyName.charAt(1))) {
+                    propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
+                }
+                PARSER_PROP_NAMES.add(propertyName);
+            }
+        }
+    }
+
+    private static final Set<String> SPECIAL_PROP_NAMES;
+    static {
+        SPECIAL_PROP_NAMES = new HashSet<String>();
+        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); 
+
+    @Test
+    public void testMergeBasicFunctionality() throws Exception {
+        for (PropertyDescriptor propDesc1 : getTemplateConfigurationSettingPropDescs(true, true)) {
+            for (PropertyDescriptor propDesc2 : getTemplateConfigurationSettingPropDescs(true, true)) {
+                TemplateConfiguration tc1 = new TemplateConfiguration();
+                TemplateConfiguration tc2 = new TemplateConfiguration();
+
+                Object value1 = SETTING_ASSIGNMENTS.get(propDesc1.getName());
+                propDesc1.getWriteMethod().invoke(tc1, value1);
+                Object value2 = SETTING_ASSIGNMENTS.get(propDesc2.getName());
+                propDesc2.getWriteMethod().invoke(tc2, value2);
+
+                tc1.merge(tc2);
+                Object mValue1 = propDesc1.getReadMethod().invoke(tc1);
+                Object mValue2 = propDesc2.getReadMethod().invoke(tc1);
+
+                assertEquals("For " + propDesc1.getName(), value1, mValue1);
+                assertEquals("For " + propDesc2.getName(), value2, mValue2);
+            }
+        }
+    }
+    
+    @Test
+    public void testMergeMapSettings() throws Exception {
+        TemplateConfiguration tc1 = new TemplateConfiguration();
+        tc1.setCustomDateFormats(ImmutableMap.of(
+                "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
+                "x", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE));
+        tc1.setCustomNumberFormats(ImmutableMap.of(
+                "hex", HexTemplateNumberFormatFactory.INSTANCE,
+                "x", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE));
+        
+        TemplateConfiguration tc2 = new TemplateConfiguration();
+        tc2.setCustomDateFormats(ImmutableMap.of(
+                "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
+                "x", EpochMillisDivTemplateDateFormatFactory.INSTANCE));
+        tc2.setCustomNumberFormats(ImmutableMap.of(
+                "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
+                "x", BaseNTemplateNumberFormatFactory.INSTANCE));
+        
+        tc1.merge(tc2);
+        
+        Map<String, ? extends TemplateDateFormatFactory> mergedCustomDateFormats = tc1.getCustomDateFormats();
+        assertEquals(EpochMillisTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("epoch"));
+        assertEquals(LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("loc"));
+        assertEquals(EpochMillisDivTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("x"));
+        
+        Map<String, ? extends TemplateNumberFormatFactory> mergedCustomNumberFormats = tc1.getCustomNumberFormats();
+        assertEquals(HexTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("hex"));
+        assertEquals(LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("loc"));
+        assertEquals(BaseNTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("x"));
+        
+        // Empty map merging optimization:
+        tc1.merge(new TemplateConfiguration());
+        assertSame(mergedCustomDateFormats, tc1.getCustomDateFormats());
+        assertSame(mergedCustomNumberFormats, tc1.getCustomNumberFormats());
+        
+        // Empty map merging optimization:
+        TemplateConfiguration tc3 = new TemplateConfiguration();
+        tc3.merge(tc1);
+        assertSame(mergedCustomDateFormats, tc3.getCustomDateFormats());
+        assertSame(mergedCustomNumberFormats, tc3.getCustomNumberFormats());
+    }
+    
+    @Test
+    public void testMergePriority() throws Exception {
+        TemplateConfiguration tc1 = new TemplateConfiguration();
+        tc1.setDateFormat("1");
+        tc1.setTimeFormat("1");
+        tc1.setDateTimeFormat("1");
+
+        TemplateConfiguration tc2 = new TemplateConfiguration();
+        tc2.setDateFormat("2");
+        tc2.setTimeFormat("2");
+
+        TemplateConfiguration tc3 = new TemplateConfiguration();
+        tc3.setDateFormat("3");
+
+        tc1.merge(tc2);
+        tc1.merge(tc3);
+
+        assertEquals("3", tc1.getDateFormat());
+        assertEquals("2", tc1.getTimeFormat());
+        assertEquals("1", tc1.getDateTimeFormat());
+    }
+    
+    @Test
+    public void testMergeCustomAttributes() throws Exception {
+        TemplateConfiguration tc1 = new TemplateConfiguration();
+        tc1.setCustomAttribute("k1", "v1");
+        tc1.setCustomAttribute("k2", "v1");
+        tc1.setCustomAttribute("k3", "v1");
+        CA1.set("V1", tc1);
+        CA2.set("V1", tc1);
+        CA3.set("V1", tc1);
+
+        TemplateConfiguration tc2 = new TemplateConfiguration();
+        tc2.setCustomAttribute("k1", "v2");
+        tc2.setCustomAttribute("k2", "v2");
+        CA1.set("V2", tc2);
+        CA2.set("V2", tc2);
+
+        TemplateConfiguration tc3 = new TemplateConfiguration();
+        tc3.setCustomAttribute("k1", "v3");
+        CA1.set("V3", tc2);
+
+        tc1.merge(tc2);
+        tc1.merge(tc3);
+
+        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));
+    }
+    
+    @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);
+        
+        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));
+
+        TemplateConfiguration tc2 = new TemplateConfiguration();
+        tc2.setCustomAttribute("k1", "v2");
+        tc2.setCustomAttribute("k2", null);
+        CA1.set("V2", tc2);
+        CA2.set(null, tc2);
+
+        TemplateConfiguration tc3 = new TemplateConfiguration();
+        tc3.setCustomAttribute("k1", null);
+        CA1.set(null, tc2);
+
+        tc1.merge(tc2);
+        tc1.merge(tc3);
+
+        assertNull(tc1.getCustomAttribute("k1"));
+        assertNull(tc1.getCustomAttribute("k2"));
+        assertNull(tc1.getCustomAttribute("k3"));
+        assertNull(CA1.get(tc1));
+        assertNull(CA2.get(tc1));
+        assertNull(CA3.get(tc1));
+        
+        TemplateConfiguration tc4 = new TemplateConfiguration();
+        tc4.setCustomAttribute("k1", "v4");
+        CA1.set("V4", tc4);
+        
+        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));
+    }
+
+    @Test
+    public void testConfigureNonParserConfig() throws Exception {
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(false, true)) {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+    
+            Object newValue = SETTING_ASSIGNMENTS.get(pd.getName());
+            pd.getWriteMethod().invoke(tc, newValue);
+            
+            Template t = new Template(null, "", DEFAULT_CFG);
+            Method tReaderMethod = t.getClass().getMethod(pd.getReadMethod().getName());
+            
+            assertNotEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+            tc.apply(t);
+            assertEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+        }
+    }
+    
+    @Test
+    public void testConfigureCustomAttributes() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
+        cfg.setCustomAttribute("k1", "c");
+        cfg.setCustomAttribute("k2", "c");
+        cfg.setCustomAttribute("k3", "c");
+
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.setCustomAttribute("k2", "tc");
+        tc.setCustomAttribute("k3", null);
+        tc.setCustomAttribute("k4", "tc");
+        tc.setCustomAttribute("k5", "tc");
+        tc.setCustomAttribute("k6", "tc");
+        CA1.set("tc", tc);
+        CA2.set("tc", tc);
+        CA3.set("tc", 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);
+        
+        tc.setParentConfiguration(cfg);
+        tc.apply(t);
+        
+        assertEquals("c", t.getCustomAttribute("k1"));
+        assertEquals("tc", t.getCustomAttribute("k2"));
+        assertNull(t.getCustomAttribute("k3"));
+        assertEquals("tc", t.getCustomAttribute("k4"));
+        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));
+    }
+    
+    @Test
+    public void testConfigureParser() throws Exception {
+        Set<String> testedProps = new HashSet<String>();
+        
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+            assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y");
+            testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE);
+        }
+        
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+            assertOutputWithoutAndWithTC(tc, "<#if true>y<#elseif false>n</#if>", "y", null);
+            testedProps.add(Configuration.NAMING_CONVENTION_KEY_CAMEL_CASE);
+        }
+        
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setWhitespaceStripping(false);
+            assertOutputWithoutAndWithTC(tc, "<#if true>\nx\n</#if>\n", "x\n", "\nx\n\n");
+            testedProps.add(Configuration.WHITESPACE_STRIPPING_KEY_CAMEL_CASE);
+        }
+
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setArithmeticEngine(new DummyArithmeticEngine());
+            assertOutputWithoutAndWithTC(tc, "${1} ${1+1}", "1 2", "11 22");
+            testedProps.add(Configuration.ARITHMETIC_ENGINE_KEY_CAMEL_CASE);
+        }
+
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setOutputFormat(XMLOutputFormat.INSTANCE);
+            assertOutputWithoutAndWithTC(tc, "${.outputFormat} ${\"a'b\"}",
+                    UndefinedOutputFormat.INSTANCE.getName() + " a'b",
+                    XMLOutputFormat.INSTANCE.getName() + " a&apos;b");
+            testedProps.add(Configuration.OUTPUT_FORMAT_KEY_CAMEL_CASE);
+        }
+
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setOutputFormat(XMLOutputFormat.INSTANCE);
+            tc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+            assertOutputWithoutAndWithTC(tc, "${'a&b'}", "a&b", "a&b");
+            testedProps.add(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE);
+        }
+        
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setStrictSyntaxMode(false);
+            assertOutputWithoutAndWithTC(tc, "<if true>y</if>", "<if true>y</if>", "y");
+            testedProps.add("strictSyntaxMode");
+        }
+
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(new Configuration(new Version(2, 3, 0)));
+            assertOutputWithoutAndWithTC(tc, "<#foo>", null, "<#foo>");
+            testedProps.add(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE);
+        }
+
+
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(new Configuration(new Version(2, 3, 0)));
+            tc.setRecognizeStandardFileExtensions(true);
+            assertOutputWithoutAndWithTC(tc, "${.outputFormat}",
+                    UndefinedOutputFormat.INSTANCE.getName(), HTMLOutputFormat.INSTANCE.getName());
+            testedProps.add(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE);
+        }
+        
+        assertEquals("Check that you have tested all parser settings; ", PARSER_PROP_NAMES, testedProps);
+    }
+    
+    @Test
+    public void testConfigureParserTooLowIcI() throws Exception {
+        Configuration cfgWithTooLowIcI = new Configuration(Configuration.VERSION_2_3_21);
+        for (PropertyDescriptor propDesc : getTemplateConfigurationSettingPropDescs(true, false)) {
+            TemplateConfiguration tc = new TemplateConfiguration();
+
+            String propName = propDesc.getName();
+            Object value = SETTING_ASSIGNMENTS.get(propName);
+            propDesc.getWriteMethod().invoke(tc, value);
+            
+            boolean shouldFail;
+            if (CONFIGURABLE_PROP_NAMES.contains(propName)) {
+                shouldFail = true;
+            } else if (PARSER_PROP_NAMES.contains(propName)) {
+                shouldFail = false;
+            } else {
+                fail("Uncategorized property: " + propName);
+                return;
+            }
+            
+            try {
+                tc.setParentConfiguration(cfgWithTooLowIcI);
+                if (shouldFail) {
+                    fail("Should fail with property: " + propName);
+                }
+            } catch (IllegalStateException e) {
+                if (!shouldFail) {
+                    throw e;
+                }
+                assertThat(e.getMessage(), containsString("2.3.22"));
+            }
+        }
+    }
+    
+    @Test
+    public void testArithmeticEngine() throws TemplateException, IOException {
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.setParentConfiguration(DEFAULT_CFG);
+        tc.setArithmeticEngine(new DummyArithmeticEngine());
+        assertOutputWithoutAndWithTC(tc,
+                "<#setting locale='en_US'>${1} ${1+1} ${1*3} <#assign x = 1>${x + x} ${x * 3}",
+                "1 2 3 2 3", "11 22 33 22 33");
+        
+        // Doesn't affect template.arithmeticEngine, only affects the parsing:
+        Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null);
+        assertEquals(DEFAULT_CFG.getArithmeticEngine(), t.getArithmeticEngine());
+    }
+
+    @Test
+    public void testStringInterpolate() throws TemplateException, IOException {
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.setParentConfiguration(DEFAULT_CFG);
+        tc.setArithmeticEngine(new DummyArithmeticEngine());
+        assertOutputWithoutAndWithTC(tc,
+                "<#setting locale='en_US'>${'${1} ${1+1} ${1*3}'} <#assign x = 1>${'${x + x} ${x * 3}'}",
+                "1 2 3 2 3", "11 22 33 22 33");
+        
+        // Doesn't affect template.arithmeticEngine, only affects the parsing:
+        Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null);
+        assertEquals(DEFAULT_CFG.getArithmeticEngine(), t.getArithmeticEngine());
+    }
+    
+    @Test
+    public void testInterpret() throws TemplateException, IOException {
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.setParentConfiguration(DEFAULT_CFG);
+        tc.setArithmeticEngine(new DummyArithmeticEngine());
+        assertOutputWithoutAndWithTC(tc,
+                "<#setting locale='en_US'><#assign src = r'${1} <#assign x = 1>${x + x}'><@src?interpret />",
+                "1 2", "11 22");
+        
+        tc.setWhitespaceStripping(false);
+        assertOutputWithoutAndWithTC(tc,
+                "<#if true>\nX</#if><#assign src = r'<#if true>\nY</#if>'><@src?interpret />",
+                "XY", "\nX\nY");
+    }
+
+    @Test
+    public void testEval() throws TemplateException, IOException {
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            tc.setArithmeticEngine(new DummyArithmeticEngine());
+            assertOutputWithoutAndWithTC(tc,
+                    "<#assign x = 1>${r'1 + x'?eval?c}",
+                    "2", "22");
+            assertOutputWithoutAndWithTC(tc,
+                    "${r'1?c'?eval}",
+                    "1", "11");
+        }
+        
+        {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            tc.setParentConfiguration(DEFAULT_CFG);
+            String outputEncoding = "ISO-8859-2";
+            tc.setOutputEncoding(outputEncoding);
+
+            String legacyNCFtl = "${r'.output_encoding!\"null\"'?eval}";
+            String camelCaseNCFtl = "${r'.outputEncoding!\"null\"'?eval}";
+
+            // Default is re-auto-detecting in ?eval:
+            assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding);
+            assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding);
+            
+            // Force camelCase:
+            tc.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+            assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", null);
+            assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding);
+            
+            // Force legacy:
+            tc.setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+            assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding);
+            assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", null);
+        }
+    }
+    
+    @Test
+    public void testSetParentConfiguration() throws IOException {
+        TemplateConfiguration tc = new TemplateConfiguration();
+        
+        Template t = new Template(null, "", DEFAULT_CFG);
+        try {
+            tc.apply(t);
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), containsString("Configuration"));
+        }
+        
+        tc.setParent(DEFAULT_CFG);
+        
+        try {
+            tc.setParentConfiguration(new Configuration());
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), containsString("Configuration"));
+        }
+
+        try {
+            // Same as setParentConfiguration
+            tc.setParent(new Configuration());
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), containsString("Configuration"));
+        }
+        
+        try {
+            tc.setParentConfiguration(null);
+            fail();
+        } catch (NullArgumentException e) {
+            // exected
+        }
+        
+        tc.setParent(DEFAULT_CFG);
+        
+        tc.apply(t);
+    }
+    
+    private void assertOutputWithoutAndWithTC(TemplateConfiguration tc, String ftl, String expectedDefaultOutput,
+            String expectedConfiguredOutput) throws TemplateException, IOException {
+        assertOutput(tc, ftl, expectedConfiguredOutput);
+        assertOutput(null, ftl, expectedDefaultOutput);
+    }
+
+    private void assertOutput(TemplateConfiguration tc, String ftl, String expectedConfiguredOutput)
+            throws TemplateException, IOException {
+        StringWriter sw = new StringWriter();
+        try {
+            Configuration cfg = tc != null ? tc.getParentConfiguration() : DEFAULT_CFG;
+            Template t = new Template("adhoc.ftlh", null, new StringReader(ftl), cfg, tc, null);
+            if (tc != null) {
+                tc.apply(t);
+            }
+            t.process(null, sw);
+            if (expectedConfiguredOutput == null) {
+                fail("Template should have fail.");
+            }
+        } catch (TemplateException e) {
+            if (expectedConfiguredOutput != null) {
+                throw e;
+            }
+        } catch (ParseException e) {
+            if (expectedConfiguredOutput != null) {
+                throw e;
+            }
+        }
+        if (expectedConfiguredOutput != null) {
+            assertEquals(expectedConfiguredOutput, sw.toString());
+        }
+    }
+
+    @Test
+    public void testIsSet() throws Exception {
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
+            TemplateConfiguration tc = new TemplateConfiguration();
+            checkAllIsSetFalseExcept(tc, null);
+            pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(pd.getName()));
+            checkAllIsSetFalseExcept(tc, pd.getName());
+        }
+    }
+
+    private void checkAllIsSetFalseExcept(TemplateConfiguration tc, String setSetting)
+            throws SecurityException, IntrospectionException,
+            IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
+            String isSetMethodName = getIsSetMethodName(pd.getReadMethod().getName());
+            Method isSetMethod;
+            try {
+                isSetMethod = TemplateConfiguration.class.getMethod(isSetMethodName);
+            } catch (NoSuchMethodException e) {
+                fail("Missing " + isSetMethodName + " method for \"" + pd.getName() + "\".");
+                return;
+            }
+            if (pd.getName().equals(setSetting)) {
+                assertTrue(isSetMethod + " should return true", (Boolean) (isSetMethod.invoke(tc)));
+            } else {
+                assertFalse(isSetMethod + " should return false", (Boolean) (isSetMethod.invoke(tc)));
+            }
+        }
+    }
+
+    /**
+     * Test case self-check.
+     */
+    @Test
+    public void checkTestAssignments() throws Exception {
+        for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
+            String propName = pd.getName();
+            if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+                fail("Test case doesn't cover all settings in SETTING_ASSIGNMENTS. Missing: " + propName);
+            }
+            Method readMethod = pd.getReadMethod();
+            String cfgMethodName = readMethod.getName();
+            if (cfgMethodName.equals("getEncoding")) {
+                // Because Configuration has local-to-encoding map too, this has a different name there.
+                cfgMethodName = "getDefaultEncoding";
+            }
+            Method cfgMethod = DEFAULT_CFG.getClass().getMethod(cfgMethodName, readMethod.getParameterTypes());
+            Object defaultSettingValue = cfgMethod.invoke(DEFAULT_CFG);
+            Object assignedValue = SETTING_ASSIGNMENTS.get(propName);
+            assertNotEquals("SETTING_ASSIGNMENTS must contain a non-default value for " + propName,
+                    assignedValue, defaultSettingValue);
+
+            TemplateConfiguration tc = new TemplateConfiguration();
+            try {
+                pd.getWriteMethod().invoke(tc, assignedValue);
+            } catch (Exception e) {
+                throw new IllegalStateException("For setting \"" + propName + "\" and assigned value of type "
+                        + (assignedValue != null ? assignedValue.getClass().getName() : "Null"),
+                        e);
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea5c47d1/src/test/java/freemarker/core/TemplateConfigurationWithTemplateCacheTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/TemplateConfigurationWithTemplateCacheTest.java b/src/test/java/freemarker/core/TemplateConfigurationWithTemplateCacheTest.java
new file mode 100644
index 0000000..611c2e3
--- /dev/null
+++ b/src/test/java/freemarker/core/TemplateConfigurationWithTemplateCacheTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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 freemarker.core;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Locale;
+
+import org.junit.Test;
+
+import freemarker.cache.ByteArrayTemplateLoader;
+import freemarker.cache.ConditionalTemplateConfigurationFactory;
+import freemarker.cache.FileNameGlobMatcher;
+import freemarker.cache.FirstMatchTemplateConfigurationFactory;
+import freemarker.cache.MergingTemplateConfigurationFactory;
+import freemarker.cache.StringTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+public class TemplateConfigurationWithTemplateCacheTest {
+
+    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);
+
+    @Test
+    public void testEncoding() throws Exception {
+        Configuration cfg = createCommonEncodingTesterConfig();
+        
+        {
+            Template t = cfg.getTemplate("utf8.ftl");
+            assertEquals("utf-8", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("utf8.ftl", "iso-8859-1");
+            assertEquals("utf-8", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("utf16.ftl");
+            assertEquals("utf-16", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl");
+            assertEquals("iso-8859-1", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl", "iso-8859-5");
+            assertEquals("iso-8859-5", t.getEncoding());
+            assertEquals(new String(TEXT_WITH_ACCENTS.getBytes("iso-8859-1"), "iso-8859-5"),
+                    getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("utf8-latin2.ftl");
+            assertEquals("iso-8859-2", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default-latin2.ftl");
+            assertEquals("iso-8859-2", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+    }
+    
+    @Test
+    public void testIncludeAndEncoding() throws Exception {
+        Configuration cfg = createCommonEncodingTesterConfig();
+        ByteArrayTemplateLoader tl = (ByteArrayTemplateLoader) cfg.getTemplateLoader();
+        tl.putTemplate("main.ftl", (
+                        "<#include 'utf8.ftl'>"
+                        + "<#include 'utf16.ftl'>"
+                        + "<#include 'default.ftl'>"
+                        + "<#include 'utf8-latin2.ftl'>"
+                        // With mostly ignored encoding params:
+                        + "<#include 'utf8.ftl' encoding='utf-16'>"
+                        + "<#include 'utf16.ftl' encoding='iso-8859-5'>"
+                        + "<#include 'default.ftl' encoding='iso-8859-5'>"
+                        + "<#include 'utf8-latin2.ftl' encoding='iso-8859-5'>"
+                ).getBytes("iso-8859-1"));
+        assertEquals(
+                TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS
+                + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS
+                + new String(TEXT_WITH_ACCENTS.getBytes("iso-8859-1"), "iso-8859-5")
+                + TEXT_WITH_ACCENTS,
+                getTemplateOutput(cfg.getTemplate("main.ftl")));
+    }
+
+    @Test
+    public void testLocale() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
+        cfg.setLocale(Locale.US);
+        
+        StringTemplateLoader tl = new StringTemplateLoader();
+        tl.putTemplate("(de).ftl", "${.locale}");
+        tl.putTemplate("default.ftl", "${.locale}");
+        tl.putTemplate("(de)-fr.ftl",
+                ("<#ftl locale='fr_FR'>${.locale}"));
+        tl.putTemplate("default-fr.ftl",
+                ("<#ftl locale='fr_FR'>${.locale}"));
+        cfg.setTemplateLoader(tl);
+
+        TemplateConfiguration tcDe = new TemplateConfiguration();
+        tcDe.setLocale(Locale.GERMANY);
+        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(de)*"), tcDe));
+        
+        {
+            Template t = cfg.getTemplate("(de).ftl");
+            assertEquals(Locale.GERMANY, t.getLocale());
+            assertEquals("de_DE", getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("(de).ftl", Locale.ITALY);
+            assertEquals(Locale.GERMANY, t.getLocale());
+            assertEquals("de_DE", getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl");
+            assertEquals(Locale.US, t.getLocale());
+            assertEquals("en_US", getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl", Locale.ITALY);
+            assertEquals(Locale.ITALY, t.getLocale());
+            assertEquals("it_IT", getTemplateOutput(t));
+        }
+    }
+
+    @Test
+    public void testPlainText() throws Exception {
+        Configuration cfg = createCommonEncodingTesterConfig();
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_22);
+        
+        TemplateConfiguration tcDE = new TemplateConfiguration();
+        tcDE.setLocale(Locale.GERMANY);
+        TemplateConfiguration tcYN = new TemplateConfiguration();
+        tcYN.setBooleanFormat("Y,N");
+        cfg.setTemplateConfigurations(
+                    new MergingTemplateConfigurationFactory(
+                            cfg.getTemplateConfigurations(),
+                            new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("utf16.ftl"), tcDE),
+                            new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("utf16.ftl"), tcYN)
+                    )
+                );
+        
+        {
+            Template t = cfg.getTemplate("utf8.ftl", null, null, false);
+            assertEquals("utf-8", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+            assertEquals(Locale.US, t.getLocale());
+            assertEquals("true,false", t.getBooleanFormat());
+        }
+        {
+            Template t = cfg.getTemplate("utf8.ftl", null, "iso-8859-1", false);
+            assertEquals("utf-8", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+        {
+            Template t = cfg.getTemplate("utf16.ftl", null, null, false);
+            assertEquals("utf-16", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+            assertEquals(Locale.GERMANY, t.getLocale());
+            assertEquals("Y,N", t.getBooleanFormat());
+        }
+        {
+            Template t = cfg.getTemplate("default.ftl", null, null, false);
+            assertEquals("iso-8859-1", t.getEncoding());
+            assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t));
+        }
+    }
+
+    @Test
+    public void testConfigurableSettings() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
+        cfg.setLocale(Locale.US);
+        
+        TemplateConfiguration tcFR = new TemplateConfiguration();
+        tcFR.setLocale(Locale.FRANCE);
+        TemplateConfiguration tcYN = new TemplateConfiguration();
+        tcYN.setBooleanFormat("Y,N");
+        TemplateConfiguration tc00 = new TemplateConfiguration();
+        tc00.setNumberFormat("0.00");
+        cfg.setTemplateConfigurations(
+                new MergingTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(fr)*"), tcFR),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(yn)*"), tcYN),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(00)*"), tc00)
+                )
+        );
+        
+        String commonFTL = "${.locale} ${true?string} ${1.2}";
+        StringTemplateLoader tl = new StringTemplateLoader();
+        tl.putTemplate("default", commonFTL);
+        tl.putTemplate("(fr)", commonFTL);
+        tl.putTemplate("(yn)(00)", commonFTL);
+        tl.putTemplate("(00)(fr)", commonFTL);
+        cfg.setTemplateLoader(tl);
+        
+        assertEquals("en_US true 1.2", getTemplateOutput(cfg.getTemplate("default")));
+        assertEquals("fr_FR true 1,2", getTemplateOutput(cfg.getTemplate("(fr)")));
+        assertEquals("en_US Y 1.20", getTemplateOutput(cfg.getTemplate("(yn)(00)")));
+        assertEquals("fr_FR true 1,20", getTemplateOutput(cfg.getTemplate("(00)(fr)")));
+    }
+    
+    @Test
+    public void testCustomAttributes() throws Exception {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
+        
+        TemplateConfiguration tc1 = new TemplateConfiguration();
+        tc1.setCustomAttribute("a1", "a1tc1");
+        tc1.setCustomAttribute("a2", "a2tc1");
+        tc1.setCustomAttribute("a3", "a3tc1");
+        CUST_ATT_1.set("ca1tc1", tc1);
+        CUST_ATT_2.set("ca2tc1", tc1);
+        
+        TemplateConfiguration tc2 = new TemplateConfiguration();
+        tc2.setCustomAttribute("a1", "a1tc2");
+        CUST_ATT_1.set("ca1tc2", tc2);
+        
+        cfg.setTemplateConfigurations(
+                new MergingTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc1)*"), tc1),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*(tc2)*"), tc2)
+                )
+        );
+        
+        String commonFTL = "<#ftl attributes={ 'a3': 'a3temp' }>";
+        StringTemplateLoader tl = new StringTemplateLoader();
+        tl.putTemplate("(tc1)", commonFTL);
+        tl.putTemplate("(tc1)noHeader", "");
+        tl.putTemplate("(tc2)", commonFTL);
+        tl.putTemplate("(tc1)(tc2)", commonFTL);
+        cfg.setTemplateLoader(tl);
+
+        {
+            Template t = cfg.getTemplate("(tc1)");
+            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));
+        }
+        {
+            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));
+        }
+        {
+            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));
+        }
+        {
+            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));
+        }
+    }
+    
+    private String getTemplateOutput(Template t) throws TemplateException, IOException {
+        StringWriter sw = new StringWriter();
+        t.process(null, sw);
+        return sw.toString();
+    }
+
+    private Configuration createCommonEncodingTesterConfig() throws UnsupportedEncodingException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
+        cfg.setDefaultEncoding("iso-8859-1");
+        cfg.setLocale(Locale.US);
+        
+        ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
+        tl.putTemplate("utf8.ftl", TEXT_WITH_ACCENTS.getBytes("utf-8"));
+        tl.putTemplate("utf16.ftl", TEXT_WITH_ACCENTS.getBytes("utf-16"));
+        tl.putTemplate("default.ftl", TEXT_WITH_ACCENTS.getBytes("iso-8859-2"));
+        tl.putTemplate("utf8-latin2.ftl",
+                ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes("iso-8859-2"));
+        tl.putTemplate("default-latin2.ftl",
+                ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes("iso-8859-2"));
+        cfg.setTemplateLoader(tl);
+        
+        TemplateConfiguration tcUtf8 = new TemplateConfiguration();
+        tcUtf8.setEncoding("utf-8");
+        TemplateConfiguration tcUtf16 = new TemplateConfiguration();
+        tcUtf16.setEncoding("utf-16");
+        cfg.setTemplateConfigurations(
+                new FirstMatchTemplateConfigurationFactory(
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf8*"), tcUtf8),
+                        new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*utf16*"), tcUtf16)
+                ).allowNoMatch(true));
+        return cfg;
+    }
+
+}