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/06/01 22:13:28 UTC

[3/5] incubator-freemarker git commit: Made some of the configuration settings use immutable List-s and Map-s even in the Builder-s, and removed methods that modified these maps:

Made some of the configuration settings use immutable List-s and Map-s even in the Builder-s, and removed methods that modified these maps:

- customDateFormats
- customNumberFormats
Optimized TemplateConfiguration.Builder.merge method to not copy the List-s and Map-s unnecessarily. Some further cleanup.


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

Branch: refs/heads/3
Commit: eae270885b5383d941384f96ed75bec116509898
Parents: a3cab7a
Author: ddekany <dd...@apache.org>
Authored: Fri May 26 12:24:51 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri May 26 12:53:20 2017 +0200

----------------------------------------------------------------------
 .../core/TemplateConfigurationTest.java         | 190 +++++++++----------
 .../core/MutableProcessingConfiguration.java    | 156 +++++++++++----
 .../core/ProcessingConfiguration.java           |   6 +-
 .../freemarker/core/TemplateConfiguration.java  |  34 +++-
 .../freemarker/core/util/_CollectionUtil.java   |  10 +-
 5 files changed, 249 insertions(+), 147 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eae27088/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
index e4d4390..c229655 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -296,7 +296,7 @@ public class TemplateConfigurationTest {
                 Object value2 = SETTING_ASSIGNMENTS.get(propDesc2.getName());
                 propDesc2.getWriteMethod().invoke(tcb2, value2);
 
-                tcb1.merge(tcb2);
+                tcb1.merge(tcb2.build());
                 if (propDesc1.getName().equals(propDesc2.getName()) && value1 instanceof List
                         && !propDesc1.getName().equals("autoIncludes")) {
                     assertEquals("For " + propDesc1.getName(),
@@ -311,86 +311,86 @@ public class TemplateConfigurationTest {
     
     @Test
     public void testMergeMapSettings() throws Exception {
-        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
-        tc1.setCustomDateFormats(ImmutableMap.of(
+        TemplateConfiguration.Builder tcb1 = new TemplateConfiguration.Builder();
+        tcb1.setCustomDateFormats(ImmutableMap.of(
                 "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
                 "x", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE));
-        tc1.setCustomNumberFormats(ImmutableMap.of(
+        tcb1.setCustomNumberFormats(ImmutableMap.of(
                 "hex", HexTemplateNumberFormatFactory.INSTANCE,
                 "x", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE));
-        tc1.setAutoImports(ImmutableMap.of("a", "a1.ftl", "b", "b1.ftl"));
+        tcb1.setAutoImports(ImmutableMap.of("a", "a1.ftl", "b", "b1.ftl"));
         
-        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
-        tc2.setCustomDateFormats(ImmutableMap.of(
+        TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
+        tcb2.setCustomDateFormats(ImmutableMap.of(
                 "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
                 "x", EpochMillisDivTemplateDateFormatFactory.INSTANCE));
-        tc2.setCustomNumberFormats(ImmutableMap.of(
+        tcb2.setCustomNumberFormats(ImmutableMap.of(
                 "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
                 "x", BaseNTemplateNumberFormatFactory.INSTANCE));
-        tc2.setAutoImports(ImmutableMap.of("b", "b2.ftl", "c", "c2.ftl"));
+        tcb2.setAutoImports(ImmutableMap.of("b", "b2.ftl", "c", "c2.ftl"));
         
-        tc1.merge(tc2);
+        tcb1.merge(tcb2.build());
         
-        Map<String, ? extends TemplateDateFormatFactory> mergedCustomDateFormats = tc1.getCustomDateFormats();
+        Map<String, ? extends TemplateDateFormatFactory> mergedCustomDateFormats = tcb1.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();
+        Map<String, ? extends TemplateNumberFormatFactory> mergedCustomNumberFormats = tcb1.getCustomNumberFormats();
         assertEquals(HexTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("hex"));
         assertEquals(LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("loc"));
         assertEquals(BaseNTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("x"));
 
-        Map<String, String> mergedAutoImports = tc1.getAutoImports();
+        Map<String, String> mergedAutoImports = tcb1.getAutoImports();
         assertEquals("a1.ftl", mergedAutoImports.get("a"));
         assertEquals("b2.ftl", mergedAutoImports.get("b"));
         assertEquals("c2.ftl", mergedAutoImports.get("c"));
         
         // Empty map merging optimization:
-        tc1.merge(new TemplateConfiguration.Builder());
-        assertSame(mergedCustomDateFormats, tc1.getCustomDateFormats());
-        assertSame(mergedCustomNumberFormats, tc1.getCustomNumberFormats());
+        tcb1.merge(new TemplateConfiguration.Builder().build());
+        assertSame(mergedCustomDateFormats, tcb1.getCustomDateFormats());
+        assertSame(mergedCustomNumberFormats, tcb1.getCustomNumberFormats());
         
         // Empty map merging optimization:
-        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
-        tc3.merge(tc1);
-        assertSame(mergedCustomDateFormats, tc3.getCustomDateFormats());
-        assertSame(mergedCustomNumberFormats, tc3.getCustomNumberFormats());
+        TemplateConfiguration.Builder tcb3 = new TemplateConfiguration.Builder();
+        tcb3.merge(tcb1.build());
+        assertSame(mergedCustomDateFormats, tcb3.getCustomDateFormats());
+        assertSame(mergedCustomNumberFormats, tcb3.getCustomNumberFormats());
     }
     
     @Test
     public void testMergeListSettings() throws Exception {
-        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
-        tc1.setAutoIncludes(ImmutableList.of("a.ftl", "x.ftl", "b.ftl"));
+        TemplateConfiguration.Builder tcb1 = new TemplateConfiguration.Builder();
+        tcb1.setAutoIncludes(ImmutableList.of("a.ftl", "x.ftl", "b.ftl"));
         
-        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
-        tc2.setAutoIncludes(ImmutableList.of("c.ftl", "x.ftl", "d.ftl"));
+        TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
+        tcb2.setAutoIncludes(ImmutableList.of("c.ftl", "x.ftl", "d.ftl"));
         
-        tc1.merge(tc2);
+        tcb1.merge(tcb2.build());
         
-        assertEquals(ImmutableList.of("a.ftl", "b.ftl", "c.ftl", "x.ftl", "d.ftl"), tc1.getAutoIncludes());
+        assertEquals(ImmutableList.of("a.ftl", "b.ftl", "c.ftl", "x.ftl", "d.ftl"), tcb1.getAutoIncludes());
     }
     
     @Test
     public void testMergePriority() throws Exception {
-        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
-        tc1.setDateFormat("1");
-        tc1.setTimeFormat("1");
-        tc1.setDateTimeFormat("1");
+        TemplateConfiguration.Builder tcb1 = new TemplateConfiguration.Builder();
+        tcb1.setDateFormat("1");
+        tcb1.setTimeFormat("1");
+        tcb1.setDateTimeFormat("1");
 
-        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
-        tc2.setDateFormat("2");
-        tc2.setTimeFormat("2");
+        TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
+        tcb2.setDateFormat("2");
+        tcb2.setTimeFormat("2");
 
-        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
-        tc3.setDateFormat("3");
+        TemplateConfiguration.Builder tcb3 = new TemplateConfiguration.Builder();
+        tcb3.setDateFormat("3");
 
-        tc1.merge(tc2);
-        tc1.merge(tc3);
+        tcb1.merge(tcb2.build());
+        tcb1.merge(tcb3.build());
 
-        assertEquals("3", tc1.getDateFormat());
-        assertEquals("2", tc1.getTimeFormat());
-        assertEquals("1", tc1.getDateTimeFormat());
+        assertEquals("3", tcb1.getDateFormat());
+        assertEquals("2", tcb1.getTimeFormat());
+        assertEquals("1", tcb1.getDateTimeFormat());
     }
     
     @Test
@@ -403,18 +403,18 @@ public class TemplateConfigurationTest {
         tc1.setCustomAttribute(CA2, "V1");
         tc1.setCustomAttribute(CA3, "V1");
 
-        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
-        tc2.setCustomAttribute("k1", "v2");
-        tc2.setCustomAttribute("k2", "v2");
-        tc2.setCustomAttribute(CA1, "V2");
-        tc2.setCustomAttribute(CA2, "V2");
+        TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
+        tcb2.setCustomAttribute("k1", "v2");
+        tcb2.setCustomAttribute("k2", "v2");
+        tcb2.setCustomAttribute(CA1, "V2");
+        tcb2.setCustomAttribute(CA2, "V2");
 
-        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
-        tc3.setCustomAttribute("k1", "v3");
-        tc3.setCustomAttribute(CA1, "V3");
+        TemplateConfiguration.Builder tcb3 = new TemplateConfiguration.Builder();
+        tcb3.setCustomAttribute("k1", "v3");
+        tcb3.setCustomAttribute(CA1, "V3");
 
-        tc1.merge(tc2);
-        tc1.merge(tc3);
+        tc1.merge(tcb2.build());
+        tc1.merge(tcb3.build());
 
         assertEquals("v3", tc1.getCustomAttribute("k1"));
         assertEquals("v2", tc1.getCustomAttribute("k2"));
@@ -426,51 +426,51 @@ public class TemplateConfigurationTest {
 
     @Test
     public void testMergeNullCustomAttributes() throws Exception {
-        TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
-        tc1.setCustomAttribute("k1", "v1");
-        tc1.setCustomAttribute("k2", "v1");
-        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", tc1.getCustomAttribute(CA1));
-        assertEquals("V1", tc1.getCustomAttribute(CA2));
-        assertNull(tc1.getCustomAttribute(CA3));
-
-        TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder();
-        tc2.setCustomAttribute("k1", "v2");
-        tc2.setCustomAttribute("k2", null);
-        tc2.setCustomAttribute(CA1, "V2");
-        tc2.setCustomAttribute(CA2, null);
-
-        TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder();
-        tc3.setCustomAttribute("k1", null);
-        tc2.setCustomAttribute(CA1, null);
-
-        tc1.merge(tc2);
-        tc1.merge(tc3);
-
-        assertNull(tc1.getCustomAttribute("k1"));
-        assertNull(tc1.getCustomAttribute("k2"));
-        assertNull(tc1.getCustomAttribute("k3"));
-        assertNull(tc1.getCustomAttribute(CA1));
-        assertNull(tc1.getCustomAttribute(CA2));
-        assertNull(tc1.getCustomAttribute(CA3));
-
-        TemplateConfiguration.Builder tc4 = new TemplateConfiguration.Builder();
-        tc4.setCustomAttribute("k1", "v4");
-        tc4.setCustomAttribute(CA1, "V4");
-
-        tc1.merge(tc4);
-
-        assertEquals("v4", tc1.getCustomAttribute("k1"));
-        assertNull(tc1.getCustomAttribute("k2"));
-        assertNull(tc1.getCustomAttribute("k3"));
-        assertEquals("V4", tc1.getCustomAttribute(CA1));
-        assertNull(tc1.getCustomAttribute(CA2));
-        assertNull(tc1.getCustomAttribute(CA3));
+        TemplateConfiguration.Builder tcb1 = new TemplateConfiguration.Builder();
+        tcb1.setCustomAttribute("k1", "v1");
+        tcb1.setCustomAttribute("k2", "v1");
+        tcb1.setCustomAttribute(CA1, "V1");
+        tcb1.setCustomAttribute(CA2,"V1");
+
+        assertEquals("v1", tcb1.getCustomAttribute("k1"));
+        assertEquals("v1", tcb1.getCustomAttribute("k2"));
+        assertNull("v1", tcb1.getCustomAttribute("k3"));
+        assertEquals("V1", tcb1.getCustomAttribute(CA1));
+        assertEquals("V1", tcb1.getCustomAttribute(CA2));
+        assertNull(tcb1.getCustomAttribute(CA3));
+
+        TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
+        tcb2.setCustomAttribute("k1", "v2");
+        tcb2.setCustomAttribute("k2", null);
+        tcb2.setCustomAttribute(CA1, "V2");
+        tcb2.setCustomAttribute(CA2, null);
+
+        TemplateConfiguration.Builder tcb3 = new TemplateConfiguration.Builder();
+        tcb3.setCustomAttribute("k1", null);
+        tcb2.setCustomAttribute(CA1, null);
+
+        tcb1.merge(tcb2.build());
+        tcb1.merge(tcb3.build());
+
+        assertNull(tcb1.getCustomAttribute("k1"));
+        assertNull(tcb1.getCustomAttribute("k2"));
+        assertNull(tcb1.getCustomAttribute("k3"));
+        assertNull(tcb1.getCustomAttribute(CA1));
+        assertNull(tcb1.getCustomAttribute(CA2));
+        assertNull(tcb1.getCustomAttribute(CA3));
+
+        TemplateConfiguration.Builder tcb4 = new TemplateConfiguration.Builder();
+        tcb4.setCustomAttribute("k1", "v4");
+        tcb4.setCustomAttribute(CA1, "V4");
+
+        tcb1.merge(tcb4.build());
+
+        assertEquals("v4", tcb1.getCustomAttribute("k1"));
+        assertNull(tcb1.getCustomAttribute("k2"));
+        assertNull(tcb1.getCustomAttribute("k3"));
+        assertEquals("V4", tcb1.getCustomAttribute(CA1));
+        assertNull(tcb1.getCustomAttribute(CA2));
+        assertNull(tcb1.getCustomAttribute(CA3));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eae27088/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
index 1118e8e..dcf0714 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -330,7 +330,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     private Boolean logTemplateExceptions;
     private Map<String, TemplateDateFormatFactory> customDateFormats;
     private Map<String, TemplateNumberFormatFactory> customNumberFormats;
-    private LinkedHashMap<String, String> autoImports;
+    private Map<String, String> autoImports;
     private List<String> autoIncludes;
     private Boolean lazyImports;
     private Boolean lazyAutoImports;
@@ -517,16 +517,37 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     /**
      * Setter pair of {@link #getCustomNumberFormats()}. Note that custom number formats are get through
      * {@link #getCustomNumberFormat(String)}, not directly though this {@link Map}, so number formats from
-     * {@link ProcessingConfiguration}-s on less specific levels are inherited without you copying them into this
+     * {@link ProcessingConfiguration}-s on less specific levels are inherited without being present in this
      * {@link Map}.
      *
      * @param customNumberFormats
-     *      Not {@code null}.
+     *         Not {@code null}; will be copied (to prevent aliasing effect); keys must conform to format name
+     *         syntactical restrictions  (see in {@link #getCustomNumberFormats()})
      */
     public void setCustomNumberFormats(Map<String, TemplateNumberFormatFactory> customNumberFormats) {
+        setCustomNumberFormats(customNumberFormats, false);
+    }
+
+    /**
+     * @param validatedImmutableUnchanging
+     *         {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
+     *         won't change later because of aliasing).
+     */
+    void setCustomNumberFormats(Map<String, TemplateNumberFormatFactory> customNumberFormats,
+            boolean validatedImmutableUnchanging) {
         _NullArgumentException.check("customNumberFormats", customNumberFormats);
-        validateFormatNames(customNumberFormats.keySet());
-        this.customNumberFormats = customNumberFormats;
+        if (!validatedImmutableUnchanging) {
+            if (customNumberFormats == this.customNumberFormats) {
+                return;
+            }
+            _CollectionUtil.safeCastMap("customNumberFormats", customNumberFormats,
+                    String.class, false,
+                    TemplateNumberFormatFactory.class, false);
+            validateFormatNames(customNumberFormats.keySet());
+            this.customNumberFormats = Collections.unmodifiableMap(new HashMap<>(customNumberFormats));
+        } else {
+            this.customNumberFormats = customNumberFormats;
+        }
     }
 
     /**
@@ -763,12 +784,40 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     protected abstract Map<String, TemplateDateFormatFactory> getDefaultCustomDateFormats();
 
     /**
-     * Setter pair of {@link #getCustomDateFormat(String)}.
+     * Setter pair of {@link #getCustomDateFormat(String)}. Note that custom date formats are get through
+     * {@link #getCustomNumberFormat(String)}, not directly though this {@link Map}, so date formats from
+     * {@link ProcessingConfiguration}-s on less specific levels are inherited without being present in this
+     * {@link Map}.
+     *
+     * @param customDateFormats
+     *         Not {@code null}; will be copied (to prevent aliasing effect); keys must conform to format name
+     *         syntactical restrictions (see in {@link #getCustomDateFormats()})
      */
     public void setCustomDateFormats(Map<String, TemplateDateFormatFactory> customDateFormats) {
+        setCustomDateFormats(customDateFormats, false);
+    }
+
+    /**
+     * @param validatedImmutableUnchanging
+     *         {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
+     *         won't change later because of aliasing).
+     */
+    void setCustomDateFormats(
+            Map<String, TemplateDateFormatFactory> customDateFormats,
+            boolean validatedImmutableUnchanging) {
         _NullArgumentException.check("customDateFormats", customDateFormats);
-        validateFormatNames(customDateFormats.keySet());
-        this.customDateFormats = customDateFormats;
+        if (!validatedImmutableUnchanging) {
+            if (customDateFormats == this.customDateFormats) {
+                return;
+            }
+            _CollectionUtil.safeCastMap("customDateFormats", customDateFormats,
+                    String.class, false,
+                    TemplateDateFormatFactory.class, false);
+            validateFormatNames(customDateFormats.keySet());
+            this.customDateFormats = Collections.unmodifiableMap(new HashMap(customDateFormats));
+        } else {
+            this.customDateFormats = customDateFormats;
+        }
     }
 
     /**
@@ -1292,15 +1341,31 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     /**
      * Setter pair of {@link #getAutoImports()}.
      * 
-     * @param map
+     * @param autoImports
      *            Maps the namespace variable names to the template names; not {@code null}, and can't contain {@code
      *            null} keys of values. The content of the {@link Map} is copied into another {@link Map}, to avoid
      *            aliasing problems. The iteration order of the original {@link Map} entries is kept.
      */
-    public void setAutoImports(Map<String, String> map) {
-        _NullArgumentException.check("map", map);
-        _CollectionUtil.safeCastMap("map", map, String.class, false, String.class, false);
-        autoImports = new LinkedHashMap<>(map);
+    public void setAutoImports(Map<String, String> autoImports) {
+        setAutoImports(autoImports, false);
+    }
+
+    /**
+     * @param validatedImmutableUnchanging
+     *         {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
+     *         won't change later because of aliasing).
+     */
+    void setAutoImports(Map<String, String> autoImports, boolean validatedImmutableUnchanging) {
+        _NullArgumentException.check("autoImports", autoImports);
+        if (!validatedImmutableUnchanging) {
+            if (autoImports == this.autoImports) {
+                return;
+            }
+            _CollectionUtil.safeCastMap("autoImports", autoImports, String.class, false, String.class, false);
+            this.autoImports = new LinkedHashMap<>(autoImports);
+        } else {
+            this.autoImports = autoImports;
+        }
     }
 
     /**
@@ -1338,20 +1403,36 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     /**
      * Setter pair of {@link #getAutoIncludes()}
      *
-     * @param templateNames Not {@code null}. The {@link List} will be copied to avoid aliasing problems.
-     */
-    public void setAutoIncludes(List<String> templateNames) {
-        _NullArgumentException.check("templateNames", templateNames);
-        _CollectionUtil.safeCastList("templateNames", templateNames, String.class, false);
-        Set<String> uniqueItems = new LinkedHashSet<>(templateNames.size() * 4 / 3, 1f);
-        for (String templateName : templateNames) {
-            if (!uniqueItems.add(templateName)) {
-                // Move clashing item at the end of the collection
-                uniqueItems.remove(templateName);
-                uniqueItems.add(templateName);
+     * @param autoIncludes Not {@code null}. The {@link List} will be copied to avoid aliasing problems.
+     */
+    public void setAutoIncludes(List<String> autoIncludes) {
+        setAutoIncludes(autoIncludes, false);
+    }
+
+    /**
+     * @param validatedImmutableUnchanging
+     *         {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
+     *         won't change later because of aliasing).
+     */
+    void setAutoIncludes(List<String> autoIncludes, boolean validatedImmutableUnchanging) {
+        _NullArgumentException.check("autoIncludes", autoIncludes);
+        if (!validatedImmutableUnchanging) {
+            if (autoIncludes == this.autoIncludes) {
+                return;
             }
+            _CollectionUtil.safeCastList("autoIncludes", autoIncludes, String.class, false);
+            Set<String> uniqueItems = new LinkedHashSet<>(autoIncludes.size() * 4 / 3, 1f);
+            for (String templateName : autoIncludes) {
+                if (!uniqueItems.add(templateName)) {
+                    // Move clashing item at the end of the collection
+                    uniqueItems.remove(templateName);
+                    uniqueItems.add(templateName);
+                }
+            }
+            this.autoIncludes = Collections.<String>unmodifiableList(new ArrayList<>(uniqueItems));
+        } else {
+            this.autoIncludes = autoIncludes;
         }
-        autoIncludes = Collections.<String>unmodifiableList(new ArrayList<>(uniqueItems));
     }
 
     /**
@@ -2056,24 +2137,29 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
      * @param customAttributes Not {@code null}. The {@link Map} is copied to prevent aliasing problems.
      */
     public void setCustomAttributes(Map<Object, Object> customAttributes) {
-        setCustomAttributesWithoutCopying(new LinkedHashMap<>(customAttributes));
+        setCustomAttributes(customAttributes, false);
     }
 
     /**
-     * Fluent API equivalent of {@link #setCustomAttributes(Map)}
+     * @param validatedImmutableUnchanging
+     *         {@code true} if we know that the 1st argument is already validated, immutable, and unchanging (means,
+     *         won't change later because of aliasing).
      */
-    public SelfT customAttributes(Map<Object, Object> customAttributes) {
-        setCustomAttributes(customAttributes);
-        return self();
+    void setCustomAttributes(Map<Object, Object> customAttributes, boolean validatedImmutableUnchanging) {
+        _NullArgumentException.check("customAttributes", customAttributes);
+        if (!validatedImmutableUnchanging) {
+            this.customAttributes = new LinkedHashMap<>(customAttributes); // TODO mutable
+        } else {
+            this.customAttributes = customAttributes;
+        }
     }
 
     /**
-     * Used internally instead of {@link #setCustomAttributes(Map)} to speed up use cases where we know that there
-     * won't be aliasing problems.
+     * Fluent API equivalent of {@link #setCustomAttributes(Map)}
      */
-    void setCustomAttributesWithoutCopying(Map<Object, Object> customAttributes) {
-        _NullArgumentException.check("customAttributes", customAttributes);
-        this.customAttributes = customAttributes;
+    public SelfT customAttributes(Map<Object, Object> customAttributes) {
+        setCustomAttributes(customAttributes);
+        return self();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eae27088/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
index 6f6b33d..d68ab78 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -189,8 +189,7 @@ public interface ProcessingConfiguration {
      * inheritance. Thus, to get a custom format you shouldn't use this {@link Map} directly, but
      * {@link #getCustomNumberFormat(String)}, which will search the format in the inheritance chain.
      *
-     * @return Never {@code null}. Unless the method was called on a builder class, the returned {@link Map} shouldn't
-     * be modified.
+     * @return Never {@code null}; unmodifiable {@link Map}.
      */
     Map<String, TemplateNumberFormatFactory> getCustomNumberFormats();
 
@@ -372,8 +371,7 @@ public interface ProcessingConfiguration {
      * inheritance. Thus, to get a custom format you shouldn't use this {@link Map} directly, but {@link
      * #getCustomDateFormat(String)}, which will search the format in the inheritance chain.
      *
-     * @return Never {@code null}. Unless the method was called on a builder class, the returned {@link Map} shouldn't
-     * be modified.
+     * @return Never {@code null}; unmodifiable {@link Map}.
      */
     Map<String, TemplateDateFormatFactory> getCustomDateFormats();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eae27088/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
index 47d21b8..8a6ccc3 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -21,6 +21,7 @@ package org.apache.freemarker.core;
 import java.io.Reader;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -135,6 +136,10 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         tabSize = builder.isTabSizeSet() ? builder.getTabSize() : null;
     }
 
+    /**
+     * Adds two {@link Map}-s (keeping the iteration order); assuming the inputs are already unmodifiable and
+     * unchanging, it returns an unmodifiable and unchanging {@link Map} itself.
+     */
     private static <K,V> Map<K,V> mergeMaps(Map<K,V> m1, Map<K,V> m2, boolean overwriteUpdatesOrder) {
         if (m1 == null) return m2;
         if (m2 == null) return m1;
@@ -149,9 +154,13 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
             }
         }
         mergedM.putAll(m2);
-        return mergedM;
+        return Collections.unmodifiableMap(mergedM);
     }
 
+    /**
+     * Adds two {@link List}-s; assuming the inputs are already unmodifiable and unchanging, it returns an
+     * unmodifiable and unchanging {@link List} itself.
+     */
     private static List<String> mergeLists(List<String> list1, List<String> list2) {
         if (list1 == null) return list2;
         if (list2 == null) return list1;
@@ -161,7 +170,7 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         ArrayList<String> mergedList = new ArrayList<>(list1.size() + list2.size());
         mergedList.addAll(list1);
         mergedList.addAll(list2);
-        return mergedList;
+        return Collections.unmodifiableList(mergedList);
     }
 
     /**
@@ -814,11 +823,12 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         }
 
         /**
-         * Set all settings in this {@link Builder} that were set in the parameter
-         * {@link TemplateConfiguration}, possibly overwriting the earlier value in this object. (A setting is said to be
-         * set in a {@link TemplateConfiguration} if it was explicitly set via a setter method, as opposed to be inherited.)
+         * Set all settings in this {@link Builder} that were set in the parameter {@link TemplateConfiguration} (or
+         * other {@link ParsingAndProcessingConfiguration}), possibly overwriting the earlier value in this object.
+         * (A setting is said to be set in a {@link ParsingAndProcessingConfiguration} if it was explicitly set via a
+         * setter method, as opposed to be inherited.)
          */
-        public void merge(ParsingAndProcessingConfiguration tc) {
+        public void merge(TemplateConfiguration tc) {
             if (tc.isAPIBuiltinEnabledSet()) {
                 setAPIBuiltinEnabled(tc.getAPIBuiltinEnabled());
             }
@@ -836,11 +846,14 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
             }
             if (tc.isCustomDateFormatsSet()) {
                 setCustomDateFormats(mergeMaps(
-                        isCustomDateFormatsSet() ? getCustomDateFormats() : null, tc.getCustomDateFormats(), false));
+                        isCustomDateFormatsSet() ? getCustomDateFormats() : null, tc.getCustomDateFormats(), false),
+                        true
+                );
             }
             if (tc.isCustomNumberFormatsSet()) {
                 setCustomNumberFormats(mergeMaps(
-                        isCustomNumberFormatsSet() ? getCustomNumberFormats() : null, tc.getCustomNumberFormats(), false));
+                        isCustomNumberFormatsSet() ? getCustomNumberFormats() : null, tc.getCustomNumberFormats(), false),
+                        true);
             }
             if (tc.isDateFormatSet()) {
                 setDateFormat(tc.getDateFormat());
@@ -927,10 +940,11 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
             }
 
             if (tc.isCustomAttributesSet()) {
-                setCustomAttributesWithoutCopying(mergeMaps(
+                setCustomAttributes(mergeMaps(
                         isCustomAttributesSet() ? getCustomAttributes() : null,
                         tc.isCustomAttributesSet() ? tc.getCustomAttributes() : null,
-                        true));
+                        true),
+                        true);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eae27088/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
index 397f951..bd703af 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_CollectionUtil.java
@@ -19,10 +19,14 @@
 
 package org.apache.freemarker.core.util;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/**
+ * Don't use this; used internally by FreeMarker, might changes without notice.
+ * {@link Collection} and {@link Map}-related utilities.
+ */
 public class _CollectionUtil {
     
     private _CollectionUtil() { }
@@ -74,12 +78,12 @@ public class _CollectionUtil {
                 if (key == null) {
                     if (!allowNullKey) {
                         throw new IllegalArgumentException(
-                                (argName != null ? "Invalid value for argument \"" + argName + "\"" : "")
+                                (argName != null ? "Invalid value for argument \"" + argName + "\": " : "")
                                         + "The Map contains null key");
                     }
                 } else {
                     throw new IllegalArgumentException(
-                            (argName != null ? "Invalid value for argument \"" + argName + "\"" : "")
+                            (argName != null ? "Invalid value for argument \"" + argName + "\": " : "")
                                     + "The Map contains a key that's not instance of " + keyClass.getName() +
                                     "; its class is " + key.getClass().getName() + ".");
                 }