freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [3/4] incubator-freemarker git commit: Made Template immutable (via public API-s). Template-specific settings now can only come from the TemplateConfiguration associated to the template, or from the #ftl header for some settings (most notably for custom
Date Tue, 11 Apr 2017 20:05:17 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 e1fa479..f2a5fc6 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -30,57 +30,28 @@ import java.util.TimeZone;
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
-import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
 import org.apache.freemarker.core.util.CommonBuilder;
-import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
 /**
- * Used for customizing the configuration settings for individual {@link Template}-s (or rather groups of templates),
- * relatively to the common setting values coming from the {@link Configuration}. This was designed with the standard
- * template loading mechanism of FreeMarker in mind ({@link Configuration#getTemplate(String)}
- * and {@link DefaultTemplateResolver}), though can also be reused for custom template loading and caching solutions.
- * 
+ * A partial set of configuration settings used for customizing the {@link Configuration}-level settings for individual
+ * {@link Template}-s (or rather, for a group of templates). That it's partial means that you should call the
+ * corresponding {@code isXxxSet()} before getting a settings, or else you may cause
+ * {@link SettingValueNotSetException}. (The fallback to the {@link Configuration} setting isn't automatic to keep
+ * the dependency graph of configuration related beans non-cyclic. As user code seldom reads settings from here anyway,
+ * this compromise was chosen.)
  * <p>
- * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism (
- * {@link Configuration#getTemplate(String)} and its overloads), localized lookup happens before the {@code locale}
- * specified here could have effect. The {@code locale} will be only set in the template that the localized lookup has
- * already found.
- * 
+ * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism ({@link
+ * Configuration#getTemplate(String)} and its overloads), localized lookup happens before the {@code locale} specified
+ * here could have effect. The {@code locale} will be only set in the template that the localized lookup has already
+ * found.
  * <p>
- * Note on the sourceEncoding setting {@code sourceEncoding}: See {@link Builder#setSourceEncoding(Charset)}.
- * 
- * <p>
- * Note that the result value of the reader methods (getter and "is" methods) is usually not useful unless the value of
- * that setting was already set on this object. Otherwise you will get the value from the parent {@link Configuration},
- * or an {@link IllegalStateException} before this object is associated to a {@link Configuration}.
- * 
- * <p>
- * If you are using this class for your own template loading and caching solution, rather than with the standard one,
- * you should be aware of a few more details:
- * 
- * <ul>
- * <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 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 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}
- * header, those values should have precedence. A consequence of this is that if you want to configure the same
- * {@link Template} with multiple {@link TemplateConfiguration}-s, you either should merge them to a single one before
- * that (with {@link Builder#merge(ParserAndProcessingConfiguration)}), or you have to apply them in reverse order of
- * their intended precedence.
- * </ul>
- * 
- * @see Template#Template(String, String, Reader, Configuration, ParserConfiguration, Charset)
- * 
- * @since 2.3.24
+ * This class is immutable. Use {@link TemplateConfiguration.Builder} to create a new instance.
+ *
+ * @see Template#Template(String, String, Reader, Configuration, TemplateConfiguration, Charset)
  */
 public final class TemplateConfiguration implements ParserAndProcessingConfiguration {
-    private Configuration configuration;
 
     private final Locale locale;
     private final String numberFormat;
@@ -164,162 +135,6 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
         tabSize = builder.isTabSizeSet() ? builder.getTabSize() : null;
     }
 
-    /**
-     * Associates this instance with a {@link Configuration}; usually you don't call this, as it's called internally
-     * when this instance is added to a {@link Configuration}. This method can be called only once (except with the same
-     * {@link Configuration} parameter again, as that changes nothing anyway).
-     *
-     * @throws IllegalArgumentException
-     *             if the argument is {@code null} or not a {@link Configuration}
-     * @throws IllegalStateException
-     *             if this object is already associated to a different {@link Configuration} object,
-     *             or if the {@code Configuration} has {@code #getIncompatibleImprovements()} less than 2.3.22 and
-     *             this object tries to change any non-parser settings
-     */
-    public void setParentConfiguration(Configuration configuration) {
-        _NullArgumentException.check(configuration);
-        synchronized (this) {
-            if (this.configuration != null && this.configuration != configuration) {
-                throw new IllegalStateException(
-                        "This TemplateConfiguration was already associated to another Configuration");
-            }
-            this.configuration = configuration;
-        }
-    }
-
-    /**
-     * Returns the parent {@link Configuration}, or {@code null} if none was associated yet.
-     */
-    public Configuration getParentConfiguration() {
-        return configuration;
-    }
-
-    private Configuration getNonNullParentConfiguration() {
-        if (configuration == null) {
-            throw new IllegalStateException("The TemplateConfiguration wasn't associated with a Configuration yet.");
-        }
-        return configuration;
-    }
-
-    /**
-     * Sets those settings of the {@link Template} which aren't yet set in the {@link Template} and are set in this
-     * {@link TemplateConfiguration}, leaves the other settings as is. A setting is said to be set in a
-     * {@link TemplateConfiguration} or {@link Template} if it was explicitly set via a setter method on that object, as
-     * opposed to be inherited from the {@link Configuration}.
-     * 
-     * <p>
-     * Note that this method doesn't deal with settings that influence the parser, as those are already baked in at this
-     * point via the {@link ParserConfiguration}. 
-     * 
-     * <p>
-     * Note that the {@code sourceEncoding} setting of the {@link Template} counts as unset if it's {@code null},
-     * even if {@code null} was set via {@link Template#setActualSourceEncoding(Charset)}.
-     *
-     * @throws IllegalStateException
-     *             If the parent configuration wasn't yet set.
-     */
-    public void apply(Template template) {
-        Configuration cfg = getNonNullParentConfiguration();
-        if (template.getConfiguration() != cfg) {
-            // This is actually not a problem right now, but for future BC we enforce this.
-            throw new IllegalArgumentException(
-                    "The argument Template doesn't belong to the same Configuration as the TemplateConfiguration");
-        }
-
-        if (isAPIBuiltinEnabledSet() && !template.isAPIBuiltinEnabledSet()) {
-            template.setAPIBuiltinEnabled(getAPIBuiltinEnabled());
-        }
-        if (isArithmeticEngineSet() && !template.isArithmeticEngineSet()) {
-            template.setArithmeticEngine(getArithmeticEngine());
-        }
-        if (isAutoFlushSet() && !template.isAutoFlushSet()) {
-            template.setAutoFlush(getAutoFlush());
-        }
-        if (isBooleanFormatSet() && !template.isBooleanFormatSet()) {
-            template.setBooleanFormat(getBooleanFormat());
-        }
-        if (isCustomDateFormatsSet()) {
-            template.setCustomDateFormats(
-                    mergeMaps(
-                            getCustomDateFormats(),
-                            template.isCustomDateFormatsSet() ? template.getCustomDateFormats() : null,
-                            false));
-        }
-        if (isCustomNumberFormatsSet()) {
-            template.setCustomNumberFormats(
-                    mergeMaps(
-                            getCustomNumberFormats(),
-                            template.isCustomNumberFormatsSet() ? template.getCustomNumberFormats() : null,
-                            false));
-        }
-        if (isDateFormatSet() && !template.isDateFormatSet()) {
-            template.setDateFormat(getDateFormat());
-        }
-        if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) {
-            template.setDateTimeFormat(getDateTimeFormat());
-        }
-        if (isLocaleSet() && !template.isLocaleSet()) {
-            template.setLocale(getLocale());
-        }
-        if (isLogTemplateExceptionsSet() && !template.isLogTemplateExceptionsSet()) {
-            template.setLogTemplateExceptions(getLogTemplateExceptions());
-        }
-        if (isNewBuiltinClassResolverSet() && !template.isNewBuiltinClassResolverSet()) {
-            template.setNewBuiltinClassResolver(getNewBuiltinClassResolver());
-        }
-        if (isNumberFormatSet() && !template.isNumberFormatSet()) {
-            template.setNumberFormat(getNumberFormat());
-        }
-        if (isObjectWrapperSet() && !template.isObjectWrapperSet()) {
-            template.setObjectWrapper(getObjectWrapper());
-        }
-        if (isOutputEncodingSet() && !template.isOutputEncodingSet()) {
-            template.setOutputEncoding(getOutputEncoding());
-        }
-        if (isShowErrorTipsSet() && !template.isShowErrorTipsSet()) {
-            template.setShowErrorTips(getShowErrorTips());
-        }
-        if (isSQLDateAndTimeTimeZoneSet() && !template.isSQLDateAndTimeTimeZoneSet()) {
-            template.setSQLDateAndTimeTimeZone(getSQLDateAndTimeTimeZone());
-        }
-        if (isTemplateExceptionHandlerSet() && !template.isTemplateExceptionHandlerSet()) {
-            template.setTemplateExceptionHandler(getTemplateExceptionHandler());
-        }
-        if (isTimeFormatSet() && !template.isTimeFormatSet()) {
-            template.setTimeFormat(getTimeFormat());
-        }
-        if (isTimeZoneSet() && !template.isTimeZoneSet()) {
-            template.setTimeZone(getTimeZone());
-        }
-        if (isURLEscapingCharsetSet() && !template.isURLEscapingCharsetSet()) {
-            template.setURLEscapingCharset(getURLEscapingCharset());
-        }
-        if (isLazyImportsSet() && !template.isLazyImportsSet()) {
-            template.setLazyImports(getLazyImports());
-        }
-        if (isLazyAutoImportsSet() && !template.isLazyAutoImportsSet()) {
-            template.setLazyAutoImports(getLazyAutoImports());
-        }
-        if (isAutoImportsSet()) {
-            // Regarding the order of the maps in the merge:
-            // - 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.isAutoImportsSet() ? template.getAutoImports() : null,
-                    true));
-        }
-        if (isAutoIncludesSet()) {
-            template.setAutoIncludes(mergeLists(
-                    getAutoIncludes(),
-                    template.isAutoIncludesSet() ? template.getAutoIncludes() : null));
-        }
-        
-        copyDirectCustomAttributes(template, false);
-
-    }
-
     private static <K,V> Map<K,V> mergeMaps(Map<K,V> m1, Map<K,V> m2, boolean overwriteUpdatesOrder) {
         if (m1 == null) return m2;
         if (m2 == null) return m1;
@@ -371,7 +186,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public int getTagSyntax() {
-        return tagSyntax != null ? tagSyntax : getNonNullParentConfiguration().getTagSyntax();
+        if (!isTagSyntaxSet()) {
+            throw new SettingValueNotSetException("tagSyntax");
+        }
+        return tagSyntax;
     }
 
     @Override
@@ -381,7 +199,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TemplateLanguage getTemplateLanguage() {
-        return templateLanguage != null ? templateLanguage : getNonNullParentConfiguration().getTemplateLanguage();
+        if (!isTemplateLanguageSet()) {
+            throw new SettingValueNotSetException("templateLanguage");
+        }
+        return templateLanguage;
     }
 
     @Override
@@ -391,8 +212,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public int getNamingConvention() {
-        return namingConvention != null ? namingConvention
-                : getNonNullParentConfiguration().getNamingConvention();
+        if (!isNamingConventionSet()) {
+            throw new SettingValueNotSetException("namingConvention");
+        }
+        return namingConvention;
     }
 
     @Override
@@ -402,8 +225,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getWhitespaceStripping() {
-        return whitespaceStripping != null ? whitespaceStripping
-                : getNonNullParentConfiguration().getWhitespaceStripping();
+        if (!isWhitespaceStrippingSet()) {
+            throw new SettingValueNotSetException("whitespaceStripping");
+        }
+        return whitespaceStripping;
     }
 
     @Override
@@ -413,8 +238,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy
-                : getNonNullParentConfiguration().getAutoEscapingPolicy();
+        if (!isAutoEscapingPolicySet()) {
+            throw new SettingValueNotSetException("autoEscapingPolicy");
+        }
+        return autoEscapingPolicy;
     }
 
     @Override
@@ -424,12 +251,18 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public OutputFormat getOutputFormat() {
-        return outputFormat != null ? outputFormat : getNonNullParentConfiguration().getOutputFormat();
+        if (!isOutputFormatSet()) {
+            throw new SettingValueNotSetException("outputFormat");
+        }
+        return outputFormat;
     }
 
     @Override
     public ArithmeticEngine getArithmeticEngine() {
-        return isArithmeticEngineSet() ? arithmeticEngine : getNonNullParentConfiguration().getArithmeticEngine();
+        if (!isArithmeticEngineSet()) {
+            throw new SettingValueNotSetException("arithmeticEngine");
+        }
+        return arithmeticEngine;
     }
 
     @Override
@@ -444,8 +277,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
     
     @Override
     public boolean getRecognizeStandardFileExtensions() {
-        return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions
-                : getNonNullParentConfiguration().getRecognizeStandardFileExtensions();
+        if (!isRecognizeStandardFileExtensionsSet()) {
+            throw new SettingValueNotSetException("recognizeStandardFileExtensions");
+        }
+        return recognizeStandardFileExtensions;
     }
     
     @Override
@@ -455,7 +290,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Charset getSourceEncoding() {
-        return isSourceEncodingSet() ? sourceEncoding : getNonNullParentConfiguration().getSourceEncoding();
+        if (!isSourceEncodingSet()) {
+            throw new SettingValueNotSetException("sourceEncoding");
+        }
+        return sourceEncoding;
     }
 
     @Override
@@ -465,8 +303,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
     
     @Override
     public int getTabSize() {
-        return isTabSizeSet() ? tabSize
-                : getNonNullParentConfiguration().getTabSize();
+        if (!isTabSizeSet()) {
+            throw new SettingValueNotSetException("tabSize");
+        }
+        return tabSize;
     }
     
     @Override
@@ -475,20 +315,20 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
     }
     
     /**
-     * Returns {@link Configuration#getIncompatibleImprovements()} from the parent {@link Configuration}. This mostly
-     * just exist to satisfy the {@link ParserConfiguration} interface.
-     * 
-     * @throws IllegalStateException
-     *             If the parent configuration wasn't yet set.
+     * Always throws {@link SettingValueNotSetException}, as this can't be set on the {@link TemplateConfiguration}
+     * level.
      */
     @Override
     public Version getIncompatibleImprovements() {
-        return getNonNullParentConfiguration().getIncompatibleImprovements();
+        throw new SettingValueNotSetException("incompatibleImprovements");
     }
 
     @Override
     public Locale getLocale() {
-        return isLocaleSet() ? locale : getNonNullParentConfiguration().getLocale();
+        if (!isLocaleSet()) {
+            throw new SettingValueNotSetException("locale");
+        }
+        return locale;
     }
 
     @Override
@@ -498,7 +338,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TimeZone getTimeZone() {
-        return isTimeZoneSet() ? timeZone : getNonNullParentConfiguration().getTimeZone();
+        if (!isTimeZoneSet()) {
+            throw new SettingValueNotSetException("timeZone");
+        }
+        return timeZone;
     }
 
     @Override
@@ -508,8 +351,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TimeZone getSQLDateAndTimeTimeZone() {
-        return isSQLDateAndTimeTimeZoneSet() ? sqlDateAndTimeTimeZone : getNonNullParentConfiguration()
-                .getSQLDateAndTimeTimeZone();
+        if (!isSQLDateAndTimeTimeZoneSet()) {
+            throw new SettingValueNotSetException("sqlDateAndTimeTimeZone");
+        }
+        return sqlDateAndTimeTimeZone;
     }
 
     @Override
@@ -519,7 +364,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getNumberFormat() {
-        return isNumberFormatSet() ? numberFormat : getNonNullParentConfiguration().getNumberFormat();
+        if (!isNumberFormatSet()) {
+            throw new SettingValueNotSetException("numberFormat");
+        }
+        return numberFormat;
     }
 
     @Override
@@ -529,18 +377,15 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
-        return isCustomNumberFormatsSet() ? customNumberFormats : getNonNullParentConfiguration().getCustomNumberFormats();
+        if (!isCustomNumberFormatsSet()) {
+            throw new SettingValueNotSetException("customNumberFormats");
+        }
+        return customNumberFormats;
     }
 
     @Override
     public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
-        if (isCustomNumberFormatsSet()) {
-            TemplateNumberFormatFactory format = customNumberFormats.get(name);
-            if (format != null) {
-                return  format;
-            }
-        }
-        return getNonNullParentConfiguration().getCustomNumberFormat(name);
+        return getCustomNumberFormats().get(name);
     }
 
     @Override
@@ -550,13 +395,16 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean hasCustomFormats() {
-        return isCustomNumberFormatsSet() ? !customNumberFormats.isEmpty()
-                : getNonNullParentConfiguration().hasCustomFormats();
+        return isCustomNumberFormatsSet() && !customNumberFormats.isEmpty()
+                || isCustomDateFormatsSet() && !customDateFormats.isEmpty();
     }
 
     @Override
     public String getBooleanFormat() {
-        return isBooleanFormatSet() ? booleanFormat : getNonNullParentConfiguration().getBooleanFormat();
+        if (!isBooleanFormatSet()) {
+            throw new SettingValueNotSetException("booleanFormat");
+        }
+        return booleanFormat;
     }
 
     @Override
@@ -566,7 +414,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getTimeFormat() {
-        return isTimeFormatSet() ? timeFormat : getNonNullParentConfiguration().getTimeFormat();
+        if (!isTimeFormatSet()) {
+            throw new SettingValueNotSetException("timeFormat");
+        }
+        return timeFormat;
     }
 
     @Override
@@ -576,7 +427,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getDateFormat() {
-        return isDateFormatSet() ? dateFormat : getNonNullParentConfiguration().getDateFormat();
+        if (!isDateFormatSet()) {
+            throw new SettingValueNotSetException("dateFormat");
+        }
+        return dateFormat;
     }
 
     @Override
@@ -586,7 +440,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public String getDateTimeFormat() {
-        return isDateTimeFormatSet() ? dateTimeFormat : getNonNullParentConfiguration().getDateTimeFormat();
+        if (!isDateTimeFormatSet()) {
+            throw new SettingValueNotSetException("dateTimeFormat");
+        }
+        return dateTimeFormat;
     }
 
     @Override
@@ -596,7 +453,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<String, TemplateDateFormatFactory> getCustomDateFormats() {
-        return isCustomDateFormatsSet() ? customDateFormats : getNonNullParentConfiguration().getCustomDateFormats();
+        if (!isCustomDateFormatsSet()) {
+            throw new SettingValueNotSetException("customDateFormats");
+        }
+        return customDateFormats;
     }
 
     @Override
@@ -607,7 +467,7 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
                 return  format;
             }
         }
-        return getNonNullParentConfiguration().getCustomDateFormat(name);
+        return null;
     }
 
     @Override
@@ -617,7 +477,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TemplateExceptionHandler getTemplateExceptionHandler() {
-        return isTemplateExceptionHandlerSet() ? templateExceptionHandler : getNonNullParentConfiguration().getTemplateExceptionHandler();
+        if (!isTemplateExceptionHandlerSet()) {
+            throw new SettingValueNotSetException("templateExceptionHandler");
+        }
+        return templateExceptionHandler;
     }
 
     @Override
@@ -627,7 +490,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public ObjectWrapper getObjectWrapper() {
-        return isObjectWrapperSet() ? objectWrapper : getNonNullParentConfiguration().getObjectWrapper();
+        if (!isObjectWrapperSet()) {
+            throw new SettingValueNotSetException("objectWrapper");
+        }
+        return objectWrapper;
     }
 
     @Override
@@ -637,7 +503,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Charset getOutputEncoding() {
-        return isOutputEncodingSet() ? outputEncoding : getNonNullParentConfiguration().getOutputEncoding();
+        if (!isOutputEncodingSet()) {
+            throw new SettingValueNotSetException("");
+        }
+        return outputEncoding;
     }
 
     @Override
@@ -647,7 +516,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Charset getURLEscapingCharset() {
-        return isURLEscapingCharsetSet() ? urlEscapingCharset : getNonNullParentConfiguration().getURLEscapingCharset();
+        if (!isURLEscapingCharsetSet()) {
+            throw new SettingValueNotSetException("urlEscapingCharset");
+        }
+        return urlEscapingCharset;
     }
 
     @Override
@@ -657,7 +529,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public TemplateClassResolver getNewBuiltinClassResolver() {
-        return isNewBuiltinClassResolverSet() ? newBuiltinClassResolver : getNonNullParentConfiguration().getNewBuiltinClassResolver();
+        if (!isNewBuiltinClassResolverSet()) {
+            throw new SettingValueNotSetException("newBuiltinClassResolver");
+        }
+        return newBuiltinClassResolver;
     }
 
     @Override
@@ -667,7 +542,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getAPIBuiltinEnabled() {
-        return isAPIBuiltinEnabledSet() ? apiBuiltinEnabled : getNonNullParentConfiguration().getAPIBuiltinEnabled();
+        if (!isAPIBuiltinEnabledSet()) {
+            throw new SettingValueNotSetException("apiBuiltinEnabled");
+        }
+        return apiBuiltinEnabled;
     }
 
     @Override
@@ -677,7 +555,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getAutoFlush() {
-        return isAutoFlushSet() ? autoFlush : getNonNullParentConfiguration().getAutoFlush();
+        if (!isAutoFlushSet()) {
+            throw new SettingValueNotSetException("autoFlush");
+        }
+        return autoFlush;
     }
 
     @Override
@@ -687,7 +568,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getShowErrorTips() {
-        return isShowErrorTipsSet() ? showErrorTips : getNonNullParentConfiguration().getShowErrorTips();
+        if (!isShowErrorTipsSet()) {
+            throw new SettingValueNotSetException("showErrorTips");
+        }
+        return showErrorTips;
     }
 
     @Override
@@ -697,7 +581,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getLogTemplateExceptions() {
-        return isLogTemplateExceptionsSet() ? logTemplateExceptions : getNonNullParentConfiguration().getLogTemplateExceptions();
+        if (!isLogTemplateExceptionsSet()) {
+            throw new SettingValueNotSetException("logTemplateExceptions");
+        }
+        return logTemplateExceptions;
     }
 
     @Override
@@ -707,7 +594,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public boolean getLazyImports() {
-        return isLazyImportsSet() ? lazyImports : getNonNullParentConfiguration().getLazyImports();
+        if (!isLazyImportsSet()) {
+            throw new SettingValueNotSetException("lazyImports");
+        }
+        return lazyImports;
     }
 
     @Override
@@ -717,7 +607,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Boolean getLazyAutoImports() {
-        return isLazyAutoImportsSet() ? lazyAutoImports : getNonNullParentConfiguration().getLazyAutoImports();
+        if (!isLazyAutoImportsSet()) {
+            throw new SettingValueNotSetException("lazyAutoImports");
+        }
+        return lazyAutoImports;
     }
 
     @Override
@@ -727,7 +620,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<String, String> getAutoImports() {
-        return isAutoImportsSet() ? autoImports : getNonNullParentConfiguration().getAutoImports();
+        if (!isAutoImportsSet()) {
+            throw new SettingValueNotSetException("");
+        }
+        return autoImports;
     }
 
     @Override
@@ -737,7 +633,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public List<String> getAutoIncludes() {
-        return isAutoIncludesSet() ? autoIncludes : getNonNullParentConfiguration().getAutoIncludes();
+        if (!isAutoIncludesSet()) {
+            throw new SettingValueNotSetException("autoIncludes");
+        }
+        return autoIncludes;
     }
 
     @Override
@@ -747,7 +646,10 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
 
     @Override
     public Map<Object, Object> getCustomAttributes() {
-        return isCustomAttributesSet() ? customAttributes : getNonNullParentConfiguration().getCustomAttributes();
+        if (!isCustomAttributesSet()) {
+            throw new SettingValueNotSetException("customAttributes");
+        }
+        return customAttributes;
     }
 
     @Override
@@ -764,14 +666,14 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
                 return attValue;
             }
         }
-        return getNonNullParentConfiguration().getCustomAttribute(name);
+        return null;
     }
 
     public static final class Builder extends MutableProcessingAndParseConfiguration<Builder>
             implements CommonBuilder<TemplateConfiguration> {
 
         public Builder() {
-            super((Configuration) null);
+            super();
         }
 
         @Override
@@ -779,12 +681,6 @@ public final class TemplateConfiguration implements ParserAndProcessingConfigura
             return new TemplateConfiguration(this);
         }
 
-        // TODO This will be removed
-        @Override
-        void setParent(ProcessingConfiguration cfg) {
-            throw new UnsupportedOperationException();
-        }
-
         @Override
         protected Locale getInheritedLocale() {
             throw new SettingValueNotSetException("locale");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateLanguage.java b/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
index 9de92bb..205fa8c 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
@@ -43,10 +43,11 @@ public abstract class TemplateLanguage {
         }
 
         @Override
-        public Template parse(String name, String sourceName, Reader reader, Configuration cfg, ParserConfiguration
-                customParserConfiguration, Charset encoding, InputStream streamToUnmarkWhenEncEstabd) throws
+        public Template parse(String name, String sourceName, Reader reader, Configuration cfg,
+                TemplateConfiguration templateConfiguration, Charset encoding,
+                InputStream streamToUnmarkWhenEncEstabd) throws
                 IOException, ParseException {
-            return new Template(name, sourceName, reader, cfg, customParserConfiguration,
+            return new Template(name, sourceName, reader, cfg, templateConfiguration,
                     encoding, streamToUnmarkWhenEncEstabd);
         }
     };
@@ -58,8 +59,9 @@ public abstract class TemplateLanguage {
         }
 
         @Override
-        public Template parse(String name, String sourceName, Reader reader, Configuration cfg, ParserConfiguration
-                customParserConfiguration, Charset sourceEncoding, InputStream streamToUnmarkWhenEncEstabd)
+        public Template parse(String name, String sourceName, Reader reader, Configuration cfg,
+                TemplateConfiguration templateConfiguration, Charset sourceEncoding,
+                InputStream streamToUnmarkWhenEncEstabd)
                 throws IOException, ParseException {
             // Read the contents into a StringWriter, then construct a single-text-block template from it.
             final StringBuilder sb = new StringBuilder();
@@ -82,17 +84,18 @@ public abstract class TemplateLanguage {
 
     /**
      * Returns if the template can specify its own charset inside the template. If so, {@link #parse(String, String,
-     * Reader, Configuration, ParserConfiguration, Charset, InputStream)} can throw
+     * Reader, Configuration, TemplateConfiguration, Charset, InputStream)} can throw
      * {@link WrongTemplateCharsetException}, and it might gets a non-{@code null} for the {@link InputStream}
      * parameter.
      */
     public abstract boolean getCanSpecifyCharsetInContent();
 
     /**
-     * See {@link Template#Template(String, String, Reader, Configuration, ParserConfiguration, Charset, InputStream)}.
+     * See {@link Template#Template(String, String, Reader, Configuration, TemplateConfiguration, Charset,
+     * InputStream)}.
      */
     public abstract Template parse(String name, String sourceName, Reader reader,
-                                   Configuration cfg, ParserConfiguration customParserConfiguration,
+                                   Configuration cfg, TemplateConfiguration templateConfiguration,
                                    Charset encoding, InputStream streamToUnmarkWhenEncEstabd)
             throws IOException, ParseException;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java b/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java
new file mode 100644
index 0000000..e9758cf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/TemplateParserConfigurationWithFallback.java
@@ -0,0 +1,146 @@
+/*
+ * 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.nio.charset.Charset;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Adds {@link Configuration} fallback to the {@link ParserConfiguration} part of a {@link TemplateConfiguration}.
+ */
+final class TemplateParserConfigurationWithFallback implements ParserConfiguration {
+
+    private final Configuration cfg;
+    private final TemplateConfiguration tCfg;
+
+    TemplateParserConfigurationWithFallback(Configuration cfg, TemplateConfiguration tCfg) {
+        this.cfg = cfg;
+        this.tCfg = tCfg;
+    }
+
+    @Override
+    public TemplateLanguage getTemplateLanguage() {
+        return tCfg.isTemplateLanguageSet() ? tCfg.getTemplateLanguage() : cfg.getTemplateLanguage();
+    }
+
+    @Override
+    public boolean isTemplateLanguageSet() {
+        return true;
+    }
+
+    @Override
+    public int getTagSyntax() {
+        return tCfg.isTagSyntaxSet() ? tCfg.getTagSyntax() : cfg.getTagSyntax();
+    }
+
+    @Override
+    public boolean isTagSyntaxSet() {
+        return true;
+    }
+
+    @Override
+    public int getNamingConvention() {
+        return tCfg.isNamingConventionSet() ? tCfg.getNamingConvention() : cfg.getNamingConvention();
+    }
+
+    @Override
+    public boolean isNamingConventionSet() {
+        return true;
+    }
+
+    @Override
+    public boolean getWhitespaceStripping() {
+        return tCfg.isWhitespaceStrippingSet() ? tCfg.getWhitespaceStripping() : cfg.getWhitespaceStripping();
+    }
+
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return true;
+    }
+
+    @Override
+    public ArithmeticEngine getArithmeticEngine() {
+        return tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine();
+    }
+
+    @Override
+    public boolean isArithmeticEngineSet() {
+        return true;
+    }
+
+    @Override
+    public int getAutoEscapingPolicy() {
+        return tCfg.isAutoEscapingPolicySet() ? tCfg.getAutoEscapingPolicy() : cfg.getAutoEscapingPolicy();
+    }
+
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return true;
+    }
+
+    @Override
+    public OutputFormat getOutputFormat() {
+        return tCfg.isOutputFormatSet() ? tCfg.getOutputFormat() : cfg.getOutputFormat();
+    }
+
+    @Override
+    public boolean isOutputFormatSet() {
+        return true;
+    }
+
+    @Override
+    public boolean getRecognizeStandardFileExtensions() {
+        return tCfg.isRecognizeStandardFileExtensionsSet() ? tCfg.getRecognizeStandardFileExtensions()
+                : cfg.getRecognizeStandardFileExtensions();
+    }
+
+    @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return true;
+    }
+
+    @Override
+    public Version getIncompatibleImprovements() {
+        // This can be only set on the Configuration-level
+        return cfg.getIncompatibleImprovements();
+    }
+
+    @Override
+    public int getTabSize() {
+        return tCfg.isTabSizeSet() ? tCfg.getTabSize() : cfg.getTabSize();
+    }
+
+    @Override
+    public boolean isTabSizeSet() {
+        return true;
+    }
+
+    @Override
+    public Charset getSourceEncoding() {
+        return tCfg.isSourceEncodingSet() ? tCfg.getSourceEncoding() : cfg.getSourceEncoding();
+    }
+
+    @Override
+    public boolean isSourceEncodingSet() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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
deleted file mode 100644
index c9c7380..0000000
--- a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.freemarker.core;
-
-import java.nio.charset.Charset;
-
-import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
-import org.apache.freemarker.core.outputformat.OutputFormat;
-
-/**
- * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- */ 
-public final class _ParserConfigurationWithInheritedFormat implements ParserConfiguration {
-
-    private final OutputFormat outputFormat;
-    private final Integer autoEscapingPolicy;
-    private final ParserConfiguration wrappedPCfg;
-
-    public _ParserConfigurationWithInheritedFormat(ParserConfiguration wrappedPCfg, OutputFormat outputFormat,
-            Integer autoEscapingPolicy) {
-        this.outputFormat = outputFormat;
-        this.autoEscapingPolicy = autoEscapingPolicy;
-        this.wrappedPCfg = wrappedPCfg;
-    }
-
-    @Override
-    public boolean getWhitespaceStripping() {
-        return wrappedPCfg.getWhitespaceStripping();
-    }
-
-    @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();
-    }
-
-    @Override
-    public boolean isTemplateLanguageSet() {
-        return wrappedPCfg.isTemplateLanguageSet();
-    }
-
-    @Override
-    public OutputFormat getOutputFormat() {
-        return outputFormat != null ? outputFormat : wrappedPCfg.getOutputFormat();
-    }
-
-    @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();
-    }
-
-    @Override
-    public int getAutoEscapingPolicy() {
-        return autoEscapingPolicy != null ? autoEscapingPolicy : wrappedPCfg.getAutoEscapingPolicy();
-    }
-
-    @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 Charset getSourceEncoding() {
-        return wrappedPCfg.getSourceEncoding();
-    }
-
-    @Override
-    public boolean isSourceEncodingSet() {
-        return wrappedPCfg.isSourceEncodingSet();
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
index ac6cb71..8fab61f 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 
 /**
@@ -63,14 +62,4 @@ public class ConditionalTemplateConfigurationFactory extends TemplateConfigurati
         }
     }
 
-    @Override
-    protected void setConfigurationOfChildren(Configuration cfg) {
-        if (templateConfiguration != null) {
-            templateConfiguration.setParentConfiguration(cfg);
-        }
-        if (templateConfigurationFactory != null) {
-            templateConfigurationFactory.setConfiguration(cfg);
-        }
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
index 9b3b665..0f09d3d 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.util._StringUtil;
 
@@ -108,11 +107,4 @@ public class FirstMatchTemplateConfigurationFactory extends TemplateConfiguratio
         return this;
     }
 
-    @Override
-    protected void setConfigurationOfChildren(Configuration cfg) {
-        for (TemplateConfigurationFactory templateConfigurationFactory : templateConfigurationFactories) {
-            templateConfigurationFactory.setConfiguration(cfg);
-        }
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
index 5e9c37b..9b3106f 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateConfiguration;
 
 /**
@@ -58,27 +57,7 @@ public class MergingTemplateConfigurationFactory extends TemplateConfigurationFa
             }
         }
 
-        if (mergedTCBuilder == null) {
-            return firstResultTC; // Maybe null
-        } else {
-            TemplateConfiguration mergedTC = mergedTCBuilder.build();
-
-            Configuration cfg = getConfiguration();
-            if (cfg == null) {
-                throw new IllegalStateException(
-                        "The TemplateConfigurationFactory wasn't associated to a Configuration yet.");
-            }
-            mergedTC.setParentConfiguration(cfg);
-
-            return mergedTC;
-        }
-    }
-    
-    @Override
-    protected void setConfigurationOfChildren(Configuration cfg) {
-        for (TemplateConfigurationFactory templateConfigurationFactory : templateConfigurationFactories) {
-            templateConfigurationFactory.setConfiguration(cfg);
-        }
+        return mergedTCBuilder == null ? firstResultTC /* Maybe null */ : mergedTCBuilder.build();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
index 5b0e0cd..fe9255d 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
@@ -20,7 +20,6 @@ package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
 
-import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateConfiguration;
 
@@ -30,8 +29,6 @@ import org.apache.freemarker.core.TemplateConfiguration;
  * @since 2.3.24
  */
 public abstract class TemplateConfigurationFactory {
-    
-    private Configuration cfg;
 
     /**
      * Returns (maybe creates) the {@link TemplateConfiguration} for the given template source.
@@ -53,38 +50,5 @@ public abstract class TemplateConfigurationFactory {
      */
     public abstract TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
             throws IOException, TemplateConfigurationFactoryException;
-    
-    /**
-     * Binds this {@link TemplateConfigurationFactory} to a {@link Configuration}. Once it's bound, it can't be bound to
-     * another {@link Configuration} any more. This is automatically called by
-     * {@link Configuration#setTemplateConfigurations(TemplateConfigurationFactory)}.
-     */
-    public final void setConfiguration(Configuration cfg) {
-        if (this.cfg != null) {
-            if (cfg != this.cfg) {
-                throw new IllegalStateException(
-                        "The TemplateConfigurationFactory is already bound to another Configuration");
-            }
-        } else {
-            this.cfg = cfg;
-            setConfigurationOfChildren(cfg);
-        }
-    }
-    
-    /**
-     * Returns the configuration this object belongs to, or {@code null} if it isn't yet bound to a
-     * {@link Configuration}.
-     */
-    public Configuration getConfiguration() {
-        return cfg;
-    }
-    
-    /**
-     * Calls {@link TemplateConfiguration#setParentConfiguration(Configuration)} on each enclosed
-     * {@link TemplateConfiguration} and {@link TemplateConfigurationFactory#setConfiguration(Configuration)}
-     * on each enclosed {@link TemplateConfigurationFactory} objects. It only supposed to call these on the direct
-     * "children" of this object, not on the children of the children.
-     */
-    protected abstract void setConfigurationOfChildren(Configuration cfg);
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
index 857b15d..2253384 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
@@ -198,10 +198,7 @@ public final class TemplateLoadingResult {
      * merged with them, with properties coming from the returned {@link TemplateConfiguration} having the highest
      * priority.
      * 
-     * @return {@code null}, or a {@link TemplateConfiguration}. The parent configuration of the
-     *         {@link TemplateConfiguration} need not be set. The returned {@link TemplateConfiguration} won't be
-     *         modified. (If the caller needs to modify it, such as to call
-     *         {@link TemplateConfiguration#setParentConfiguration(Configuration)}, it has to copy it first.)
+     * @return {@code null}, or a {@link TemplateConfiguration}.
      */
     public TemplateConfiguration getTemplateConfiguration() {
         return templateConfiguration;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 e06eba1..48650e8 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
@@ -532,16 +532,15 @@ public class DefaultTemplateResolver extends TemplateResolver {
             } catch (TemplateConfigurationFactoryException e) {
                 throw newIOException("Error while getting TemplateConfiguration; see cause exception.", e);
             }
-            TemplateConfiguration resultTC = templateLoaderResult.getTemplateConfiguration();
-            if (resultTC != null) {
+            TemplateConfiguration templateLoaderResultTC = templateLoaderResult.getTemplateConfiguration();
+            if (templateLoaderResultTC != null) {
                 TemplateConfiguration.Builder mergedTCBuilder = new TemplateConfiguration.Builder();
                 if (cfgTC != null) {
                     mergedTCBuilder.merge(cfgTC);
                 }
-                mergedTCBuilder.merge(resultTC);
+                mergedTCBuilder.merge(templateLoaderResultTC);
 
                 tc = mergedTCBuilder.build();
-                tc.setParentConfiguration(config);
             } else {
                 tc = cfgTC;
             }
@@ -550,8 +549,10 @@ public class DefaultTemplateResolver extends TemplateResolver {
         if (tc != null && tc.isLocaleSet()) {
             locale = tc.getLocale();
         }
-        Charset initialEncoding = tc != null ? tc.getSourceEncoding() : config.getSourceEncoding();
-        TemplateLanguage templateLanguage = tc != null ? tc.getTemplateLanguage() : config .getTemplateLanguage();
+        Charset initialEncoding = tc != null && tc.isSourceEncodingSet() ? tc.getSourceEncoding()
+                : config.getSourceEncoding();
+        TemplateLanguage templateLanguage = tc != null && tc.isTemplateLanguageSet() ? tc.getTemplateLanguage()
+                : config.getTemplateLanguage();
 
         Template template;
         {
@@ -614,11 +615,7 @@ public class DefaultTemplateResolver extends TemplateResolver {
             }
         }
 
-        if (tc != null) {
-            tc.apply(template);
-        }
-        
-        template.setLocale(locale);
+        template.setLookupLocale(locale);
         template.setCustomLookupCondition(customLookupCondition);
         return template;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a7bbc2a..661cb5d 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -95,8 +95,12 @@ public class FMParser {
     private LinkedList escapes = new LinkedList();
     private int mixedContentNesting; // for stripText
 
-    FMParser(Template template, Reader reader, ParserConfiguration pCfg, InputStream streamToUnmarkWhenEncEstabd) {
-        this(template, true, readerToTokenManager(reader, pCfg), pCfg, streamToUnmarkWhenEncEstabd);
+    FMParser(Template template, Reader reader,
+            ParserConfiguration pCfg, OutputFormat outputFormat, Integer autoEscapingPolicy,
+            InputStream streamToUnmarkWhenEncEstabd) {
+        this(template, true, readerToTokenManager(reader, pCfg),
+                pCfg, outputFormat, autoEscapingPolicy,
+                streamToUnmarkWhenEncEstabd);
     }
 
     private static FMParserTokenManager readerToTokenManager(Reader reader, ParserConfiguration pCfg) {
@@ -105,7 +109,8 @@ public class FMParser {
         return new FMParserTokenManager(simpleCharStream);
     }
 
-    FMParser(Template template, boolean newTemplate, FMParserTokenManager tkMan, ParserConfiguration pCfg,
+    FMParser(Template template, boolean newTemplate, FMParserTokenManager tkMan,
+            ParserConfiguration pCfg, OutputFormat contextOutputFormat, Integer contextAutoEscapingPolicy,
     		InputStream streamToUnmarkWhenEncEstabd) {
         this(tkMan);
 
@@ -122,16 +127,14 @@ public class FMParser {
         this.incompatibleImprovements = incompatibleImprovements;
 
         {
-            OutputFormat outputFormatFromExt;
-            if (!pCfg.getRecognizeStandardFileExtensions()
-                    || (outputFormatFromExt = getFormatFromStdFileExt()) == null) {
-                autoEscapingPolicy = pCfg.getAutoEscapingPolicy();
-                outputFormat = pCfg.getOutputFormat();
-            } else {
-                // Override it
-                autoEscapingPolicy = Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY;
-                outputFormat = outputFormatFromExt;
-            }
+            OutputFormat outputFormatFromExt = pCfg.getRecognizeStandardFileExtensions() ? getFormatFromStdFileExt()
+                    : null;
+            outputFormat = contextOutputFormat != null ? contextOutputFormat
+                    : outputFormatFromExt != null ? outputFormatFromExt
+                    : pCfg.getOutputFormat();
+            autoEscapingPolicy = contextAutoEscapingPolicy != null ? contextAutoEscapingPolicy
+                    : outputFormatFromExt != null ? Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY
+                    : pCfg.getAutoEscapingPolicy();
         }
         recalculateAutoEscapingField();
 
@@ -169,7 +172,7 @@ public class FMParser {
         // If this is a Template under construction, we do the below.
         // If this is just the enclosing Template for ?eval or such, we must not modify it.
         if (newTemplate) {
-            template.setAutoEscaping(autoEscaping);
+            template.setAutoEscapingPolicy(autoEscapingPolicy);
             template.setOutputFormat(outputFormat);
         }
     }
@@ -192,7 +195,7 @@ public class FMParser {
     private OutputFormat getFormatFromStdFileExt() {
         String sourceName = template.getSourceName();
         if (sourceName == null) {
-            return null; // Not possible anyway...
+            return null;
         }
 
         int ln = sourceName.length();
@@ -3925,7 +3928,8 @@ void HeaderElement() :
                                 autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
                             }
                             recalculateAutoEscapingField();
-                            template.setAutoEscaping(autoEscaping);
+
+                            template.setAutoEscapingPolicy(autoEscapingPolicy);
                         } else if (ks.equalsIgnoreCase("output_format") || ks.equals("outputFormat")) {
                             if (vs == null) {
                                 throw new ParseException("Expected a string constant for \"" + ks + "\".", exp);
@@ -3937,9 +3941,9 @@ void HeaderElement() :
 					        } catch (UnregisteredOutputFormatException e) {
 					            throw new ParseException(e.getMessage(), exp, e.getCause());
 					        }
-                            recalculateAutoEscapingField();                                
+                            recalculateAutoEscapingField();
+
                             template.setOutputFormat(outputFormat);
-                            template.setAutoEscaping(autoEscaping);
                         } else if (ks.equalsIgnoreCase("ns_prefixes") || ks.equals("nsPrefixes")) {
                             if (!(value instanceof TemplateHashModelEx)) {
                                 throw new ParseException("Expecting a hash of prefixes to namespace URI's.", exp);
@@ -3972,7 +3976,13 @@ void HeaderElement() :
                                 for (TemplateModelIterator it = keys.iterator(); it.hasNext();) {
                                         String attName = ((TemplateScalarModel) it.next()).getAsString();
                                         Object attValue = DeepUnwrap.unwrap(attributeMap.get(attName));
-                                        template.setCustomAttribute(attName, attValue);
+                                        if (attValue != null && !(attValue instanceof Serializable)) {
+                                            throw new ParseException(
+                                                    "Value of attribute " + _StringUtil.jQuote(attName)
+                                                    + " should implement java.io.Serializable.",
+                                                    exp);
+                                        }
+                                        template.setCustomAttribute(attName, (Serializable) attValue);
                                 }
                             } catch (TemplateModelException tme) {
                             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/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 1847aba..ba0267b 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -188,6 +188,8 @@ the FreeMarer 3 changelog here:
     TemplateConfiguration.encoding and Template.encoding to sourceEncoding. (Before this, defaultEncoding was exclusive
     to Configuration, but now it's like any other ParserConfiguration setting that can be overidden on the 3 levels.)
   - Made TemplateConfiguration immutable, added a TemplateConfiguration.Builder.
+  - Made Template immutable (via public API-s). Template-specific settings now can only come from the TemplateConfiguration associated
+    to the template, or from the #ftl header for some settings (most notably for custom attributes).
 - Settings that have contained a charset name (sourceEncoding, outputEncoding, URLEscapingCharset) are now of type Charset,
   not String. For string based configuration sources (such as .properties files) this means that:
   - Unrecognized charset names are now errors
@@ -200,3 +202,5 @@ the FreeMarer 3 changelog here:
   equivalent of the sourceEncoding ParserConfiguration setting. This is in line with Template.actualTagSyntax and the
   other "actual" properties. (Just as in FM2, Template.getParserConfiguration() still can be used get the
   sourceEncoding used during parsing.)
+- Made TemplateModel classes used by the parser for literals Serializable. (Without this attribute values set in the #ftl
+  header wouldn't be always Serializable, which in turn will sabotage making Template-s Serializable in the future.)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
index 9b643b9..bff5dc0 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
@@ -161,7 +161,7 @@ public class ConfigurableTest {
     }
     
     private MutableProcessingConfiguration createConfigurable() throws IOException {
-        return new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
+        return new TemplateConfiguration.Builder();
     }
 
     private boolean keyFieldExists(String name) throws Exception {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index f24c63e..b5c382f 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -1209,67 +1209,121 @@ public class ConfigurationTest extends TestCase {
     
     @Test
     public void testHasCustomFormats() throws IOException, TemplateException {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        Environment env = t.createProcessingEnvironment(null, null);
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
-        env.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertFalse(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        t.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertFalse(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        
-        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
-        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        
-        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            Template t = new Template(null, "", cfg, null);
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+            env.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertTrue(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
         // Same with number formats:
-        
-        env.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertFalse(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        t.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertFalse(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
-        
-        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        t.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        env.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        assertFalse(cfg.hasCustomFormats());
-        assertFalse(t.hasCustomFormats());
-        assertFalse(env.hasCustomFormats());
-        
-        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
-        assertTrue(cfg.hasCustomFormats());
-        assertTrue(t.hasCustomFormats());
-        assertTrue(env.hasCustomFormats());
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            Template t = new Template(null, "", cfg, null);
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertFalse(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+            env.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+
+            assertFalse(cfg.hasCustomFormats());
+            assertFalse(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            tcb.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertFalse(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
+
+        {
+            Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+            cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            Template t = new Template(null, "", cfg, tcb.build());
+            Environment env = t.createProcessingEnvironment(null, null);
+
+            assertTrue(cfg.hasCustomFormats());
+            assertTrue(t.hasCustomFormats());
+            assertTrue(env.hasCustomFormats());
+        }
     }
     
     public void testNamingConventionSetSetting() throws ConfigurationException {
@@ -1379,11 +1433,13 @@ public class ConfigurationTest extends TestCase {
         Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
         for (boolean camelCase : new boolean[] { false, true }) {
             List<String> names = new ArrayList<>(cfg.getSettingNames(camelCase));
-            List<String> cfgableNames = new ArrayList<>(new Template(null, "", cfg).getSettingNames(camelCase));
-            assertStartsWith(names, cfgableNames);
+            List<String> procCfgNames = new ArrayList<>(new Template(null, "", cfg)
+                    .createProcessingEnvironment(null, _NullWriter.INSTANCE)
+                    .getSettingNames(camelCase));
+            assertStartsWith(names, procCfgNames);
             
             String prevName = null;
-            for (int i = cfgableNames.size(); i < names.size(); i++) {
+            for (int i = procCfgNames.size(); i < names.size(); i++) {
                 String name = names.get(i);
                 if (prevName != null) {
                     assertThat(name, greaterThan(prevName));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
index cf336bd..00771cd 100644
--- a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
+++ b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
@@ -28,6 +28,7 @@ import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 
 @SuppressWarnings("boxing")
 public class CustomAttributeTest {
@@ -35,10 +36,12 @@ public class CustomAttributeTest {
     private static final String KEY_1 = "key1";
     private static final String KEY_2 = "key2";
     private static final String KEY_3 = "key3";
-    
-    private static final Object VALUE_1 = new Object();
+    private static final Integer KEY_4 = 4;
+
+    private static final Integer VALUE_1 = 1; // Serializable
     private static final Object VALUE_2 = new Object();
     private static final Object VALUE_3 = new Object();
+    private static final Object VALUE_4 = new Object();
     private static final Object VALUE_LIST = ImmutableList.<Object>of(
             "s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A"));
     private static final Object VALUE_BIGDECIMAL = BigDecimal.valueOf(22);
@@ -47,120 +50,114 @@ public class CustomAttributeTest {
 
     @Test
     public void testStringKey() throws Exception {
-        Template t = new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
-        assertEquals(0, t.getCustomAttributeNames().length);        
-        assertNull(t.getCustomAttribute(KEY_1));
-        
-        t.setCustomAttribute(KEY_1, VALUE_1);
-        assertArrayEquals(new String[] { KEY_1 }, t.getCustomAttributeNames());        
-        assertSame(VALUE_1, t.getCustomAttribute(KEY_1));
-        
-        t.setCustomAttribute(KEY_2, VALUE_2);
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));        
-        assertSame(VALUE_1, t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
-        
-        t.setCustomAttribute(KEY_1, VALUE_2);
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));        
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
+        // Need some MutableProcessingConfiguration:
+        TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder();
+
+        assertEquals(0, mpc.getCustomAttributeNames().length);
+        assertNull(mpc.getCustomAttribute(KEY_1));
         
-        t.setCustomAttribute(KEY_1, null);
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));        
-        assertNull(t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
+        mpc.setCustomAttribute(KEY_1, VALUE_1);
+        assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames());
+        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
         
-        t.removeCustomAttribute(KEY_1);
-        assertArrayEquals(new String[] { KEY_2 }, t.getCustomAttributeNames());        
-        assertNull(t.getCustomAttribute(KEY_1));
-        assertSame(VALUE_2, t.getCustomAttribute(KEY_2));
+        mpc.setCustomAttribute(KEY_2, VALUE_2);
+        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames()));
+        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
+
+        mpc.setCustomAttribute(KEY_1, VALUE_2);
+        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames()));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
+
+        mpc.setCustomAttribute(KEY_1, null);
+        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames()));
+        assertNull(mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
+
+        mpc.removeCustomAttribute(KEY_1);
+        assertArrayEquals(new String[] { KEY_2 }, mpc.getCustomAttributeNames());
+        assertNull(mpc.getCustomAttribute(KEY_1));
+        assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2));
     }
 
     @Test
     public void testRemoveFromEmptySet() throws Exception {
-        Template t = new Template(null, "", new Configuration(Configuration.VERSION_3_0_0));
-        t.removeCustomAttribute(KEY_1);
-        assertEquals(0, t.getCustomAttributeNames().length);        
-        assertNull(t.getCustomAttribute(KEY_1));
-        
-        t.setCustomAttribute(KEY_1, VALUE_1);
-        assertArrayEquals(new String[] { KEY_1 }, t.getCustomAttributeNames());        
-        assertSame(VALUE_1, t.getCustomAttribute(KEY_1));
+        // Need some MutableProcessingConfiguration:
+        TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder();
+
+        mpc.removeCustomAttribute(KEY_1);
+        assertEquals(0, mpc.getCustomAttributeNames().length);
+        assertNull(mpc.getCustomAttribute(KEY_1));
+
+        mpc.setCustomAttribute(KEY_1, VALUE_1);
+        assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames());
+        assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1));
     }
-    
+
     @Test
-    public void testFtlHeader() throws Exception {
+    public void testAttrsFromFtlHeaderOnly() throws Exception {
         Template t = new Template(null, "<#ftl attributes={"
                 + "'" + KEY_1 + "': [ 's', 2, true, {  'a': 'A' } ], "
                 + "'" + KEY_2 + "': " + VALUE_BIGDECIMAL + " "
                 + "}>",
                 new Configuration(Configuration.VERSION_3_0_0));
-        
-        assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(t.getCustomAttributeNames()));
+
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet());
         assertEquals(VALUE_LIST, t.getCustomAttribute(KEY_1));
         assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2));
-        
+
         t.setCustomAttribute(KEY_1, VALUE_1);
         assertEquals(VALUE_1, t.getCustomAttribute(KEY_1));
         assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2));
+
+        t.setCustomAttribute(KEY_1, null);
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet());
+        assertNull(t.getCustomAttribute(KEY_1));
     }
-    
+
     @Test
-    public void testFtl2Header() throws Exception {
+    public void testAttrsFromFtlHeaderAndFromTemplateConfiguration() throws Exception {
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomAttribute(KEY_3, VALUE_3);
+        tcb.setCustomAttribute(KEY_4, VALUE_4);
         Template t = new Template(null, "<#ftl attributes={"
                 + "'" + KEY_1 + "': 'a', "
                 + "'" + KEY_2 + "': 'b', "
                 + "'" + KEY_3 + "': 'c' "
                 + "}>",
-                new Configuration(Configuration.VERSION_3_0_0));
-        
-        assertArrayEquals(new String[] { KEY_1, KEY_2, KEY_3 }, sort(t.getCustomAttributeNames()));
+                new Configuration(Configuration.VERSION_3_0_0),
+                tcb.build());
+
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet());
         assertEquals("a", t.getCustomAttribute(KEY_1));
         assertEquals("b", t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
-        
-        t.removeCustomAttribute(KEY_2);
-        assertArrayEquals(new String[] { KEY_1, KEY_3 }, sort(t.getCustomAttributeNames()));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertNull(t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
+        assertEquals("c", t.getCustomAttribute(KEY_3)); // Has overridden TC attribute
+        assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); // Inherited TC attribute
+
+        t.setCustomAttribute(KEY_3, null);
+        assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet());
+        assertNull("null value shouldn't cause fallback to TC attribute", t.getCustomAttribute(KEY_3));
     }
 
+
     @Test
-    public void testFtl3Header() throws Exception {
-        Template t = new Template(null, "<#ftl attributes={"
-                + "'" + KEY_1 + "': 'a', "
-                + "'" + KEY_2 + "': 'b', "
-                + "'" + KEY_3 + "': 'c' "
-                + "}>",
-                new Configuration(Configuration.VERSION_3_0_0));
-        
-        assertArrayEquals(new String[] { KEY_1, KEY_2, KEY_3 }, sort(t.getCustomAttributeNames()));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertEquals("b", t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
-        
-        t.setCustomAttribute(KEY_2, null);
-        assertArrayEquals(new String[] { KEY_1, KEY_2, KEY_3 }, sort(t.getCustomAttributeNames()));
-        assertEquals("a", t.getCustomAttribute(KEY_1));
-        assertNull(t.getCustomAttribute(KEY_2));
-        assertEquals("c", t.getCustomAttribute(KEY_3));
+    public void testAttrsFromTemplateConfigurationOnly() throws Exception {
+        TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+        tcb.setCustomAttribute(KEY_3, VALUE_3);
+        tcb.setCustomAttribute(KEY_4, VALUE_4);
+        Template t = new Template(null, "",
+                new Configuration(Configuration.VERSION_3_0_0),
+                tcb.build());
+
+        assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomAttributes().keySet());
+        assertEquals(VALUE_3, t.getCustomAttribute(KEY_3));
+        assertEquals(VALUE_4, t.getCustomAttribute(KEY_4));
     }
-    
+
     private Object[] sort(String[] customAttributeNames) {
         Arrays.sort(customAttributeNames);
         return customAttributeNames;
     }
 
-    @Test
-    public void testObjectKey() throws Exception {
-        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-        Template t = new Template(null, "", cfg);
-        assertNull(t.getCustomAttribute(CUST_ATT_KEY));
-        cfg.setCustomAttribute(CUST_ATT_KEY, "cfg");
-        assertEquals("cfg", t.getCustomAttribute(CUST_ATT_KEY));
-        t.setCustomAttribute(CUST_ATT_KEY, "t");
-        assertEquals("t", t.getCustomAttribute(CUST_ATT_KEY));
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0356b30b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
index baee63b..7d37d95 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -282,21 +282,28 @@ public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
         dropConfiguration();
         Configuration cfg = getConfiguration();
         cfg.addAutoImport("t1", "t1.ftl");
-        Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg);
 
+        TemplateConfiguration tc;
+        if (layer == Template.class) {
+            TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+            setLazynessOfConfigurable(tcb, lazyImports, lazyAutoImports, setLazyAutoImports);
+            tc = tcb.build();
+        } else {
+            tc = null;
+        }
+
+        Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg, tc);
         StringWriter sw = new StringWriter();
         Environment env = t.createProcessingEnvironment(null, sw);
-        
+
         if (layer == Configuration.class) {
             setLazynessOfConfigurable(cfg, lazyImports, lazyAutoImports, setLazyAutoImports);
-        } else if (layer == Template.class) {
-            setLazynessOfConfigurable(t, lazyImports, lazyAutoImports, setLazyAutoImports);
         } else if (layer == Environment.class) {
             setLazynessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports);
-        } else {
+        } else if (layer != Template.class) {
             throw new IllegalArgumentException();
         }
-        
+
         env.process();
         assertEquals(expectedOutput, sw.toString());
     }



Mime
View raw message