Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 18fd4e8f2 -> 164f292ef


Added `protected Xxx getImplictXxx()` methods to 
Configuration.ExtendableBuilder for Map and List settings. These allow adding 
values to these Map-s/List-s over those explictly set by the user who configure 
FreeMarker. The default implemention doesn't utilize these, except for 
sharedVariables to add the legacy directives like "compress" (though these will 
be eventually removed).


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

Branch: refs/heads/3
Commit: 164f292efe9d59e2dc818d544ec4388df064afc5
Parents: 18fd4e8
Author: ddekany <[email protected]>
Authored: Sat Jun 10 09:54:56 2017 +0200
Committer: ddekany <[email protected]>
Committed: Sat Jun 10 09:54:56 2017 +0200

----------------------------------------------------------------------
 .../freemarker/core/ConfigurationTest.java      |  91 ++++++++++-
 .../apache/freemarker/core/Configuration.java   | 150 ++++++++++++++++---
 .../core/MutableProcessingConfiguration.java    |   6 +-
 .../org/apache/freemarker/core/Template.java    |   4 +-
 .../freemarker/core/TemplateConfiguration.java  |  64 +-------
 .../freemarker/core/util/_CollectionUtil.java   |  51 +++++++
 6 files changed, 281 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/164f292e/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index fa1c4d2..7cf205f 100644
--- 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -55,6 +55,8 @@ import 
org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
 import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
 import 
org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import 
org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
+import 
org.apache.freemarker.core.templateresolver.TemplateConfigurationFactoryException;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
@@ -1454,6 +1456,51 @@ public class ConfigurationTest extends TestCase {
         assertNotEquals(mutableValue, immutableValue); // No aliasing
     }
 
+    @Test
+    public void testImpliedSettingValues() throws IOException, 
TemplateConfigurationFactoryException {
+        Configuration cfg = new ImpliedSettingValuesTestBuilder().build();
+
+        assertEquals("Y,N", cfg.getTemplateConfigurations().get("t.yn", 
null).getBooleanFormat());
+        assertNotNull(cfg.getCustomNumberFormat("hex"));
+        assertNotNull(cfg.getCustomDateFormat("epoch"));
+        assertEquals(ImmutableMap.of("lib", "lib.ftl"), cfg.getAutoImports());
+        assertEquals(ImmutableList.of("inc.ftl"), cfg.getAutoIncludes());
+        assertEquals(ImmutableMap.of("v", 1), cfg.getSharedVariables());
+    }
+
+    @Test
+    public void testImpliedSettingValues2() throws IOException, 
TemplateConfigurationFactoryException {
+        Configuration cfg = new ImpliedSettingValuesTestBuilder()
+                .templateConfigurations(
+                        new ConditionalTemplateConfigurationFactory(
+                                new FileNameGlobMatcher("*.jn"),
+                                new 
TemplateConfiguration.Builder().booleanFormat("J,N").build()
+                        )
+                )
+                .customNumberFormats(ImmutableMap.of("baseN", 
BaseNTemplateNumberFormatFactory.INSTANCE))
+                .customDateFormats(ImmutableMap.of("epochDiv", 
EpochMillisDivTemplateDateFormatFactory.INSTANCE))
+                .autoImports(ImmutableMap.of("lib2", "lib2.ftl"))
+                .autoIncludes(ImmutableList.of("inc2.ftl"))
+                .sharedVariables(ImmutableMap.of("v2", 2))
+                .build();
+
+        TemplateConfigurationFactory tcf = cfg.getTemplateConfigurations();
+        assertEquals("Y,N", tcf.get("t.yn", null).getBooleanFormat());
+        assertEquals("J,N", tcf.get("t.jn", null).getBooleanFormat());
+
+        assertNotNull(cfg.getCustomNumberFormat("hex"));
+        assertNotNull(cfg.getCustomNumberFormat("baseN"));
+
+        assertNotNull(cfg.getCustomDateFormat("epoch"));
+        assertNotNull(cfg.getCustomDateFormat("epochDiv"));
+
+        assertEquals(ImmutableMap.of("lib", "lib.ftl", "lib2", "lib2.ftl"), 
cfg.getAutoImports());
+
+        assertEquals(ImmutableList.of("inc.ftl", "inc2.ftl"), 
cfg.getAutoIncludes());
+
+        assertEquals(ImmutableMap.of("v", 1, "v2", 2), 
cfg.getSharedVariables());
+    }
+
     @SuppressWarnings("boxing")
     private void assertStartsWith(List<String> list, List<String> headList) {
         int index = 0;
@@ -1483,5 +1530,47 @@ public class ConfigurationTest extends TestCase {
         }
         
     }
-    
+
+    private static class ImpliedSettingValuesTestBuilder
+            extends 
Configuration.ExtendableBuilder<ImpliedSettingValuesTestBuilder> {
+
+        ImpliedSettingValuesTestBuilder() {
+            super(Configuration.VERSION_3_0_0);
+        }
+
+        @Override
+        protected TemplateConfigurationFactory 
getImpliedTemplateConfigurations() {
+            return new ConditionalTemplateConfigurationFactory(
+                    new FileNameGlobMatcher("*.yn"),
+                    new 
TemplateConfiguration.Builder().booleanFormat("Y,N").build());
+        }
+
+        @Override
+        protected Map<String, TemplateNumberFormatFactory> 
getImpliedCustomNumberFormats() {
+            return ImmutableMap.<String, TemplateNumberFormatFactory>of(
+                    "hex", HexTemplateNumberFormatFactory.INSTANCE);
+        }
+
+        @Override
+        protected Map<String, TemplateDateFormatFactory> 
getImpliedCustomDateFormats() {
+            return ImmutableMap.<String, TemplateDateFormatFactory>of(
+                    "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE);
+        }
+
+        @Override
+        protected Map<String, String> getImpliedAutoImports() {
+            return ImmutableMap.of("lib", "lib.ftl");
+        }
+
+        @Override
+        protected List<String> getImpliedAutoIncludes() {
+            return ImmutableList.of("inc.ftl");
+        }
+
+        @Override
+        protected Map<String, Object> getImplicitSharedVariables() {
+            return ImmutableMap.<String, Object>of("v", 1);
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/164f292e/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
index e7e5947..93bd1ff 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -64,6 +64,7 @@ import 
org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
 import org.apache.freemarker.core.templateresolver.CacheStorage;
 import org.apache.freemarker.core.templateresolver.GetTemplateResult;
 import 
org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import 
org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
 import 
org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
@@ -372,26 +373,15 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
         ObjectWrapper objectWrapper = builder.getObjectWrapper();
 
         {
-            Map<String, Object> sharedVariables = builder.getSharedVariables();
+            Map<String, Object> sharedVariables = 
_CollectionUtil.mergeImmutableMaps(builder
+                    .getImplicitSharedVariables(), 
builder.getSharedVariables(), false);
 
             HashMap<String, TemplateModel> wrappedSharedVariables = new 
HashMap<>(
-                    (sharedVariables.size() + 5 /* [FM3] 5 legacy vars */) * 4 
/ 3 + 1, 0.75f);
-
-            // TODO [FM3] Get rid of this
-            wrappedSharedVariables.put("capture_output", new CaptureOutput());
-            wrappedSharedVariables.put("compress", StandardCompress.INSTANCE);
-            wrappedSharedVariables.put("html_escape", new HtmlEscape());
-            wrappedSharedVariables.put("normalize_newlines", new 
NormalizeNewlines());
-            wrappedSharedVariables.put("xml_escape", new XmlEscape());
-
-            // In case the inherited sharedVariables aren't empty, we want to 
merge the two maps:
-            wrapAndPutSharedVariables(wrappedSharedVariables, 
builder.getDefaultSharedVariables(),
-                    objectWrapper);
-            if (builder.isSharedVariablesSet()) {
-                wrapAndPutSharedVariables(wrappedSharedVariables, 
sharedVariables, objectWrapper);
-            }
+                    sharedVariables.size() * 4 / 3 + 1, 0.75f);
+            wrapAndPutSharedVariables(wrappedSharedVariables, sharedVariables, 
objectWrapper);
+
             this.wrappedSharedVariables = wrappedSharedVariables;
-            this.sharedVariables = Collections.unmodifiableMap(new 
LinkedHashMap<>(sharedVariables));
+            this.sharedVariables = 
Collections.unmodifiableMap(sharedVariables);
         }
 
         // ParsingConfiguration settings:
@@ -426,10 +416,14 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
         showErrorTips = builder.getShowErrorTips();
         apiBuiltinEnabled = builder.getAPIBuiltinEnabled();
         logTemplateExceptions = builder.getLogTemplateExceptions();
-        customDateFormats = builder.getCustomDateFormats();
-        customNumberFormats = builder.getCustomNumberFormats();
-        autoImports = builder.getAutoImports();
-        autoIncludes = builder.getAutoIncludes();
+        customDateFormats = _CollectionUtil.mergeImmutableMaps(
+                builder.getImpliedCustomDateFormats(), 
builder.getCustomDateFormats(), false);
+        customNumberFormats = _CollectionUtil.mergeImmutableMaps(
+                builder.getImpliedCustomNumberFormats(), 
builder.getCustomNumberFormats(), false);
+        autoImports = _CollectionUtil.mergeImmutableMaps(
+                builder.getImpliedAutoImports(), builder.getAutoImports(), 
true);
+        autoIncludes = _CollectionUtil.mergeImmutableLists(
+                builder.getImpliedAutoIncludes(), builder.getAutoIncludes(), 
true);
         lazyImports = builder.getLazyImports();
         lazyAutoImports = builder.getLazyAutoImports();
         customSettings = builder.getCustomSettings(false);
@@ -474,11 +468,21 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
                     templateResolver, TEMPLATE_NAME_FORMAT_KEY, 
templateNameFormat);
         }
 
-        templateConfigurations = builder.getTemplateConfigurations();
+        TemplateConfigurationFactory templateConfigurations = 
builder.getTemplateConfigurations();
         if (!templateResolver.supportsTemplateConfigurationsSetting()) {
             checkSettingIsNullForThisTemplateResolver(
                     templateResolver, TEMPLATE_CONFIGURATIONS_KEY, 
templateConfigurations);
         }
+        TemplateConfigurationFactory impliedTemplateConfigurations = 
builder.getImpliedTemplateConfigurations();
+        if (impliedTemplateConfigurations != null) {
+            if (templateConfigurations != null) {
+                templateConfigurations = new 
MergingTemplateConfigurationFactory(
+                        impliedTemplateConfigurations, templateConfigurations);
+            } else {
+                templateConfigurations = impliedTemplateConfigurations;
+            }
+        }
+        this.templateConfigurations = templateConfigurations;
 
         templateResolver.setDependencies(new 
TemplateResolverDependenciesImpl(this, templateResolver));
     }
@@ -2338,6 +2342,18 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
         }
 
         /**
+         * The template configurations that will be added to the built {@link 
Configuration} before the ones
+         * coming from {@link #setCustomNumberFormats(Map)}}, where addition 
happens with
+         * {@link MergingTemplateConfigurationFactory}. When overriding this 
method, always
+         * consider adding to the return value of the super method, rather 
than replacing it.
+         *
+         * @return Maybe {@code null}.
+         */
+        protected TemplateConfigurationFactory 
getImpliedTemplateConfigurations() {
+            return null;
+        }
+
+        /**
          * Setter pair of {@link Configuration#getTemplateConfigurations()}.
          */
         public void setTemplateConfigurations(TemplateConfigurationFactory 
templateConfigurations) {
@@ -2515,10 +2531,38 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
             return sharedVariables != null;
         }
 
+        /**
+         * The {@link Map} to use as shared variables if {@link 
#isSharedVariablesSet()} is {@code false}.
+         *
+         * @see #getImplicitSharedVariables()
+         */
         protected Map<String, Object> getDefaultSharedVariables() {
             return Collections.emptyMap();
         }
 
+        private static final Map<String, Object> DEFAULT_SHARED_VARIABLES;
+        static {
+            // TODO [FM3] Get rid of these
+            Map<String, Object> sharedVariables = new HashMap<>();
+            sharedVariables.put("capture_output", new CaptureOutput());
+            sharedVariables.put("compress", StandardCompress.INSTANCE);
+            sharedVariables.put("html_escape", new HtmlEscape());
+            sharedVariables.put("normalize_newlines", new NormalizeNewlines());
+            sharedVariables.put("xml_escape", new XmlEscape());
+            DEFAULT_SHARED_VARIABLES = 
Collections.unmodifiableMap(sharedVariables);
+        }
+
+        /**
+         * The shared variables that will be added to the built {@link 
Configuration} before the ones coming from
+         * {@link #getSharedVariables()}. When overriding this method, always 
consider adding to the return value
+         * of the super method, rather than replacing it.
+         *
+         * @return Immutable {@link Map}; not {@code null}
+         */
+        protected Map<String, Object> getImplicitSharedVariables() {
+            return DEFAULT_SHARED_VARIABLES;
+        }
+
         /**
          * Setter pair of {@link Configuration#getSharedVariables()}.
          *
@@ -2669,11 +2713,27 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
             return Collections.emptyMap();
         }
 
+        /**
+         * {@inheritDoc}
+         *
+         * @see #getImpliedCustomNumberFormats()
+         */
         @Override
         protected TemplateNumberFormatFactory 
getDefaultCustomNumberFormat(String name) {
             return null;
         }
 
+        /**
+         * The custom number formats that will be added to the built {@link 
Configuration} before the ones coming from
+         * {@link #getCustomNumberFormats()}. When overriding this method, 
always consider adding to the return
+         * value of the super method, rather than replacing it.
+         *
+         * @return Immutable {@link Map}; not {@code null}
+         */
+        protected Map<String, TemplateNumberFormatFactory> 
getImpliedCustomNumberFormats() {
+            return Collections.emptyMap();
+        }
+
         @Override
         protected String getDefaultBooleanFormat() {
             return TemplateBooleanFormat.C_TRUE_FALSE;
@@ -2694,11 +2754,27 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
             return "";
         }
 
+        /**
+         * {@inheritDoc}
+         *
+         * @see #getImpliedCustomDateFormats()
+         */
         @Override
         protected Map<String, TemplateDateFormatFactory> 
getDefaultCustomDateFormats() {
             return Collections.emptyMap();
         }
 
+        /**
+         * The custom date formats that will be added to the built {@link 
Configuration} before the ones coming from
+         * {@link #getCustomDateFormats()}. When overriding this method, 
always consider adding to the return value
+         * of the super method, rather than replacing it.
+         *
+         * @return Immutable {@link Map}; not {@code null}
+         */
+        protected Map<String, TemplateDateFormatFactory> 
getImpliedCustomDateFormats() {
+            return Collections.emptyMap();
+        }
+
         @Override
         protected TemplateDateFormatFactory getDefaultCustomDateFormat(String 
name) {
             return null;
@@ -2781,16 +2857,46 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
             return null;
         }
 
+        /**
+         * {@inheritDoc}
+         *
+         * @see #getImpliedAutoImports()
+         */
         @Override
         protected Map<String, String> getDefaultAutoImports() {
             return Collections.emptyMap();
         }
 
+        /**
+         * The auto-imports that will be added to the built {@link 
Configuration} before the ones coming from
+         * {@link #getSharedVariables()}. When overriding this method, always 
consider adding to the return value
+         * of the super method, rather than replacing it.
+         */
+        protected Map<String, String> getImpliedAutoImports() {
+            return Collections.emptyMap();
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @see #getImpliedAutoIncludes()
+         */
         @Override
         protected List<String> getDefaultAutoIncludes() {
             return Collections.emptyList();
         }
 
+        /**
+         * The imports that will be added to the built {@link Configuration} 
before the ones coming from
+         * {@link #getAutoIncludes()}. When overriding this method, always 
consider adding to the return
+         * value of the super method, rather than replacing it.
+         *
+         * @return Immutable {@link List}; not {@code null}
+         */
+        protected List<String> getImpliedAutoIncludes() {
+            return Collections.emptyList();
+        }
+
         @Override
         protected Object getDefaultCustomSetting(Serializable key, Object 
defaultValue, boolean useDefaultValue) {
             if (useDefaultValue) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/164f292e/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 75eac65..98d927f 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
@@ -555,7 +555,7 @@ public abstract class MutableProcessingConfiguration<SelfT 
extends MutableProces
     /**
      * Fluent API equivalent of {@link #setCustomNumberFormats(Map)}
      */
-    public SelfT customNumberFormats(Map<String, TemplateNumberFormatFactory> 
value) {
+    public SelfT customNumberFormats(Map<String, ? extends 
TemplateNumberFormatFactory> value) {
         setCustomNumberFormats(value);
         return self();
     }
@@ -619,7 +619,7 @@ public abstract class MutableProcessingConfiguration<SelfT 
extends MutableProces
         int commaIdx = booleanFormat.indexOf(',');
         if (commaIdx == -1) {
             throw new IllegalArgumentException(
-                    "Setting value must be string that contains two 
comma-separated values for true and false, " +
+                    "Setting value must be a string that contains two 
comma-separated values for true and false, " +
                     "respectively.");
         }
         
@@ -826,7 +826,7 @@ public abstract class MutableProcessingConfiguration<SelfT 
extends MutableProces
     /**
      * Fluent API equivalent of {@link #setCustomDateFormats(Map)}
      */
-    public SelfT customDateFormats(Map<String, TemplateDateFormatFactory> 
value) {
+    public SelfT customDateFormats(Map<String, ? extends 
TemplateDateFormatFactory> value) {
         setCustomDateFormats(value);
         return self();
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/164f292e/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
index 3a0122d..8113eb8 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
@@ -661,7 +661,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
         return customLookupCondition;
     }
 
-    // TODO [FM3] Should not be public, should be final field
+    // TODO [FM3] Should be gone; see comment above the lookupLocale field
     /**
      * Mostly only used internally; setter pair of {@link 
#getCustomLookupCondition()}. This meant to be called directly
      * after instantiating the template with its constructor, after a 
successfull lookup that used this condition. So
@@ -803,7 +803,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
         return tCfg != null && tCfg.isLocaleSet() ? tCfg.getLocale() : 
cfg.getLocale();
     }
 
-    // TODO [FM3] Temporary hack; See comment above the locale field
+    // TODO [FM3] Temporary hack; See comment above the lookupLocale field
     public void setLookupLocale(Locale lookupLocale) {
         this.lookupLocale = lookupLocale;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/164f292e/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 4aaa7c2..284ec6b 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,20 +21,16 @@ package org.apache.freemarker.core;
 import java.io.Reader;
 import java.io.Serializable;
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 import java.util.TimeZone;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.util.CommonBuilder;
+import org.apache.freemarker.core.util._CollectionUtil;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
@@ -139,52 +135,6 @@ 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;
-        if (m1.isEmpty()) return m2;
-        if (m2.isEmpty()) return m1;
-
-        LinkedHashMap<K, V> mergedM = new LinkedHashMap<>((m1.size() + 
m2.size()) * 4 / 3 + 1, 0.75f);
-        mergedM.putAll(m1);
-        if (overwriteUpdatesOrder) {
-            for (K m2Key : m2.keySet()) {
-                mergedM.remove(m2Key); // So that duplicate keys are moved 
after m1 keys
-            }
-        }
-        mergedM.putAll(m2);
-        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, boolean skipDuplicatesInList1) {
-        if (list1 == null) return list2;
-        if (list2 == null) return list1;
-        if (list1.isEmpty()) return list2;
-        if (list2.isEmpty()) return list1;
-
-        ArrayList<String> mergedList = new ArrayList<>(list1.size() + 
list2.size());
-        if (skipDuplicatesInList1) {
-            Set<String> list2Set = new HashSet<>(list2);
-            for (String it : list1) {
-                if (!list2Set.contains(it)) {
-                    mergedList.add(it);
-                }
-            }
-        } else {
-            mergedList.addAll(list1);
-        }
-        mergedList.addAll(list2);
-        return Collections.unmodifiableList(mergedList);
-    }
-
     @Override
     public TagSyntax getTagSyntax() {
         if (!isTagSyntaxSet()) {
@@ -858,13 +808,13 @@ public final class TemplateConfiguration implements 
ParsingAndProcessingConfigur
                 setBooleanFormat(tc.getBooleanFormat());
             }
             if (tc.isCustomDateFormatsSet()) {
-                setCustomDateFormats(mergeMaps(
+                setCustomDateFormats(_CollectionUtil.mergeImmutableMaps(
                         isCustomDateFormatsSet() ? getCustomDateFormats() : 
null, tc.getCustomDateFormats(), false),
                         true
                 );
             }
             if (tc.isCustomNumberFormatsSet()) {
-                setCustomNumberFormats(mergeMaps(
+                setCustomNumberFormats(_CollectionUtil.mergeImmutableMaps(
                         isCustomNumberFormatsSet() ? getCustomNumberFormats() 
: null, tc.getCustomNumberFormats(), false),
                         true);
             }
@@ -941,24 +891,24 @@ public final class TemplateConfiguration implements 
ParsingAndProcessingConfigur
                 setLazyAutoImports(tc.getLazyAutoImports());
             }
             if (tc.isAutoImportsSet()) {
-                setAutoImports(mergeMaps(
+                setAutoImports(_CollectionUtil.mergeImmutableMaps(
                         isAutoImportsSet() ? getAutoImports() : null,
                         tc.isAutoImportsSet() ? tc.getAutoImports() : null,
                         true),
                         true);
             }
             if (tc.isAutoIncludesSet()) {
-                setAutoIncludes(mergeLists(
+                setAutoIncludes(_CollectionUtil.mergeImmutableLists(
                         isAutoIncludesSet() ? getAutoIncludes() : null,
                         tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null,
                         true),
                         true);
             }
 
-            setCustomSettingsMap(mergeMaps(
+            setCustomSettingsMap(_CollectionUtil.mergeImmutableMaps(
                     getCustomSettings(false),
                     tc.getCustomSettings(false),
-                    true));
+                    false));
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/164f292e/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 1f91821..d5a6574 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
@@ -23,8 +23,11 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Don't use this; used internally by FreeMarker, might changes without notice.
@@ -144,4 +147,52 @@ public class _CollectionUtil {
         return isMapKnownToBeUnmodifiable(map) ? map : 
Collections.unmodifiableMap(map);
     }
 
+    /**
+     * 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.
+     */
+    public static <K,V> Map<K,V> mergeImmutableMaps(Map<K,V> m1, Map<K,V> m2, 
boolean keepOriginalOrder) {
+        if (m1 == null) return m2;
+        if (m2 == null) return m1;
+        if (m1.isEmpty()) return m2;
+        if (m2.isEmpty()) return m1;
+
+        Map<K, V> mergedM = keepOriginalOrder
+                ? new LinkedHashMap<K, V>((m1.size() + m2.size()) * 4 / 3 + 1, 
0.75f)
+                : new HashMap<K, V>((m1.size() + m2.size()) * 4 / 3 + 1, 
0.75f);
+        mergedM.putAll(m1);
+        if (keepOriginalOrder) {
+            for (K m2Key : m2.keySet()) {
+                mergedM.remove(m2Key); // So that duplicate keys are moved 
after m1 keys
+            }
+        }
+        mergedM.putAll(m2);
+        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.
+     */
+    public static List<String> mergeImmutableLists(List<String> list1, 
List<String> list2,
+            boolean skipDuplicatesInList1) {
+        if (list1 == null) return list2;
+        if (list2 == null) return list1;
+        if (list1.isEmpty()) return list2;
+        if (list2.isEmpty()) return list1;
+
+        ArrayList<String> mergedList = new ArrayList<>(list1.size() + 
list2.size());
+        if (skipDuplicatesInList1) {
+            Set<String> list2Set = new HashSet<>(list2);
+            for (String it : list1) {
+                if (!list2Set.contains(it)) {
+                    mergedList.add(it);
+                }
+            }
+        } else {
+            mergedList.addAll(list1);
+        }
+        mergedList.addAll(list2);
+        return Collections.unmodifiableList(mergedList);
+    }
 }

Reply via email to