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/03/27 11:48:51 UTC

[2/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.

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