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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java 
b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
new file mode 100644
index 0000000..16cbdd5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.Writer;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+
+/**
+ * <b>Don't implement this interface yourself</b>; use the existing 
implementation(s). This interface is implemented by
+ * classes that hold settings that affect {@linkplain Template#process(Object, 
Writer) template processing} (as opposed
+ * to template parsing). New parser settings can be added in new FreeMarker 
versions, which will break your
+ * implementation.
+ *
+ * @see ParserConfiguration
+ */
+// TODO [FM3] JavaDoc
+public interface ProcessingConfiguration {
+
+     /**
+     * Getter pair of {@link MutableProcessingConfiguration#setLocale(Locale)}.
+     */
+    Locale getLocale();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLocaleSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setTimeZone(TimeZone)}.
+     */
+    TimeZone getTimeZone();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTimeZoneSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setSQLDateAndTimeTimeZone(TimeZone)}.
+     *
+     * @return {@code null} if the value of {@link #getTimeZone()} should be 
used for formatting {@link java.sql.Date
+     * java.sql.Date} and {@link java.sql.Time java.sql.Time} values, 
otherwise the time zone that should be used to
+     * format the values of those two types.
+     */
+    TimeZone getSQLDateAndTimeTimeZone();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isSQLDateAndTimeTimeZoneSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setNumberFormat(String)}.
+     */
+    String getNumberFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNumberFormatSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setCustomNumberFormats(Map)}.
+     */
+    Map<String, ? extends TemplateNumberFormatFactory> 
getCustomNumberFormats();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomNumberFormatsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setBooleanFormat(String)}.
+     */
+    String getBooleanFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isBooleanFormatSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setTimeFormat(String)}.
+     */
+    String getTimeFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTimeFormatSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setDateFormat(String)}.
+     */
+    String getDateFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isDateFormatSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setDateTimeFormat(String)}.
+     */
+    String getDateTimeFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isDateTimeFormatSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setCustomDateFormats(Map)}.
+     */
+    Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomDateFormatsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setTemplateExceptionHandler(TemplateExceptionHandler)}.
+     */
+    TemplateExceptionHandler getTemplateExceptionHandler();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTemplateExceptionHandlerSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setArithmeticEngine(ArithmeticEngine)}.
+     */
+    ArithmeticEngine getArithmeticEngine();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isArithmeticEngineSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setObjectWrapper(ObjectWrapper)}.
+     */
+    ObjectWrapper getObjectWrapper();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isObjectWrapperSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setOutputEncoding(String)}.
+     */
+    String getOutputEncoding();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isOutputEncodingSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setURLEscapingCharset(String)}.
+     */
+    String getURLEscapingCharset();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isURLEscapingCharsetSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setNewBuiltinClassResolver(TemplateClassResolver)}.
+     */
+    TemplateClassResolver getNewBuiltinClassResolver();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNewBuiltinClassResolverSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setAutoFlush(boolean)}.
+     */
+    boolean getAutoFlush();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoFlushSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setShowErrorTips(boolean)}.
+     */
+    boolean getShowErrorTips();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isShowErrorTipsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setLogTemplateExceptions(boolean)}.
+     */
+    boolean getLogTemplateExceptions();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLogTemplateExceptionsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setLazyImports(boolean)}.
+     */
+    boolean getLazyImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLazyImportsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setLazyAutoImports(Boolean)}.
+     */
+    Boolean getLazyAutoImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLazyAutoImportsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setAutoImports(Map)}.
+     */
+    Map<String, String> getAutoImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoImportsSet();
+
+    /**
+     * Getter pair of {@link 
MutableProcessingConfiguration#setAutoIncludes(List)}.
+     */
+    List<String> getAutoIncludes();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoIncludesSet();
+
+    Map<Object, Object> getCustomAttributes();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then 
depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the 
setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomAttributesSet();
+
+    Object getCustomAttribute(Object name);
+
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
index a5a574c..4436874 100644
--- 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
@@ -551,10 +551,8 @@ public class DefaultTemplateResolver extends 
TemplateResolver {
         if (tc != null && tc.isLocaleSet()) {
             locale = tc.getLocale();
         }
-
-        String initialEncoding = tc != null && tc.isEncodingSet() ? 
tc.getEncoding() : config.getDefaultEncoding();
-        TemplateLanguage templateLanguage = tc != null && 
tc.isTemplateLanguageSet() ? tc.getTemplateLanguage()
-                : config .getTemplateLanguage();
+        String initialEncoding = tc != null ? tc.getEncoding() : 
config.getEncoding();
+        TemplateLanguage templateLanguage = tc != null ? 
tc.getTemplateLanguage() : config .getTemplateLanguage();
 
         Template template;
         {

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

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

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java 
b/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java
new file mode 100644
index 0000000..cbd7e11
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/util/_ObjectHolder.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.util;
+
+/**
+ * For internal use only; don't depend on this, there's no backward 
compatibility guarantee at all!
+ */
+public class _ObjectHolder<T> {
+
+    private T object;
+
+    public _ObjectHolder(T object) {
+        this.object = object;
+    }
+
+    public T get() {
+        return object;
+    }
+
+    public void set(T object) {
+        this.object = object;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        _ObjectHolder<?> that = (_ObjectHolder<?>) o;
+
+        return object != null ? object.equals(that.object) : that.object == 
null;
+    }
+
+    @Override
+    public int hashCode() {
+        return object != null ? object.hashCode() : 0;
+    }
+}

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

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

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

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

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

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


Reply via email to