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/03 11:47:02 UTC

[1/2] incubator-freemarker git commit: Renamed "customAttribute" (just "attribute" in FM2) to "customSetting". So what happened is that FM2 attributes were split to two separate concepts: custom state scopes (see CustomStateScope interface) and custom se

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 dc689993a -> 24673a17b


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/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 f165990..feac008 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
@@ -111,12 +111,12 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
     private Map prefixToNamespaceURILookup = new HashMap();
     private Map namespaceURIToPrefixLookup = new HashMap();
     /** Custom attributes specified inside the template with the #ftl directive. Maybe {@code null}. */
-    private Map<String, Serializable> headerCustomAttributes;
+    private Map<String, Serializable> headerCustomSettings;
     /**
-     * In case {@link #headerCustomAttributes} is not {@code null} and the {@link TemplateConfiguration} also specifies
+     * In case {@link #headerCustomSettings} is not {@code null} and the {@link TemplateConfiguration} also specifies
      * custom attributes, this is the two set of custom attributes merged. Otherwise it's {@code null}.
      */
-    private transient Map<Serializable, Object> tcAndHeaderCustomAttributes;
+    private transient Map<Serializable, Object> tcAndHeaderCustomSettings;
 
     private AutoEscapingPolicy autoEscapingPolicy;
     // Values from template content that are detected automatically:
@@ -345,8 +345,8 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
      * protected when construction is done.
      */
     private void finishConstruction() {
-        headerCustomAttributes = _CollectionUtil.unmodifiableMap(headerCustomAttributes);
-        tcAndHeaderCustomAttributes = _CollectionUtil.unmodifiableMap(tcAndHeaderCustomAttributes);
+        headerCustomSettings = _CollectionUtil.unmodifiableMap(headerCustomSettings);
+        tcAndHeaderCustomSettings = _CollectionUtil.unmodifiableMap(tcAndHeaderCustomSettings);
         writeProtected = true;
     }
 
@@ -1068,24 +1068,24 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
     @Override
-    public Map<Serializable, Object> getCustomAttributesSnapshot(boolean includeInherited) {
+    public Map<Serializable, Object> getCustomSettingsSnapshot(boolean includeInherited) {
         boolean nonInheritedAttrsFinal;
         Map<? extends Serializable, ? extends Object> nonInheritedAttrs;
-        if (tcAndHeaderCustomAttributes != null) {
-            nonInheritedAttrs = tcAndHeaderCustomAttributes;
+        if (tcAndHeaderCustomSettings != null) {
+            nonInheritedAttrs = tcAndHeaderCustomSettings;
             nonInheritedAttrsFinal = writeProtected;
-        } else if (headerCustomAttributes != null) {
-            nonInheritedAttrs = headerCustomAttributes;
+        } else if (headerCustomSettings != null) {
+            nonInheritedAttrs = headerCustomSettings;
             nonInheritedAttrsFinal = writeProtected;
         } else if (tCfg != null) {
-            nonInheritedAttrs =  tCfg.getCustomAttributesSnapshot(false);
+            nonInheritedAttrs =  tCfg.getCustomSettingsSnapshot(false);
             nonInheritedAttrsFinal = true;
         } else {
             nonInheritedAttrs = Collections.emptyMap();
             nonInheritedAttrsFinal = true;
         }
 
-        Map<Serializable, Object> inheritedAttrs = includeInherited ? cfg.getCustomAttributesSnapshot(true)
+        Map<Serializable, Object> inheritedAttrs = includeInherited ? cfg.getCustomSettingsSnapshot(true)
                 : Collections.<Serializable, Object>emptyMap();
 
         LinkedHashMap<Serializable, Object> mergedAttrs;
@@ -1103,65 +1103,65 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
     }
 
     @Override
-    public boolean isCustomAttributeSet(Serializable key) {
-        if (tcAndHeaderCustomAttributes != null) {
-            return tcAndHeaderCustomAttributes.containsKey(key);
+    public boolean isCustomSettingSet(Serializable key) {
+        if (tcAndHeaderCustomSettings != null) {
+            return tcAndHeaderCustomSettings.containsKey(key);
         }
-        return headerCustomAttributes != null && headerCustomAttributes.containsKey(key)
-                || tCfg != null && tCfg.isCustomAttributeSet(key);
+        return headerCustomSettings != null && headerCustomSettings.containsKey(key)
+                || tCfg != null && tCfg.isCustomSettingSet(key);
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key) {
-        return getCustomAttribute(key, null, false);
+    public Object getCustomSetting(Serializable key) {
+        return getCustomSetting(key, null, false);
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key, Object defaultValue) {
-        return getCustomAttribute(key, defaultValue, true);
+    public Object getCustomSetting(Serializable key, Object defaultValue) {
+        return getCustomSetting(key, defaultValue, true);
     }
 
-    private Object getCustomAttribute(Serializable key, Object defaultValue, boolean useDefaultValue) {
-        if (tcAndHeaderCustomAttributes != null) {
-            Object value = tcAndHeaderCustomAttributes.get(key);
-            if (value != null || tcAndHeaderCustomAttributes.containsKey(key)) {
+    private Object getCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
+        if (tcAndHeaderCustomSettings != null) {
+            Object value = tcAndHeaderCustomSettings.get(key);
+            if (value != null || tcAndHeaderCustomSettings.containsKey(key)) {
                 return value;
             }
         } else {
-            if (headerCustomAttributes != null) {
-                Object value = headerCustomAttributes.get(key);
-                if (value != null || headerCustomAttributes.containsKey(key)) {
+            if (headerCustomSettings != null) {
+                Object value = headerCustomSettings.get(key);
+                if (value != null || headerCustomSettings.containsKey(key)) {
                     return value;
                 }
             }
             if (tCfg != null) {
-                Object value = tCfg.getCustomAttribute(key, MISSING_VALUE_MARKER);
+                Object value = tCfg.getCustomSetting(key, MISSING_VALUE_MARKER);
                 if (value != MISSING_VALUE_MARKER) {
                     return value;
                 }
             }
         }
-        return useDefaultValue ? cfg.getCustomAttribute(key, defaultValue) : cfg.getCustomAttribute(key);
+        return useDefaultValue ? cfg.getCustomSetting(key, defaultValue) : cfg.getCustomSetting(key);
     }
 
     /**
      * Should be called by the parser, for example to add the attributes specified in the #ftl header.
      */
-    void setHeaderCustomAttribute(String attName, Serializable attValue) {
+    void setHeaderCustomSetting(String attName, Serializable attValue) {
         checkWritable();
 
-        if (headerCustomAttributes == null) {
-            headerCustomAttributes = new LinkedHashMap<>();
+        if (headerCustomSettings == null) {
+            headerCustomSettings = new LinkedHashMap<>();
         }
-        headerCustomAttributes.put(attName, attValue);
+        headerCustomSettings.put(attName, attValue);
 
         if (tCfg != null) {
-            Map<Serializable, Object> tcCustAttrs = tCfg.getCustomAttributesSnapshot(false);
+            Map<Serializable, Object> tcCustAttrs = tCfg.getCustomSettingsSnapshot(false);
             if (!tcCustAttrs.isEmpty()) {
-                if (tcAndHeaderCustomAttributes == null) {
-                    tcAndHeaderCustomAttributes = new LinkedHashMap<>(tcCustAttrs);
+                if (tcAndHeaderCustomSettings == null) {
+                    tcAndHeaderCustomSettings = new LinkedHashMap<>(tcCustAttrs);
                 }
-                tcAndHeaderCustomAttributes.put(attName, attValue);
+                tcAndHeaderCustomSettings.put(attName, attValue);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/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 f727002..ca37fa6 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
@@ -83,7 +83,7 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
     private final Boolean lazyImports;
     private final Boolean lazyAutoImports;
     private final boolean lazyAutoImportsSet;
-    private final Map<Serializable, Object> customAttributes;
+    private final Map<Serializable, Object> customSettings;
     
     private final TemplateLanguage templateLanguage;
     private final TagSyntax tagSyntax;
@@ -124,7 +124,7 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         lazyImports = builder.isLazyImportsSet() ? builder.getLazyImports() : null;
         lazyAutoImportsSet = builder.isLazyAutoImportsSet();
         lazyAutoImports = lazyAutoImportsSet ? builder.getLazyAutoImports() : null;
-        customAttributes = builder.getCustomAttributesSnapshot(false);
+        customSettings = builder.getCustomSettingsSnapshot(false);
 
         templateLanguage = builder.isTemplateLanguageSet() ? builder.getTemplateLanguage() : null;
         tagSyntax = builder.isTagSyntaxSet() ? builder.getTagSyntax() : null;
@@ -634,28 +634,28 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
      * Note that the {@code includeInherited} has no effect here, as {@link TemplateConfiguration}-s has no parent.
      */
     @Override
-    public Map<Serializable, Object> getCustomAttributesSnapshot(boolean includeInherited) {
-        return customAttributes;
+    public Map<Serializable, Object> getCustomSettingsSnapshot(boolean includeInherited) {
+        return customSettings;
     }
 
     @Override
-    public boolean isCustomAttributeSet(Serializable key) {
-        return customAttributes.containsKey(key);
+    public boolean isCustomSettingSet(Serializable key) {
+        return customSettings.containsKey(key);
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key) {
-        Object result = getCustomAttribute(key, MISSING_VALUE_MARKER);
+    public Object getCustomSetting(Serializable key) {
+        Object result = getCustomSetting(key, MISSING_VALUE_MARKER);
         if (result == MISSING_VALUE_MARKER) {
-            throw new CustomAttributeNotSetException(key);
+            throw new CustomSettingNotSetException(key);
         }
         return result;
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key, Object defaultValue) {
-        Object attValue = customAttributes.get(key);
-        if (attValue != null || customAttributes.containsKey(key)) {
+    public Object getCustomSetting(Serializable key, Object defaultValue) {
+        Object attValue = customSettings.get(key);
+        if (attValue != null || customSettings.containsKey(key)) {
             return attValue;
         }
         return defaultValue;
@@ -804,16 +804,16 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
         }
 
         @Override
-        protected Object getDefaultCustomAttribute(Serializable key, Object defaultValue, boolean useDefaultValue) {
+        protected Object getDefaultCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
             // We don't inherit from anything.
             if (useDefaultValue) {
                 return defaultValue;
             }
-            throw new CustomAttributeNotSetException(key);
+            throw new CustomSettingNotSetException(key);
         }
 
         @Override
-        protected void collectDefaultCustomAttributesSnapshot(Map<Serializable, Object> target) {
+        protected void collectDefaultCustomSettingsSnapshot(Map<Serializable, Object> target) {
             // We don't inherit from anything.
         }
 
@@ -934,9 +934,9 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
                         tc.isAutoIncludesSet() ? tc.getAutoIncludes() : null));
             }
 
-            setCustomAttributesMap(mergeMaps(
-                    getCustomAttributesSnapshot(false),
-                    tc.getCustomAttributesSnapshot(false),
+            setCustomSettingsMap(mergeMaps(
+                    getCustomSettingsSnapshot(false),
+                    tc.getCustomSettingsSnapshot(false),
                     true));
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj
index 0be9d81..552f236 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -3955,9 +3955,9 @@ void HeaderElement() :
                                 }
                             } catch (TemplateModelException tme) {
                             }
-                        } else if (ks.equalsIgnoreCase("attributes")) {
+                        } else if (ks.equalsIgnoreCase("custom_settings") || ks.equals("customSettings")) {
                             if (!(value instanceof TemplateHashModelEx)) {
-                                throw new ParseException("Expecting a hash of attribute names to values.", exp);
+                                throw new ParseException("Expecting a hash value for custom settings.", exp);
                             }
                             TemplateHashModelEx attributeMap = (TemplateHashModelEx) value;
                             try {
@@ -3967,11 +3967,11 @@ void HeaderElement() :
                                         Object attValue = DeepUnwrap.unwrap(attributeMap.get(attName));
                                         if (attValue != null && !(attValue instanceof Serializable)) {
                                             throw new ParseException(
-                                                    "Value of attribute " + _StringUtil.jQuote(attName)
+                                                    "Value of custom setting " + _StringUtil.jQuote(attName)
                                                     + " should implement java.io.Serializable.",
                                                     exp);
                                         }
-                                        template.setHeaderCustomAttribute(attName, (Serializable) attValue);
+                                        template.setHeaderCustomSetting(attName, (Serializable) attValue);
                                 }
                             } catch (TemplateModelException tme) {
                             }
@@ -3979,6 +3979,9 @@ void HeaderElement() :
                             String correctName;
 	                        if (ks.equals("charset") || ks.equals("source_encoding") || ks.equals("sourceEncoding")) {
 	                            correctName = "encoding";
+	                        } else if (ks.equals("attributes")) {
+	                            correctName = token_source.namingConvention == NamingConvention.CAMEL_CASE
+	                                    ? "customSettings" : "custom_settings";
 	                        } else if (ks.equals("xmlns")) {
 	                            // [2.4] If camel case will be the default, update this
                                 correctName

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
index f4a4895..016e24d 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -902,7 +902,7 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     private ContentType getTemplateSpecificContentType(final Template template) {
-        Object contentTypeAttr = template.getCustomAttribute("content_type", null);
+        Object contentTypeAttr = template.getCustomSetting("content_type", null);
         if (contentTypeAttr != null) {
             // Converted with toString() for backward compatibility.
             return new ContentType(contentTypeAttr.toString());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
index e3c05d4..2b25a29 100644
--- a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
+++ b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/FreemarkerServletTest.java
@@ -44,8 +44,6 @@ import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurati
 import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
-import org.apache.freemarker.servlet.FreemarkerServlet;
-import org.apache.freemarker.servlet.FreemarkerServletConfigurationBuilder;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.mock.web.MockHttpServletRequest;
@@ -314,7 +312,7 @@ public class FreemarkerServletTest {
         } catch (ServletException e) {
             assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
-        // But the legacy content_type template attribute can still set the output charset:
+        // But the legacy content_type template custom setting can still set the output charset:
         assertOutputEncodingEquals(
                 StandardCharsets.UTF_8, // <- expected response.characterEncoding
                 StandardCharsets.UTF_8, // <- expected env.outputEncoding
@@ -349,7 +347,7 @@ public class FreemarkerServletTest {
         } catch (ServletException e) {
             assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
-        // The legacy content_type template attribute can still specify an output charset, though it will be ignored:
+        // The legacy content_type template custom setting can still specify an output charset, though it will be ignored:
         assertOutputEncodingEquals(
                 SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
                 SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
@@ -394,7 +392,7 @@ public class FreemarkerServletTest {
         } catch (ServletException e) {
             assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
-        // The legacy content_type template attribute can still specify an output charset, though it will be overridden:
+        // The legacy content_type template custom setting can still specify an output charset, though it will be overridden:
         assertOutputEncodingEquals(
                 StandardCharsets.UTF_16LE, // <- expected response.characterEncoding
                 StandardCharsets.UTF_16LE, // <- expected env.outputEncoding
@@ -583,10 +581,10 @@ public class FreemarkerServletTest {
                         .getBytes(StandardCharsets.UTF_8));
                 tl.putTemplate(FOO_OUT_UTF8_FTL, "foo"
                         .getBytes(StandardCharsets.UTF_8));
-                tl.putTemplate(CONTENT_TYPE_ATTR_FTL, "<#ftl attributes={ 'content_type': 'text/plain' }>foo"
+                tl.putTemplate(CONTENT_TYPE_ATTR_FTL, "<#ftl customSettings={ 'content_type': 'text/plain' }>foo"
                         .getBytes(StandardCharsets.UTF_8));
                 tl.putTemplate(CONTENT_TYPE_ATTR_WITH_CHARSET_FTL,
-                        "<#ftl attributes={ 'content_type': 'text/plain; charset=UTF-8' }>foo"
+                        "<#ftl customSettings={ 'content_type': 'text/plain; charset=UTF-8' }>foo"
                         .getBytes(StandardCharsets.UTF_8));
                 tl.putTemplate(OUTPUT_FORMAT_HEADER_FTL, "<#ftl outputFormat='plainText'>foo"
                         .getBytes(StandardCharsets.UTF_8));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
index 053ef32..32412a9 100644
--- a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
+++ b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
@@ -87,7 +87,7 @@ public class RealServletContainertTest extends WebAppTestCase {
     }
 
     @Test
-    public void basicCustomAttributes() throws Exception {
+    public void basicServletScopeAttributes() throws Exception {
         restartWebAppIfStarted(WEBAPP_BASIC);  // To clear the application scope attributes
         assertExpectedEqualsOutput(WEBAPP_BASIC, "attributes.txt", "tester"
                 + "?action=" + AllKindOfContainersModel2Action.class.getName()


[2/2] incubator-freemarker git commit: Renamed "customAttribute" (just "attribute" in FM2) to "customSetting". So what happened is that FM2 attributes were split to two separate concepts: custom state scopes (see CustomStateScope interface) and custom se

Posted by dd...@apache.org.
Renamed "customAttribute" (just "attribute" in FM2) to "customSetting". So what happened is that FM2 attributes were split to two separate concepts: custom state scopes (see CustomStateScope interface) and custom settings. The last are immutable, just like the standard configuration settings. As a consequence, <#ftl attributes={...}> has also changed to <#ftl customSettings={...}>.


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

Branch: refs/heads/3
Commit: 24673a17b0bb7a74e8f3a1b0d0306732cee133ae
Parents: dc68999
Author: ddekany <dd...@apache.org>
Authored: Fri Jun 2 13:55:08 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Jun 3 13:46:44 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                              |  23 +-
 .../freemarker/core/CustomAttributeTest.java    | 281 -------------------
 .../freemarker/core/CustomSettingTest.java      | 281 +++++++++++++++++++
 .../core/ParsingErrorMessagesTest.java          |   7 +
 .../core/TemplateConfigurationTest.java         | 142 +++++-----
 ...gurationWithDefaultTemplateResolverTest.java |  62 ++--
 .../TemplateConfigurationFactoryTest.java       |  12 +-
 .../apache/freemarker/core/Configuration.java   |  36 +--
 .../core/CustomAttributeNotSetException.java    |  48 ----
 .../core/CustomSettingNotSetException.java      |  49 ++++
 .../org/apache/freemarker/core/Environment.java |  10 +-
 .../core/MutableProcessingConfiguration.java    | 132 ++++-----
 .../core/ProcessingConfiguration.java           |  30 +-
 .../org/apache/freemarker/core/Template.java    |  76 ++---
 .../freemarker/core/TemplateConfiguration.java  |  36 +--
 freemarker-core/src/main/javacc/FTL.jj          |  11 +-
 .../freemarker/servlet/FreemarkerServlet.java   |   2 +-
 .../servlet/FreemarkerServletTest.java          |  12 +-
 .../servlet/jsp/RealServletContainertTest.java  |   2 +-
 19 files changed, 630 insertions(+), 622 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index 1ca0250..b3676a9 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -173,18 +173,17 @@ the FreeMarer 3 changelog here:
 - Require customLookupCondition-s to be Serializable.
 - Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer
   builders to create them.
-  - Removed CustomAttribute class. Custom attribute keys can be anything at the moment (this will be certainly restricted later)
-  - As customAttributes won't be modifiable after Builder.build(), they can't be used for on-demand created data structures anymore (such as
-    Template-scoped caches) anymore. To fulfill that role, the CustomStateKey class and the CustomStateScope interface was introduced, which
-    is somewhat similar to the now removed CustomAttribute. CustomStateScope contains one method, Object getCustomState(CustomStateKey), which
-    may calls CustomStateKey.create() to lazily create the state object for the key. Configuration, Template and Environment implements
-    CustomStateScope.
-  - Cleaned up customAttribute related API-s. Most notably, a new getCustomAttribute overload was added, `getCustomAttribute(Serializable key, Object default)`,
-    where the default value is used if the attribute wasn't set (not even to null). (There's a reserved ProcessingConfiguration.MISSING_VALUE_MARKER that
-    can be used as default but not as the value of a custom attribute). `getCustomAttribute(Serializable key)` now throws MissingCustomAttributeValue exception
-    if the attribute isn't set. Also, getCustomAttributesSnapshot(includeInherited) was added, which is mostly useful for debugging purposes. Also note that the
-    key of custom attributes now must be Serializable.
-  - Environment.setCustomState(Object, Object) and getCustomState(Object) was replaced with CustomStateScope.getCustomState(CustomStateKey).
+  - Removed CustomAttribute class. Custom attribute keys can be anything Serializable.
+  - As "attributes" can't be modifiable after Builder.build(), and hence didn't fit into the new system, the "attributes" setting was removed.
+    In place of, two new concepts were introduced, targetting two different "attributes" use-cases:
+    - Custom states: CustomStateKey class and the CustomStateScope interface was introduced, which
+      is somewhat similar to the now removed CustomAttribute. CustomStateScope contains one method, Object getCustomState(CustomStateKey), which
+      may calls CustomStateKey.create() to lazily create the state object for the key. Configuration, Template and Environment implements
+      CustomStateScope, so you can store state in any of these scopes.
+    - Custom settings: These have the same mutability rules as normal settings, that is, you can't modify them except in builders objects and
+      in the Envronment. You can get their values with `getCustomSetting(Serializble key[, Object defaultValue])`. If no default is specified,
+      and the custom setting is not set, a CustomSettingNotSetException is thrown. Just like standard settings, custom settings automatically
+      inherited from higher scopes.
   - Added ProcessingConfiguration interface for the read-only access of template processing settings. This is similar to the
     already existing (in FM2) ParserConfiguration interface.
   - Renamed Configurable to MutableProcessingAndParserConfiguration. Made it abstract too.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
deleted file mode 100644
index d714380..0000000
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import static org.apache.freemarker.core.ProcessingConfiguration.MISSING_VALUE_MARKER;
-import static org.hamcrest.Matchers.containsString;
-import static org.junit.Assert.*;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.Collections;
-import java.util.Map;
-
-import org.junit.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-@SuppressWarnings("boxing")
-public class CustomAttributeTest {
-    
-    private static final String KEY_1 = "key1";
-    private static final String KEY_2 = "key2";
-    private static final String KEY_3 = "key3";
-    private static final Integer KEY_4 = 4;
-
-    private static final Integer VALUE_1 = 1; // Serializable
-    private static final Object VALUE_2 = new Object();
-    private static final Object VALUE_3 = new Object();
-    private static final Object VALUE_4 = new Object();
-
-    @Test
-    public void testMutableProcessingConfiguration() throws Exception {
-        testMutableProcessingConfiguration(new Configuration.Builder(Configuration.VERSION_3_0_0));
-
-        testMutableProcessingConfiguration(new TemplateConfiguration.Builder());
-
-        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
-        Environment env = new Template(null, "", cfg).createProcessingEnvironment(null, null);
-        testMutableProcessingConfiguration(env);
-    }
-
-    private void testMutableProcessingConfiguration(MutableProcessingConfiguration<?> mpc) {
-        assertTrue(mpc.getCustomAttributesSnapshot(true).isEmpty());
-        assertTrue(mpc.getCustomAttributesSnapshot(false).isEmpty());
-        testMissingCustomAttributeAccess(mpc, KEY_1);
-
-        mpc.setCustomAttribute(KEY_1, VALUE_1);
-        mpc.setCustomAttribute(KEY_2, VALUE_2);
-
-        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
-        testMissingCustomAttributeAccess(mpc, KEY_3);
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, KEY_2, VALUE_2), mpc.getCustomAttributesSnapshot(true));
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, KEY_2, VALUE_2), mpc.getCustomAttributesSnapshot(false));
-
-        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2, "default"));
-        mpc.unsetCustomAttribute(KEY_2);
-        assertEquals("default", mpc.getCustomAttribute(KEY_2, "default"));
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1), mpc.getCustomAttributesSnapshot(true));
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1), mpc.getCustomAttributesSnapshot(false));
-
-        mpc.unsetAllCustomAttributes();
-        assertTrue(mpc.getCustomAttributesSnapshot(true).isEmpty());
-
-        testCustomAttributesSnapshotIsUnmodifiable(mpc);
-        mpc.setCustomAttribute(KEY_1, VALUE_1);
-        testCustomAttributesSnapshotIsUnmodifiable(mpc);
-
-        // Test no aliasing
-        Map<Serializable, Object> attrMap1 = mpc.getCustomAttributesSnapshot(false);
-        mpc.setCustomAttribute(KEY_2, VALUE_2);
-        assertNull(attrMap1.get(KEY_2));
-
-        mpc.unsetAllCustomAttributes();
-        mpc.setCustomAttribute(KEY_1, VALUE_1);
-        mpc.setCustomAttributes(ImmutableMap.of(KEY_2, VALUE_2, KEY_3, VALUE_3));
-        assertEquals(
-                ImmutableMap.of(KEY_1, VALUE_1, KEY_2, VALUE_2, KEY_3, VALUE_3),
-                mpc.getCustomAttributesSnapshot(false));
-
-        try {
-            mpc.setCustomAttribute(KEY_1, MISSING_VALUE_MARKER);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), containsString("MISSING_VALUE_MARKER"));
-        }
-        try {
-            mpc.setCustomAttributes(ImmutableMap.of(KEY_1, MISSING_VALUE_MARKER));
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), containsString("MISSING_VALUE_MARKER"));
-        }
-    }
-
-    private void testMissingCustomAttributeAccess(ProcessingConfiguration pc) {
-        testMissingCustomAttributeAccess(pc, "noSuchKey");
-    }
-
-    private void testCustomAttributesSnapshotIsUnmodifiable(ProcessingConfiguration pc) {
-        for (boolean includeInherited : new boolean[] { false,  true }) {
-            Map<Serializable, Object> map = pc.getCustomAttributesSnapshot(includeInherited);
-            try {
-                map.put("aNewKey", 123);
-                fail();
-            } catch (UnsupportedOperationException e) {
-                // Expected
-            }
-        }
-    }
-
-    private void testMissingCustomAttributeAccess(ProcessingConfiguration pc, Serializable key) {
-        try {
-            pc.getCustomAttribute(key);
-            fail();
-        } catch (CustomAttributeNotSetException e) {
-            assertSame(key, e.getKey());
-        }
-
-        assertNull(pc.getCustomAttribute(key, null));
-        assertEquals("default", pc.getCustomAttribute(key, "default"));
-        assertSame(MISSING_VALUE_MARKER,
-                pc.getCustomAttribute(key, MISSING_VALUE_MARKER));
-    }
-
-    @Test
-    public void testTemplateAttrsFromFtlHeaderOnly() throws Exception {
-        Template t = new Template(null, "<#ftl attributes={"
-                + "'" + KEY_1 + "': [ 's', 2, true, {  'a': 'A' } ], "
-                + "'" + KEY_2 + "': 22 "
-                + "}>",
-                new Configuration.Builder(Configuration.VERSION_3_0_0).build());
-
-        assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributesSnapshot(true).keySet());
-        assertEquals(
-                ImmutableList.<Object>of("s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A")),
-                t.getCustomAttribute(KEY_1));
-        assertEquals(BigDecimal.valueOf(22), t.getCustomAttribute(KEY_2));
-
-        testMissingCustomAttributeAccess(t);
-        testCustomAttributesSnapshotIsUnmodifiable(t);
-    }
-
-    @Test
-    public void testTemplateAttrsFromFtlHeaderAndFromTemplateConfiguration() throws Exception {
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setCustomAttribute(KEY_3, VALUE_3);
-        tcb.setCustomAttribute(KEY_4, VALUE_4);
-        Template t = new Template(null, "<#ftl attributes={"
-                + "'" + KEY_1 + "': 'a', "
-                + "'" + KEY_2 + "': 'b', "
-                + "'" + KEY_3 + "': 'c' "
-                + "}>",
-                new Configuration.Builder(Configuration.VERSION_3_0_0).build(),
-                tcb.build());
-
-        assertEquals(ImmutableMap.of(KEY_1, "a", KEY_2, "b", KEY_3, "c", KEY_4, VALUE_4),
-                t.getCustomAttributesSnapshot(true));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertEquals("b", t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3)); // Has overridden TC attribute
-        assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); // Inherited TC attribute
-
-        testMissingCustomAttributeAccess(t);
-        testCustomAttributesSnapshotIsUnmodifiable(t);
-    }
-
-    @Test
-    public void testTemplateAttrsFromTemplateConfigurationOnly() throws Exception {
-        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setCustomAttribute(KEY_3, VALUE_3);
-        tcb.setCustomAttribute(KEY_4, VALUE_4);
-        Template t = new Template(null, "",
-                new Configuration.Builder(Configuration.VERSION_3_0_0).build(),
-                tcb.build());
-
-        assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomAttributesSnapshot(true).keySet());
-        assertEquals(VALUE_3, t.getCustomAttribute(KEY_3));
-        assertEquals(VALUE_4, t.getCustomAttribute(KEY_4));
-
-        testMissingCustomAttributeAccess(t);
-        testCustomAttributesSnapshotIsUnmodifiable(t);
-    }
-
-    @Test
-    public void testTemplateAttrsFromConfigurationOnly() throws Exception {
-        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
-                .customAttribute(KEY_1, VALUE_1)
-                .build();
-        Template t = new Template(null, "", cfg);
-
-        assertEquals(VALUE_1, t.getCustomAttribute(KEY_1));
-        assertEquals("default", t.getCustomAttribute(KEY_2, "default"));
-
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1), t.getCustomAttributesSnapshot(true));
-        assertTrue(t.getCustomAttributesSnapshot(false).isEmpty());
-
-        testMissingCustomAttributeAccess(t);
-        testCustomAttributesSnapshotIsUnmodifiable(t);
-    }
-
-    @Test
-    public void testTemplateAttrsFromTemplateAndConfiguration() throws Exception {
-        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
-                .customAttribute(KEY_1, VALUE_1)
-                .build();
-        Template t = new Template(null, "<#ftl attributes={'k2':'v2'}>", cfg);
-
-        assertEquals(VALUE_1, t.getCustomAttribute(KEY_1));
-        assertEquals("v2", t.getCustomAttribute("k2"));
-
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2"), t.getCustomAttributesSnapshot(true));
-        assertEquals(ImmutableMap.of("k2", "v2"), t.getCustomAttributesSnapshot(false));
-
-        testMissingCustomAttributeAccess(t);
-        testCustomAttributesSnapshotIsUnmodifiable(t);
-    }
-
-    @Test
-    public void testAllLayers() throws Exception {
-        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
-                .customAttribute(KEY_1, VALUE_1)
-                .build();
-        Template t = new Template(null, "<#ftl attributes={'k2':'v2'}>", cfg,
-                new TemplateConfiguration.Builder().customAttribute(KEY_3, VALUE_3).build());
-
-        assertEquals(VALUE_1, t.getCustomAttribute(KEY_1));
-        assertEquals("v2", t.getCustomAttribute("k2"));
-        assertEquals(VALUE_3, t.getCustomAttribute(KEY_3));
-
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2", KEY_3, VALUE_3),
-                t.getCustomAttributesSnapshot(true));
-        assertEquals(ImmutableMap.of("k2", "v2", KEY_3, VALUE_3),
-                t.getCustomAttributesSnapshot(false));
-
-        testMissingCustomAttributeAccess(t);
-        testCustomAttributesSnapshotIsUnmodifiable(t);
-
-        Environment env = t.createProcessingEnvironment(null, null);
-
-        assertEquals(VALUE_1, env.getCustomAttribute(KEY_1));
-        assertEquals("v2", env.getCustomAttribute("k2"));
-        assertEquals(VALUE_3, env.getCustomAttribute(KEY_3));
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2", KEY_3, VALUE_3),
-                env.getCustomAttributesSnapshot(true));
-        assertEquals(Collections.emptyMap(),
-                env.getCustomAttributesSnapshot(false));
-
-        env.setCustomAttribute(KEY_4, VALUE_4);
-        assertEquals(VALUE_1, env.getCustomAttribute(KEY_1));
-        assertEquals("v2", env.getCustomAttribute("k2"));
-        assertEquals(VALUE_3, env.getCustomAttribute(KEY_3));
-        assertEquals(VALUE_4, env.getCustomAttribute(KEY_4));
-        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2", KEY_3, VALUE_3, KEY_4, VALUE_4),
-                env.getCustomAttributesSnapshot(true));
-        assertEquals(ImmutableMap.of(KEY_4, VALUE_4),
-                env.getCustomAttributesSnapshot(false));
-
-        testMissingCustomAttributeAccess(env);
-        testCustomAttributesSnapshotIsUnmodifiable(env);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomSettingTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomSettingTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomSettingTest.java
new file mode 100644
index 0000000..51debd3
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomSettingTest.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import static org.apache.freemarker.core.ProcessingConfiguration.MISSING_VALUE_MARKER;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.*;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+@SuppressWarnings("boxing")
+public class CustomSettingTest {
+    
+    private static final String KEY_1 = "key1";
+    private static final String KEY_2 = "key2";
+    private static final String KEY_3 = "key3";
+    private static final Integer KEY_4 = 4;
+
+    private static final Integer VALUE_1 = 1; // Serializable
+    private static final Object VALUE_2 = new Object();
+    private static final Object VALUE_3 = new Object();
+    private static final Object VALUE_4 = new Object();
+
+    @Test
+    public void testMutableProcessingConfiguration() throws Exception {
+        testMutableProcessingConfiguration(new Configuration.Builder(Configuration.VERSION_3_0_0));
+
+        testMutableProcessingConfiguration(new TemplateConfiguration.Builder());
+
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
+        Environment env = new Template(null, "", cfg).createProcessingEnvironment(null, null);
+        testMutableProcessingConfiguration(env);
+    }
+
+    private void testMutableProcessingConfiguration(MutableProcessingConfiguration<?> mpc) {
+        assertTrue(mpc.getCustomSettingsSnapshot(true).isEmpty());
+        assertTrue(mpc.getCustomSettingsSnapshot(false).isEmpty());
+        testMissingCustomSettingAccess(mpc, KEY_1);
+
+        mpc.setCustomSetting(KEY_1, VALUE_1);
+        mpc.setCustomSetting(KEY_2, VALUE_2);
+
+        assertSame(VALUE_1, mpc.getCustomSetting(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomSetting(KEY_2));
+        testMissingCustomSettingAccess(mpc, KEY_3);
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, KEY_2, VALUE_2), mpc.getCustomSettingsSnapshot(true));
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, KEY_2, VALUE_2), mpc.getCustomSettingsSnapshot(false));
+
+        assertSame(VALUE_2, mpc.getCustomSetting(KEY_2, "default"));
+        mpc.unsetCustomSetting(KEY_2);
+        assertEquals("default", mpc.getCustomSetting(KEY_2, "default"));
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1), mpc.getCustomSettingsSnapshot(true));
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1), mpc.getCustomSettingsSnapshot(false));
+
+        mpc.unsetAllCustomSettings();
+        assertTrue(mpc.getCustomSettingsSnapshot(true).isEmpty());
+
+        testCustomSettingsSnapshotIsUnmodifiable(mpc);
+        mpc.setCustomSetting(KEY_1, VALUE_1);
+        testCustomSettingsSnapshotIsUnmodifiable(mpc);
+
+        // Test no aliasing
+        Map<Serializable, Object> attrMap1 = mpc.getCustomSettingsSnapshot(false);
+        mpc.setCustomSetting(KEY_2, VALUE_2);
+        assertNull(attrMap1.get(KEY_2));
+
+        mpc.unsetAllCustomSettings();
+        mpc.setCustomSetting(KEY_1, VALUE_1);
+        mpc.setCustomSettings(ImmutableMap.of(KEY_2, VALUE_2, KEY_3, VALUE_3));
+        assertEquals(
+                ImmutableMap.of(KEY_1, VALUE_1, KEY_2, VALUE_2, KEY_3, VALUE_3),
+                mpc.getCustomSettingsSnapshot(false));
+
+        try {
+            mpc.setCustomSetting(KEY_1, MISSING_VALUE_MARKER);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), containsString("MISSING_VALUE_MARKER"));
+        }
+        try {
+            mpc.setCustomSettings(ImmutableMap.of(KEY_1, MISSING_VALUE_MARKER));
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), containsString("MISSING_VALUE_MARKER"));
+        }
+    }
+
+    private void testMissingCustomSettingAccess(ProcessingConfiguration pc) {
+        testMissingCustomSettingAccess(pc, "noSuchKey");
+    }
+
+    private void testCustomSettingsSnapshotIsUnmodifiable(ProcessingConfiguration pc) {
+        for (boolean includeInherited : new boolean[] { false,  true }) {
+            Map<Serializable, Object> map = pc.getCustomSettingsSnapshot(includeInherited);
+            try {
+                map.put("aNewKey", 123);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // Expected
+            }
+        }
+    }
+
+    private void testMissingCustomSettingAccess(ProcessingConfiguration pc, Serializable key) {
+        try {
+            pc.getCustomSetting(key);
+            fail();
+        } catch (CustomSettingNotSetException e) {
+            assertSame(key, e.getKey());
+        }
+
+        assertNull(pc.getCustomSetting(key, null));
+        assertEquals("default", pc.getCustomSetting(key, "default"));
+        assertSame(MISSING_VALUE_MARKER,
+                pc.getCustomSetting(key, MISSING_VALUE_MARKER));
+    }
+
+    @Test
+    public void testTemplateAttrsFromFtlHeaderOnly() throws Exception {
+        Template t = new Template(null, "<#ftl customSettings={"
+                + "'" + KEY_1 + "': [ 's', 2, true, {  'a': 'A' } ], "
+                + "'" + KEY_2 + "': 22 "
+                + "}>",
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build());
+
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomSettingsSnapshot(true).keySet());
+        assertEquals(
+                ImmutableList.<Object>of("s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A")),
+                t.getCustomSetting(KEY_1));
+        assertEquals(BigDecimal.valueOf(22), t.getCustomSetting(KEY_2));
+
+        testMissingCustomSettingAccess(t);
+        testCustomSettingsSnapshotIsUnmodifiable(t);
+    }
+
+    @Test
+    public void testTemplateAttrsFromFtlHeaderAndFromTemplateConfiguration() throws Exception {
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomSetting(KEY_3, VALUE_3);
+        tcb.setCustomSetting(KEY_4, VALUE_4);
+        Template t = new Template(null, "<#ftl customSettings={"
+                + "'" + KEY_1 + "': 'a', "
+                + "'" + KEY_2 + "': 'b', "
+                + "'" + KEY_3 + "': 'c' "
+                + "}>",
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build(),
+                tcb.build());
+
+        assertEquals(ImmutableMap.of(KEY_1, "a", KEY_2, "b", KEY_3, "c", KEY_4, VALUE_4),
+                t.getCustomSettingsSnapshot(true));
+        assertEquals("a", t.getCustomSetting(KEY_1));
+        assertEquals("b", t.getCustomSetting(KEY_2));
+        assertEquals("c", t.getCustomSetting(KEY_3)); // Has overridden TC attribute
+        assertEquals(VALUE_4, t.getCustomSetting(KEY_4)); // Inherited TC attribute
+
+        testMissingCustomSettingAccess(t);
+        testCustomSettingsSnapshotIsUnmodifiable(t);
+    }
+
+    @Test
+    public void testTemplateAttrsFromTemplateConfigurationOnly() throws Exception {
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomSetting(KEY_3, VALUE_3);
+        tcb.setCustomSetting(KEY_4, VALUE_4);
+        Template t = new Template(null, "",
+                new Configuration.Builder(Configuration.VERSION_3_0_0).build(),
+                tcb.build());
+
+        assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomSettingsSnapshot(true).keySet());
+        assertEquals(VALUE_3, t.getCustomSetting(KEY_3));
+        assertEquals(VALUE_4, t.getCustomSetting(KEY_4));
+
+        testMissingCustomSettingAccess(t);
+        testCustomSettingsSnapshotIsUnmodifiable(t);
+    }
+
+    @Test
+    public void testTemplateAttrsFromConfigurationOnly() throws Exception {
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .customSetting(KEY_1, VALUE_1)
+                .build();
+        Template t = new Template(null, "", cfg);
+
+        assertEquals(VALUE_1, t.getCustomSetting(KEY_1));
+        assertEquals("default", t.getCustomSetting(KEY_2, "default"));
+
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1), t.getCustomSettingsSnapshot(true));
+        assertTrue(t.getCustomSettingsSnapshot(false).isEmpty());
+
+        testMissingCustomSettingAccess(t);
+        testCustomSettingsSnapshotIsUnmodifiable(t);
+    }
+
+    @Test
+    public void testTemplateAttrsFromTemplateAndConfiguration() throws Exception {
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .customSetting(KEY_1, VALUE_1)
+                .build();
+        Template t = new Template(null, "<#ftl customSettings={'k2':'v2'}>", cfg);
+
+        assertEquals(VALUE_1, t.getCustomSetting(KEY_1));
+        assertEquals("v2", t.getCustomSetting("k2"));
+
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2"), t.getCustomSettingsSnapshot(true));
+        assertEquals(ImmutableMap.of("k2", "v2"), t.getCustomSettingsSnapshot(false));
+
+        testMissingCustomSettingAccess(t);
+        testCustomSettingsSnapshotIsUnmodifiable(t);
+    }
+
+    @Test
+    public void testAllLayers() throws Exception {
+        Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .customSetting(KEY_1, VALUE_1)
+                .build();
+        Template t = new Template(null, "<#ftl customSettings={'k2':'v2'}>", cfg,
+                new TemplateConfiguration.Builder().customSetting(KEY_3, VALUE_3).build());
+
+        assertEquals(VALUE_1, t.getCustomSetting(KEY_1));
+        assertEquals("v2", t.getCustomSetting("k2"));
+        assertEquals(VALUE_3, t.getCustomSetting(KEY_3));
+
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2", KEY_3, VALUE_3),
+                t.getCustomSettingsSnapshot(true));
+        assertEquals(ImmutableMap.of("k2", "v2", KEY_3, VALUE_3),
+                t.getCustomSettingsSnapshot(false));
+
+        testMissingCustomSettingAccess(t);
+        testCustomSettingsSnapshotIsUnmodifiable(t);
+
+        Environment env = t.createProcessingEnvironment(null, null);
+
+        assertEquals(VALUE_1, env.getCustomSetting(KEY_1));
+        assertEquals("v2", env.getCustomSetting("k2"));
+        assertEquals(VALUE_3, env.getCustomSetting(KEY_3));
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2", KEY_3, VALUE_3),
+                env.getCustomSettingsSnapshot(true));
+        assertEquals(Collections.emptyMap(),
+                env.getCustomSettingsSnapshot(false));
+
+        env.setCustomSetting(KEY_4, VALUE_4);
+        assertEquals(VALUE_1, env.getCustomSetting(KEY_1));
+        assertEquals("v2", env.getCustomSetting("k2"));
+        assertEquals(VALUE_3, env.getCustomSetting(KEY_3));
+        assertEquals(VALUE_4, env.getCustomSetting(KEY_4));
+        assertEquals(ImmutableMap.of(KEY_1, VALUE_1, "k2", "v2", KEY_3, VALUE_3, KEY_4, VALUE_4),
+                env.getCustomSettingsSnapshot(true));
+        assertEquals(ImmutableMap.of(KEY_4, VALUE_4),
+                env.getCustomSettingsSnapshot(false));
+
+        testMissingCustomSettingAccess(env);
+        testCustomSettingsSnapshotIsUnmodifiable(env);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
index c8b1f7d..05d44dc 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
@@ -113,4 +113,11 @@ public class ParsingErrorMessagesTest {
         //System.out.println(e);
     }
 
+    @Test
+    public void testUnknownHeaderParameter() {
+        assertErrorContains("<#ftl foo=1>", "Unknown", "foo");
+        assertErrorContains("<#ftl attributes={}>", "Unknown", "attributes", "custom_settings");
+        assertErrorContains("<#ftl outputFormat='HTML' attributes={}>", "Unknown", "attributes", "customSettings");
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/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 92b5bfb..eb5e7da 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
@@ -237,7 +237,7 @@ public class TemplateConfigurationTest {
         IGNORED_PROP_NAMES.add("strictBeanModels");
         IGNORED_PROP_NAMES.add("parentConfiguration");
         IGNORED_PROP_NAMES.add("settings");
-        IGNORED_PROP_NAMES.add("customAttributes");
+        IGNORED_PROP_NAMES.add("customSettings");
     }
 
     private static final Set<String> CONFIGURABLE_PROP_NAMES;
@@ -395,83 +395,83 @@ public class TemplateConfigurationTest {
     }
     
     @Test
-    public void testMergeCustomAttributes() throws Exception {
+    public void testMergeCustomSettings() throws Exception {
         TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder();
-        tc1.setCustomAttribute("k1", "v1");
-        tc1.setCustomAttribute("k2", "v1");
-        tc1.setCustomAttribute("k3", "v1");
-        tc1.setCustomAttribute(CA1, "V1");
-        tc1.setCustomAttribute(CA2, "V1");
-        tc1.setCustomAttribute(CA3, "V1");
+        tc1.setCustomSetting("k1", "v1");
+        tc1.setCustomSetting("k2", "v1");
+        tc1.setCustomSetting("k3", "v1");
+        tc1.setCustomSetting(CA1, "V1");
+        tc1.setCustomSetting(CA2, "V1");
+        tc1.setCustomSetting(CA3, "V1");
 
         TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
-        tcb2.setCustomAttribute("k1", "v2");
-        tcb2.setCustomAttribute("k2", "v2");
-        tcb2.setCustomAttribute(CA1, "V2");
-        tcb2.setCustomAttribute(CA2, "V2");
+        tcb2.setCustomSetting("k1", "v2");
+        tcb2.setCustomSetting("k2", "v2");
+        tcb2.setCustomSetting(CA1, "V2");
+        tcb2.setCustomSetting(CA2, "V2");
 
         TemplateConfiguration.Builder tcb3 = new TemplateConfiguration.Builder();
-        tcb3.setCustomAttribute("k1", "v3");
-        tcb3.setCustomAttribute(CA1, "V3");
+        tcb3.setCustomSetting("k1", "v3");
+        tcb3.setCustomSetting(CA1, "V3");
 
         tc1.merge(tcb2.build());
         tc1.merge(tcb3.build());
 
-        assertEquals("v3", tc1.getCustomAttribute("k1"));
-        assertEquals("v2", tc1.getCustomAttribute("k2"));
-        assertEquals("v1", tc1.getCustomAttribute("k3"));
-        assertEquals("V3", tc1.getCustomAttribute(CA1));
-        assertEquals("V2", tc1.getCustomAttribute(CA2));
-        assertEquals("V1", tc1.getCustomAttribute(CA3));
+        assertEquals("v3", tc1.getCustomSetting("k1"));
+        assertEquals("v2", tc1.getCustomSetting("k2"));
+        assertEquals("v1", tc1.getCustomSetting("k3"));
+        assertEquals("V3", tc1.getCustomSetting(CA1));
+        assertEquals("V2", tc1.getCustomSetting(CA2));
+        assertEquals("V1", tc1.getCustomSetting(CA3));
     }
 
     @Test
-    public void testMergeNullCustomAttributes() throws Exception {
+    public void testMergeNullCustomSettings() throws Exception {
         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"));
-        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomAttribute("k3", MISSING_VALUE_MARKER));
-        assertEquals("V1", tcb1.getCustomAttribute(CA1));
-        assertEquals("V1", tcb1.getCustomAttribute(CA2));
-        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomAttribute(CA3, MISSING_VALUE_MARKER));
+        tcb1.setCustomSetting("k1", "v1");
+        tcb1.setCustomSetting("k2", "v1");
+        tcb1.setCustomSetting(CA1, "V1");
+        tcb1.setCustomSetting(CA2,"V1");
+
+        assertEquals("v1", tcb1.getCustomSetting("k1"));
+        assertEquals("v1", tcb1.getCustomSetting("k2"));
+        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomSetting("k3", MISSING_VALUE_MARKER));
+        assertEquals("V1", tcb1.getCustomSetting(CA1));
+        assertEquals("V1", tcb1.getCustomSetting(CA2));
+        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomSetting(CA3, MISSING_VALUE_MARKER));
 
         TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder();
-        tcb2.setCustomAttribute("k1", "v2");
-        tcb2.setCustomAttribute("k2", null);
-        tcb2.setCustomAttribute(CA1, "V2");
-        tcb2.setCustomAttribute(CA2, null);
+        tcb2.setCustomSetting("k1", "v2");
+        tcb2.setCustomSetting("k2", null);
+        tcb2.setCustomSetting(CA1, "V2");
+        tcb2.setCustomSetting(CA2, null);
 
         TemplateConfiguration.Builder tcb3 = new TemplateConfiguration.Builder();
-        tcb3.setCustomAttribute("k1", null);
-        tcb2.setCustomAttribute(CA1, null);
+        tcb3.setCustomSetting("k1", null);
+        tcb2.setCustomSetting(CA1, null);
 
         tcb1.merge(tcb2.build());
         tcb1.merge(tcb3.build());
 
-        assertNull(tcb1.getCustomAttribute("k1"));
-        assertNull(tcb1.getCustomAttribute("k2"));
-        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomAttribute("k3", MISSING_VALUE_MARKER));
-        assertNull(tcb1.getCustomAttribute(CA1));
-        assertNull(tcb1.getCustomAttribute(CA2));
-        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomAttribute(CA3, MISSING_VALUE_MARKER));
+        assertNull(tcb1.getCustomSetting("k1"));
+        assertNull(tcb1.getCustomSetting("k2"));
+        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomSetting("k3", MISSING_VALUE_MARKER));
+        assertNull(tcb1.getCustomSetting(CA1));
+        assertNull(tcb1.getCustomSetting(CA2));
+        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomSetting(CA3, MISSING_VALUE_MARKER));
 
         TemplateConfiguration.Builder tcb4 = new TemplateConfiguration.Builder();
-        tcb4.setCustomAttribute("k1", "v4");
-        tcb4.setCustomAttribute(CA1, "V4");
+        tcb4.setCustomSetting("k1", "v4");
+        tcb4.setCustomSetting(CA1, "V4");
 
         tcb1.merge(tcb4.build());
 
-        assertEquals("v4", tcb1.getCustomAttribute("k1"));
-        assertNull(tcb1.getCustomAttribute("k2"));
-        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomAttribute("k3", MISSING_VALUE_MARKER));
-        assertEquals("V4", tcb1.getCustomAttribute(CA1));
-        assertNull(tcb1.getCustomAttribute(CA2));
-        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomAttribute(CA3, MISSING_VALUE_MARKER));
+        assertEquals("v4", tcb1.getCustomSetting("k1"));
+        assertNull(tcb1.getCustomSetting("k2"));
+        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomSetting("k3", MISSING_VALUE_MARKER));
+        assertEquals("V4", tcb1.getCustomSetting(CA1));
+        assertNull(tcb1.getCustomSetting(CA2));
+        assertEquals(MISSING_VALUE_MARKER, tcb1.getCustomSetting(CA3, MISSING_VALUE_MARKER));
     }
 
     @Test
@@ -497,33 +497,33 @@ public class TemplateConfigurationTest {
     }
     
     @Test
-    public void testConfigureCustomAttributes() throws Exception {
+    public void testConfigureCustomSettings() throws Exception {
         Configuration cfg = new TestConfigurationBuilder()
-                .customAttribute("k1", "c")
-                .customAttribute("k2", "c")
-                .customAttribute("k3", "c")
-                .customAttribute("k8", "c")
+                .customSetting("k1", "c")
+                .customSetting("k2", "c")
+                .customSetting("k3", "c")
+                .customSetting("k8", "c")
                 .build();
 
         TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setCustomAttribute("k2", "tc");
-        tcb.setCustomAttribute("k3", null);
-        tcb.setCustomAttribute("k4", "tc");
-        tcb.setCustomAttribute("k5", "tc");
-        tcb.setCustomAttribute("k6", "tc");
+        tcb.setCustomSetting("k2", "tc");
+        tcb.setCustomSetting("k3", null);
+        tcb.setCustomSetting("k4", "tc");
+        tcb.setCustomSetting("k5", "tc");
+        tcb.setCustomSetting("k6", "tc");
 
         TemplateConfiguration tc = tcb.build();
-        Template t = new Template(null, "<#ftl attributes={'k5':'t', 'k7':'t', 'k8':'t'}>", cfg, tc);
+        Template t = new Template(null, "<#ftl customSettings={'k5':'t', 'k7':'t', 'k8':'t'}>", cfg, tc);
 
-        assertEquals("c", t.getCustomAttribute("k1"));
-        assertEquals("tc", t.getCustomAttribute("k2"));
-        assertNull(t.getCustomAttribute("k3"));
-        assertEquals("tc", t.getCustomAttribute("k4"));
-        assertEquals("t", t.getCustomAttribute("k5"));
+        assertEquals("c", t.getCustomSetting("k1"));
+        assertEquals("tc", t.getCustomSetting("k2"));
+        assertNull(t.getCustomSetting("k3"));
+        assertEquals("tc", t.getCustomSetting("k4"));
+        assertEquals("t", t.getCustomSetting("k5"));
         // TODO [FM3] when { ... 'k6': null ... } works in FTL, put this back.
-        // assertNull(t.getCustomAttribute("k6"));
-        assertEquals("t", t.getCustomAttribute("k7"));
-        assertEquals("t", t.getCustomAttribute("k8"));
+        // assertNull(t.getCustomSetting("k6"));
+        assertEquals("t", t.getCustomSetting("k7"));
+        assertEquals("t", t.getCustomSetting("k8"));
     }
     
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
index f242b66..5e42e3b 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java
@@ -42,8 +42,8 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
 
     private static final String TEXT_WITH_ACCENTS = "pr\u00F3ba";
 
-    private static final Serializable CUST_ATT_1 = Integer.valueOf(111);
-    private static final Serializable CUST_ATT_2 = Integer.valueOf(222);
+    private static final Serializable CUST_SETTING_1 = Integer.valueOf(111);
+    private static final Serializable CUST_SETTING_2 = Integer.valueOf(222);
 
     private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2");
 
@@ -166,8 +166,8 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
     }
     
     @Test
-    public void testCustomAttributes() throws Exception {
-        String commonFTL = "<#ftl attributes={ 'a3': 'a3temp' }>";
+    public void testCustomSettings() throws Exception {
+        String commonFTL = "<#ftl customSettings={ 'a3': 'a3temp' }>";
         StringTemplateLoader tl = new StringTemplateLoader();
         tl.putTemplate("(tc1)", commonFTL);
         tl.putTemplate("(tc1)noHeader", "");
@@ -180,52 +180,52 @@ public class TemplateConfigurationWithDefaultTemplateResolverTest {
                                 new ConditionalTemplateConfigurationFactory(
                                         new FileNameGlobMatcher("*(tc1)*"),
                                         new TemplateConfiguration.Builder()
-                                                .customAttribute("a1", "a1tc1")
-                                                .customAttribute("a2", "a2tc1")
-                                                .customAttribute("a3", "a3tc1")
-                                                .customAttribute(CUST_ATT_1, "ca1tc1")
-                                                .customAttribute(CUST_ATT_2, "ca2tc1")
+                                                .customSetting("a1", "a1tc1")
+                                                .customSetting("a2", "a2tc1")
+                                                .customSetting("a3", "a3tc1")
+                                                .customSetting(CUST_SETTING_1, "ca1tc1")
+                                                .customSetting(CUST_SETTING_2, "ca2tc1")
                                                 .build()),
                                 new ConditionalTemplateConfigurationFactory(
                                         new FileNameGlobMatcher("*(tc2)*"),
                                         new TemplateConfiguration.Builder()
-                                                .customAttribute("a1", "a1tc2")
-                                                .customAttribute(CUST_ATT_1, "ca1tc2")
+                                                .customSetting("a1", "a1tc2")
+                                                .customSetting(CUST_SETTING_1, "ca1tc2")
                                                 .build())))
                 .templateLoader(tl)
                 .build();
 
         {
             Template t = cfg.getTemplate("(tc1)");
-            assertEquals("a1tc1", t.getCustomAttribute("a1"));
-            assertEquals("a2tc1", t.getCustomAttribute("a2"));
-            assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
+            assertEquals("a1tc1", t.getCustomSetting("a1"));
+            assertEquals("a2tc1", t.getCustomSetting("a2"));
+            assertEquals("a3temp", t.getCustomSetting("a3"));
+            assertEquals("ca1tc1", t.getCustomSetting(CUST_SETTING_1));
+            assertEquals("ca2tc1", t.getCustomSetting(CUST_SETTING_2));
         }
         {
             Template t = cfg.getTemplate("(tc1)noHeader");
-            assertEquals("a1tc1", t.getCustomAttribute("a1"));
-            assertEquals("a2tc1", t.getCustomAttribute("a2"));
-            assertEquals("a3tc1", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
+            assertEquals("a1tc1", t.getCustomSetting("a1"));
+            assertEquals("a2tc1", t.getCustomSetting("a2"));
+            assertEquals("a3tc1", t.getCustomSetting("a3"));
+            assertEquals("ca1tc1", t.getCustomSetting(CUST_SETTING_1));
+            assertEquals("ca2tc1", t.getCustomSetting(CUST_SETTING_2));
         }
         {
             Template t = cfg.getTemplate("(tc2)");
-            assertEquals("a1tc2", t.getCustomAttribute("a1"));
-            assertEquals(MISSING_VALUE_MARKER, t.getCustomAttribute("a2", MISSING_VALUE_MARKER));
-            assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals(MISSING_VALUE_MARKER, t.getCustomAttribute(CUST_ATT_2, MISSING_VALUE_MARKER));
+            assertEquals("a1tc2", t.getCustomSetting("a1"));
+            assertEquals(MISSING_VALUE_MARKER, t.getCustomSetting("a2", MISSING_VALUE_MARKER));
+            assertEquals("a3temp", t.getCustomSetting("a3"));
+            assertEquals("ca1tc2", t.getCustomSetting(CUST_SETTING_1));
+            assertEquals(MISSING_VALUE_MARKER, t.getCustomSetting(CUST_SETTING_2, MISSING_VALUE_MARKER));
         }
         {
             Template t = cfg.getTemplate("(tc1)(tc2)");
-            assertEquals("a1tc2", t.getCustomAttribute("a1"));
-            assertEquals("a2tc1", t.getCustomAttribute("a2"));
-            assertEquals("a3temp", t.getCustomAttribute("a3"));
-            assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1));
-            assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2));
+            assertEquals("a1tc2", t.getCustomSetting("a1"));
+            assertEquals("a2tc1", t.getCustomSetting("a2"));
+            assertEquals("a3temp", t.getCustomSetting("a3"));
+            assertEquals("ca1tc2", t.getCustomSetting(CUST_SETTING_1));
+            assertEquals("ca2tc1", t.getCustomSetting(CUST_SETTING_2));
         }
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core-test/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
index 78ccf81..547dedf 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryTest.java
@@ -154,8 +154,8 @@ public class TemplateConfigurationFactoryTest {
     @SuppressWarnings("boxing")
     private TemplateConfiguration newTemplateConfiguration(int id) {
         TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
-        tcb.setCustomAttribute("id", id);
-        tcb.setCustomAttribute("contains" + id, true);
+        tcb.setCustomSetting("id", id);
+        tcb.setCustomSetting("contains" + id, true);
         return tcb.build();
     }
 
@@ -167,10 +167,10 @@ public class TemplateConfigurationFactoryTest {
     private void assertApplicable(TemplateConfigurationFactory tcf, String sourceName, TemplateConfiguration... expectedTCs)
             throws IOException, TemplateConfigurationFactoryException {
         TemplateConfiguration mergedTC = tcf.get(sourceName, DummyTemplateLoadingSource.INSTANCE);
-        List<Serializable> mergedTCAttNames = new ArrayList<>(mergedTC.getCustomAttributesSnapshot(false).keySet());
+        List<Serializable> mergedTCAttNames = new ArrayList<>(mergedTC.getCustomSettingsSnapshot(false).keySet());
 
         for (TemplateConfiguration expectedTC : expectedTCs) {
-            Integer tcId = (Integer) expectedTC.getCustomAttribute("id");
+            Integer tcId = (Integer) expectedTC.getCustomSetting("id");
             if (tcId == null) {
                 fail("TemplateConfiguration-s must be created with newTemplateConfiguration(id) in this test");
             }
@@ -185,12 +185,12 @@ public class TemplateConfigurationFactoryTest {
             }
         }
         
-        assertEquals(expectedTCs[expectedTCs.length - 1].getCustomAttribute("id"), mergedTC.getCustomAttribute("id"));
+        assertEquals(expectedTCs[expectedTCs.length - 1].getCustomSetting("id"), mergedTC.getCustomSetting("id"));
     }
 
     private boolean containsCustomAttr(Serializable attKey, TemplateConfiguration... expectedTCs) {
         for (TemplateConfiguration expectedTC : expectedTCs) {
-            if (expectedTC.getCustomAttribute(attKey, ProcessingConfiguration.MISSING_VALUE_MARKER)
+            if (expectedTC.getCustomSetting(attKey, ProcessingConfiguration.MISSING_VALUE_MARKER)
                     != ProcessingConfiguration.MISSING_VALUE_MARKER) {
                 return true;
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/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 6691053..4ecb354 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
@@ -282,7 +282,7 @@ public final class Configuration
     private final List<String> autoIncludes;
     private final Boolean lazyImports;
     private final Boolean lazyAutoImports;
-    private final Map<Serializable, Object> customAttributes;
+    private final Map<Serializable, Object> customSettings;
 
     // CustomStateScope:
 
@@ -437,7 +437,7 @@ public final class Configuration
         autoIncludes = builder.getAutoIncludes();
         lazyImports = builder.getLazyImports();
         lazyAutoImports = builder.getLazyAutoImports();
-        customAttributes = builder.getCustomAttributesSnapshot(false);
+        customSettings = builder.getCustomSettingsSnapshot(false);
     }
 
     private <SelfT extends ExtendableBuilder<SelfT>> void wrapAndPutSharedVariables(
@@ -1180,8 +1180,8 @@ public final class Configuration
      * Because {@link Configuration} has on parent, the {@code includeInherited} parameter is ignored.
      */
     @Override
-    public Map<Serializable, Object> getCustomAttributesSnapshot(boolean includeInherited) {
-        return customAttributes;
+    public Map<Serializable, Object> getCustomSettingsSnapshot(boolean includeInherited) {
+        return customSettings;
     }
 
     /**
@@ -1189,33 +1189,33 @@ public final class Configuration
      * <p>
      * Unlike the other isXxxSet methods of {@link Configuration}, this can return {@code false}, as at least the
      * builders in FreeMarker Core can't provide defaults for custom attributes. Note that since
-     * {@link #getCustomAttribute(Serializable)} just returns {@code null} for unset custom attributes, it's usually not a
+     * {@link #getCustomSetting(Serializable)} just returns {@code null} for unset custom attributes, it's usually not a
      * problem.
      */
     @Override
-    public boolean isCustomAttributeSet(Serializable key) {
-        return customAttributes.containsKey(key);
+    public boolean isCustomSettingSet(Serializable key) {
+        return customSettings.containsKey(key);
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key) {
-        return getCustomAttribute(key, null, false);
+    public Object getCustomSetting(Serializable key) {
+        return getCustomSetting(key, null, false);
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key, Object defaultValue) {
-        return getCustomAttribute(key, defaultValue, true);
+    public Object getCustomSetting(Serializable key, Object defaultValue) {
+        return getCustomSetting(key, defaultValue, true);
     }
 
-    private Object getCustomAttribute(Serializable key, Object defaultValue, boolean useDefaultValue) {
-        Object value = customAttributes.get(key);
-        if (value != null || customAttributes.containsKey(key)) {
+    private Object getCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
+        Object value = customSettings.get(key);
+        if (value != null || customSettings.containsKey(key)) {
             return value;
         }
         if (useDefaultValue) {
             return defaultValue;
         }
-        throw new CustomAttributeNotSetException(key);
+        throw new CustomSettingNotSetException(key);
     }
 
     @Override
@@ -2626,15 +2626,15 @@ public final class Configuration
         }
 
         @Override
-        protected Object getDefaultCustomAttribute(Serializable key, Object defaultValue, boolean useDefaultValue) {
+        protected Object getDefaultCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
             if (useDefaultValue) {
                 return defaultValue;
             }
-            throw new CustomAttributeNotSetException(key);
+            throw new CustomSettingNotSetException(key);
         }
 
         @Override
-        protected void collectDefaultCustomAttributesSnapshot(Map<Serializable, Object> target) {
+        protected void collectDefaultCustomSettingsSnapshot(Map<Serializable, Object> target) {
             // Doesn't inherit anything
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core/src/main/java/org/apache/freemarker/core/CustomAttributeNotSetException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CustomAttributeNotSetException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomAttributeNotSetException.java
deleted file mode 100644
index 891f928..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/CustomAttributeNotSetException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import java.io.Serializable;
-
-import org.apache.freemarker.core.util._StringUtil;
-
-/**
- * Thrown by {@link ProcessingConfiguration#getCustomAttribute(Serializable)} if the custom attribute is not set.
- */
-public class CustomAttributeNotSetException extends SettingValueNotSetException {
-
-    private final Serializable key;
-
-    /**
-     * @param key {@link ProcessingConfiguration#getCustomAttribute(Serializable)}
-     */
-    public CustomAttributeNotSetException(Serializable key) {
-        super("customAttributes[" + key instanceof String ? _StringUtil.jQuote(key) : _StringUtil.tryToString(key) +
-                        "]", false);
-        this.key = key;
-    }
-
-    /**
-     * The argument to {@link ProcessingConfiguration#getCustomAttribute(Serializable)}.
-     */
-    public Serializable getKey() {
-        return key;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core/src/main/java/org/apache/freemarker/core/CustomSettingNotSetException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CustomSettingNotSetException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomSettingNotSetException.java
new file mode 100644
index 0000000..180a0fb
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CustomSettingNotSetException.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.Serializable;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Thrown by {@link ProcessingConfiguration#getCustomSetting(Serializable)} if the custom attribute is not set.
+ */
+public class CustomSettingNotSetException extends SettingValueNotSetException {
+
+    private final Serializable key;
+
+    /**
+     * @param key {@link ProcessingConfiguration#getCustomSetting(Serializable)}
+     */
+    public CustomSettingNotSetException(Serializable key) {
+        super("custom[" + key instanceof String ? _StringUtil.jQuote(key)
+                : key.getClass().getName() + " " + _StringUtil.tryToString(key) + "]",
+                false);
+        this.key = key;
+    }
+
+    /**
+     * The argument to {@link ProcessingConfiguration#getCustomSetting(Serializable)}.
+     */
+    public Serializable getKey() {
+        return key;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
index fa2d696..869b72d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
@@ -1092,14 +1092,14 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     @Override
-    protected Object getDefaultCustomAttribute(Serializable key, Object defaultValue, boolean useDefaultValue) {
-        return useDefaultValue ? getMainTemplate().getCustomAttribute(key, defaultValue)
-                : getMainTemplate().getCustomAttribute(key);
+    protected Object getDefaultCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
+        return useDefaultValue ? getMainTemplate().getCustomSetting(key, defaultValue)
+                : getMainTemplate().getCustomSetting(key);
     }
 
     @Override
-    protected void collectDefaultCustomAttributesSnapshot(Map<Serializable, Object> target) {
-        target.putAll(getMainTemplate().getCustomAttributesSnapshot(true));
+    protected void collectDefaultCustomSettingsSnapshot(Map<Serializable, Object> target) {
+        target.putAll(getMainTemplate().getCustomSettingsSnapshot(true));
     }
 
     /*

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/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 25d6c62..c87a9be 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
@@ -334,9 +334,9 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     private Boolean lazyImports;
     private Boolean lazyAutoImports;
     private boolean lazyAutoImportsSet;
-    private Map<Serializable, Object> customAttributes = Collections.emptyMap();
-    /** If {@code false}, we must use copy-on-write behavior for {@link #customAttributes}. */
-    private boolean customAttributesModifiable;
+    private Map<Serializable, Object> customSettings = Collections.emptyMap();
+    /** If {@code false}, we must use copy-on-write behavior for {@link #customSettings}. */
+    private boolean customSettingsModifiable;
 
     /**
      * Creates a new instance. Normally you do not need to use this constructor,
@@ -2126,165 +2126,165 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
     }
 
     /**
-     * Setter pair of {@link #getCustomAttribute(Serializable)}.
+     * Setter pair of {@link #getCustomSetting(Serializable)}.
      *
      * @param key
      *         The identifier of the the custom attribute; not {@code null}. Usually an enum or a {@link String}. Must
      *         be usable as {@link HashMap} key.
      * @param value
      *         The value of the custom attribute. {@code null} is a legal attribute value. Thus, setting the value to
-     *         {@code null} doesn't unset (remove) the attribute; use {@link #unsetCustomAttribute(Serializable)} for
+     *         {@code null} doesn't unset (remove) the attribute; use {@link #unsetCustomSetting(Serializable)} for
      *         that. Also, {@link #MISSING_VALUE_MARKER} is not an allowed value.
      *         The content of the object shouldn't be changed after it was added as an attribute (ideally, it should
      *         be a true immutable object); if you need to change the content, certainly you should use the
      *         {@link CustomStateScope} API.
      */
-    public void setCustomAttribute(Serializable key, Object value) {
+    public void setCustomSetting(Serializable key, Object value) {
         _NullArgumentException.check("key", key);
         if (value == MISSING_VALUE_MARKER) {
-            throw new IllegalArgumentException("MISSING_VALUE_MARKER can't be used as attribute value");
+            throw new IllegalArgumentException("MISSING_VALUE_MARKER can't be used as custom setting value");
         }
-        ensureCustomAttributesModifiable();
-        customAttributes.put(key, value);
+        ensureCustomSettingsModifiable();
+        customSettings.put(key, value);
     }
 
     /**
-     * Fluent API equivalent of {@link #setCustomAttribute(Serializable, Object)}
+     * Fluent API equivalent of {@link #setCustomSetting(Serializable, Object)}
      */
-    public SelfT customAttribute(Serializable key, Object value) {
-        setCustomAttribute(key, value);
+    public SelfT customSetting(Serializable key, Object value) {
+        setCustomSetting(key, value);
         return self();
     }
 
     @Override
-    public boolean isCustomAttributeSet(Serializable key) {
-        return customAttributes.containsKey(key);
+    public boolean isCustomSettingSet(Serializable key) {
+        return customSettings.containsKey(key);
     }
 
     /**
      * Unset the custom attribute for this {@link ProcessingConfiguration} (but not from the parent
      * {@link ProcessingConfiguration}, from where it will be possibly inherited after this), as if
-     * {@link #setCustomAttribute(Serializable, Object)} was never called for it on this
+     * {@link #setCustomSetting(Serializable, Object)} was never called for it on this
      * {@link ProcessingConfiguration}. Note that this is different than setting the custom attribute value to {@code
-     * null}, as then {@link #getCustomAttribute(Serializable)} will just return that {@code null}, and won't look for the
+     * null}, as then {@link #getCustomSetting(Serializable)} will just return that {@code null}, and won't look for the
      * attribute in the parent {@link ProcessingConfiguration}.
      *
-     * @param key As in {@link #getCustomAttribute(Serializable)}
-     */
-    public void unsetCustomAttribute(Serializable key) {
-        if (customAttributesModifiable) {
-            customAttributes.remove(key);
-        } else if (customAttributes.containsKey(key)) {
-            ensureCustomAttributesModifiable();
-            customAttributes.remove(key);
+     * @param key As in {@link #getCustomSetting(Serializable)}
+     */
+    public void unsetCustomSetting(Serializable key) {
+        if (customSettingsModifiable) {
+            customSettings.remove(key);
+        } else if (customSettings.containsKey(key)) {
+            ensureCustomSettingsModifiable();
+            customSettings.remove(key);
         }
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key) throws CustomAttributeNotSetException {
-        return getCustomAttribute(key, null, false);
+    public Object getCustomSetting(Serializable key) throws CustomSettingNotSetException {
+        return getCustomSetting(key, null, false);
     }
 
     @Override
-    public Object getCustomAttribute(Serializable key, Object defaultValue) {
-        return getCustomAttribute(key, defaultValue, true);
+    public Object getCustomSetting(Serializable key, Object defaultValue) {
+        return getCustomSetting(key, defaultValue, true);
     }
 
-    private Object getCustomAttribute(Serializable key, Object defaultValue, boolean useDefaultValue) {
-        Object value = customAttributes.get(key);
-        if (value != null || customAttributes.containsKey(key)) {
+    private Object getCustomSetting(Serializable key, Object defaultValue, boolean useDefaultValue) {
+        Object value = customSettings.get(key);
+        if (value != null || customSettings.containsKey(key)) {
             return value;
         }
-        return getDefaultCustomAttribute(key, defaultValue, useDefaultValue);
+        return getDefaultCustomSetting(key, defaultValue, useDefaultValue);
     }
 
     @Override
-    public Map<Serializable, Object> getCustomAttributesSnapshot(boolean includeInherited) {
+    public Map<Serializable, Object> getCustomSettingsSnapshot(boolean includeInherited) {
         if (includeInherited) {
             LinkedHashMap<Serializable, Object> result = new LinkedHashMap<>();
-            collectDefaultCustomAttributesSnapshot(result);
+            collectDefaultCustomSettingsSnapshot(result);
             if (!result.isEmpty()) {
-                if (customAttributes != null) {
-                    result.putAll(customAttributes);
+                if (customSettings != null) {
+                    result.putAll(customSettings);
                 }
                 return Collections.unmodifiableMap(result);
             }
         }
 
         // When there's no need for inheritance:
-        customAttributesModifiable = false; // Copy-on-write on next modification
-        return _CollectionUtil.unmodifiableMap(customAttributes);
+        customSettingsModifiable = false; // Copy-on-write on next modification
+        return _CollectionUtil.unmodifiableMap(customSettings);
     }
 
     /**
-     * Called from {@link #getCustomAttributesSnapshot(boolean)}, adds the default (such as inherited) custom attributes
+     * Called from {@link #getCustomSettingsSnapshot(boolean)}, adds the default (such as inherited) custom attributes
      * to the argument {@link Map}.
      */
-    protected abstract void collectDefaultCustomAttributesSnapshot(Map<Serializable, Object> target);
+    protected abstract void collectDefaultCustomSettingsSnapshot(Map<Serializable, Object> target);
 
-    private void ensureCustomAttributesModifiable() {
-        if (!customAttributesModifiable) {
-            customAttributes = new LinkedHashMap<>(customAttributes);
-            customAttributesModifiable = true;
+    private void ensureCustomSettingsModifiable() {
+        if (!customSettingsModifiable) {
+            customSettings = new LinkedHashMap<>(customSettings);
+            customSettingsModifiable = true;
         }
     }
 
     /**
-     * Called be {@link #getCustomAttribute(Serializable)} and {@link #getCustomAttribute(Serializable, Object)} if the
+     * Called be {@link #getCustomSetting(Serializable)} and {@link #getCustomSetting(Serializable, Object)} if the
      * attribute wasn't set in the current {@link ProcessingConfiguration}.
      *
      * @param useDefaultValue
      *         If {@code true}, and the attribute is missing, then return {@code defaultValue}, otherwise throw {@link
-     *         CustomAttributeNotSetException}.
+     *         CustomSettingNotSetException}.
      *
-     * @throws CustomAttributeNotSetException
+     * @throws CustomSettingNotSetException
      *         if the attribute wasn't set in the parents, or has no default otherwise, and {@code useDefaultValue} was
      *         {@code false}.
      */
-    protected abstract Object getDefaultCustomAttribute(
-            Serializable key, Object defaultValue, boolean useDefaultValue) throws CustomAttributeNotSetException;
+    protected abstract Object getDefaultCustomSetting(
+            Serializable key, Object defaultValue, boolean useDefaultValue) throws CustomSettingNotSetException;
 
     /**
-     * Convenience method for calling {@link #setCustomAttribute(Serializable, Object)} for each {@link Map} entry.
+     * Convenience method for calling {@link #setCustomSetting(Serializable, Object)} for each {@link Map} entry.
      * Note that it won't remove the already existing custom attributes.
      */
-    public void setCustomAttributes(Map<? extends Serializable, ?> customAttributes) {
-        _NullArgumentException.check("customAttributes", customAttributes);
-        for (Object value : customAttributes.values()) {
+    public void setCustomSettings(Map<? extends Serializable, ?> customSettings) {
+        _NullArgumentException.check("customSettings", customSettings);
+        for (Object value : customSettings.values()) {
             if (value == MISSING_VALUE_MARKER) {
                 throw new IllegalArgumentException("MISSING_VALUE_MARKER can't be used as attribute value");
             }
         }
 
-        ensureCustomAttributesModifiable();
-        this.customAttributes.putAll(customAttributes);
-        customAttributesModifiable = true;
+        ensureCustomSettingsModifiable();
+        this.customSettings.putAll(customSettings);
+        customSettingsModifiable = true;
     }
 
     /**
-     * Fluent API equivalent of {@link #setCustomAttributes(Map)}
+     * Fluent API equivalent of {@link #setCustomSettings(Map)}
      */
-    public SelfT customAttributes(Map<Serializable, Object> customAttributes) {
-        setCustomAttributes(customAttributes);
+    public SelfT customSettings(Map<Serializable, Object> customSettings) {
+        setCustomSettings(customSettings);
         return self();
     }
 
     /**
      * Used internally to avoid copying the {@link Map} when we know that its content won't change anymore.
      */
-    void setCustomAttributesMap(Map<Serializable, Object> customAttributes) {
-        _NullArgumentException.check("customAttributes", customAttributes);
-        this.customAttributes = customAttributes;
-        this.customAttributesModifiable = false;
+    void setCustomSettingsMap(Map<Serializable, Object> customSettings) {
+        _NullArgumentException.check("customSettings", customSettings);
+        this.customSettings = customSettings;
+        this.customSettingsModifiable = false;
     }
 
     /**
      * Unsets all custom attributes which were set in this {@link ProcessingConfiguration} (but doesn't unset
      * those inherited from a parent {@link ProcessingConfiguration}).
      */
-    public void unsetAllCustomAttributes() {
-        customAttributes = Collections.emptyMap();
-        customAttributesModifiable = false;
+    public void unsetAllCustomSettings() {
+        customSettings = Collections.emptyMap();
+        customSettingsModifiable = false;
     }
 
     protected final List<String> parseAsList(String text) throws GenericParseException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/24673a17/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 3756b34..283d9e1 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
@@ -50,7 +50,7 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 public interface ProcessingConfiguration {
 
     /**
-     * Useful as the default value parameter to {#getCustomAttribute(Serializable, Object)}, because this value is not
+     * Useful as the default value parameter to {#getCustomSetting(Serializable, Object)}, because this value is not
      * allowed for custom attributes.
      */
     Object MISSING_VALUE_MARKER = new Object();
@@ -679,9 +679,9 @@ public interface ProcessingConfiguration {
      * settings added dynamically (as opposed to in compilation time), where each custom attribute is treated as an
      * individual setting. So where predefined configuration settings used to have {@code isXxxSet}, {@code
      * unsetXxx}, and {@code setXxx} methods, custom attributes have these too, with a key (the identifier of the
-     * custom attribute) as an extra argument (see {@link #isCustomAttributeSet(Serializable)},
-     * {@link MutableProcessingConfiguration#setCustomAttribute(Serializable, Object)},
-     * {@link MutableProcessingConfiguration#unsetCustomAttribute(Serializable)}).
+     * custom attribute) as an extra argument (see {@link #isCustomSettingSet(Serializable)},
+     * {@link MutableProcessingConfiguration#setCustomSetting(Serializable, Object)},
+     * {@link MutableProcessingConfiguration#unsetCustomSetting(Serializable)}).
      * <p>
      * When the {@link ProcessingConfiguration} is part of a setting inheritance chain ({@link Environment} inherits
      * settings from the main {@link Template}, which inherits from the {@link Configuration}), this method will search
@@ -708,18 +708,18 @@ public interface ProcessingConfiguration {
      * @return The value of the custom attribute; possibly {@code null}, as that's a legal attribute value. The content
      * of the value object shouldn't be changed after it was added as an attribute (ideally, it should be an
      * immutable object); if you need to change the content, certainly you should use the {@link CustomStateScope}
-     * API. Note that if the custom attribute was created with <tt>&lt;#ftl&nbsp;attributes={...}&gt;</tt>, then this
-     * value is already unwrapped (i.e. it's a <code>String</code>, or a <code>List</code>, or a <code>Map</code>,
+     * API. Note that if the custom attribute was created with <tt>&lt;#ftl&nbsp;customSettings={...}&gt;</tt>, then
+     * this value is already unwrapped (i.e. it's a <code>String</code>, or a <code>List</code>, or a <code>Map</code>,
      * ...etc., not a FreeMarker specific class).
      *
-     * @throws CustomAttributeNotSetException if the custom attribute was not set (not even to {@code null}), nor in
+     * @throws CustomSettingNotSetException if the custom attribute was not set (not even to {@code null}), nor in
      * this {@link ProcessingConfiguration}, nor in another where we inherit settings from. Use
-     * {@link #getCustomAttribute(Serializable, Object)} to avoid this exception.
+     * {@link #getCustomSetting(Serializable, Object)} to avoid this exception.
      */
-    Object getCustomAttribute(Serializable key) throws CustomAttributeNotSetException;
+    Object getCustomSetting(Serializable key) throws CustomSettingNotSetException;
 
     /**
-     * Same as {@link #getCustomAttribute(Serializable)}, but instead of throwing {@link CustomAttributeNotSetException}
+     * Same as {@link #getCustomSetting(Serializable)}, but instead of throwing {@link CustomSettingNotSetException}
      * it returns the default value specified as the 2nd argument.
      *
      * @param defaultValue
@@ -728,7 +728,7 @@ public interface ProcessingConfiguration {
      *         the attribute was set. If you want to know if the value was set, {@link #MISSING_VALUE_MARKER} can
      *         be used, as it's guaranteed that an attribute never has that value.
      */
-    Object getCustomAttribute(Serializable key, Object defaultValue);
+    Object getCustomSetting(Serializable key, Object defaultValue);
 
     /**
      * Tells if this custom attribute is set directly in this object (not in its parent
@@ -737,23 +737,23 @@ public interface ProcessingConfiguration {
      * {@link SettingValueNotSetException}. Note that if an attribute was set to {@code
      * null} (as opposed to not set at all) then this method will return {@code true}.
      */
-    boolean isCustomAttributeSet(Serializable key);
+    boolean isCustomSettingSet(Serializable key);
 
     /**
-     * Collects all {@linkplain #getCustomAttribute(Serializable)} custom attributes} into a {@link Map}; mostly useful for
+     * Collects all {@linkplain #getCustomSetting(Serializable)} custom attributes} into a {@link Map}; mostly useful for
      * debugging and tooling, and is possibly too slow to call very frequently.
      *
      * @param includeInherited
      *         If {@code false}, only the custom attributes set in this {@link ProcessingConfiguration} will be
      *         collected, otherwise the custom attributes inherited from the parent {@link ProcessingConfiguration}-s
      *         will be too. Note that it's the last that matches the behavior of {@link
-     *         #getCustomAttribute(Serializable)}.
+     *         #getCustomSetting(Serializable)}.
      *
      * @return An unmodifiable and unchanging {@link Map}; not {@code null}. The object identity of keys and values of
      * this {@link Map} will not change when custom attributes are set/unset later (hence it's a snapshot). But, if
      * a key or value objects are themselves mutable objects, FreeMarker can't prevent their content from changing.
      * You shouldn't change the content of those objects.
      */
-    Map<Serializable, Object> getCustomAttributesSnapshot(boolean includeInherited);
+    Map<Serializable, Object> getCustomSettingsSnapshot(boolean includeInherited);
 
 }