http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d61a45d/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java 
b/src/main/java/org/apache/freemarker/core/Configuration.java
index fa5d5d4..df15c0f 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -19,18 +19,16 @@
 
 package org.apache.freemarker.core;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Serializable;
 import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -44,14 +42,11 @@ import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.outputformat.OutputFormat;
@@ -74,17 +69,15 @@ import 
org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
-import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateResolver;
 import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
 import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
 import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
-import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.MruCacheStorage;
-import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
-import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CaptureOutput;
+import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util.HtmlEscape;
 import org.apache.freemarker.core.util.NormalizeNewlines;
 import org.apache.freemarker.core.util.StandardCompress;
@@ -97,27 +90,35 @@ import 
org.apache.freemarker.core.util._UnmodifiableCompositeSet;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
 /**
  * <b>The main entry point into the FreeMarker API</b>; encapsulates the 
configuration settings of FreeMarker,
  * also serves as a central template-loading and caching service.
  *
- * <p>This class is meant to be used in a singleton pattern. That is, you 
invoke an instance of this at the beginning of
- * the application life-cycle, set its {@link #setSetting(String, String) 
configuration settings} there (either with the
- * setter methods like {@link #setTemplateLoader(TemplateLoader)} or by 
loading a {@code .properties} file), and then
+ * <p>This class is meant to be used in a singleton pattern. That is, you 
create an instance of this at the beginning of
+ * the application life-cycle with {@link Configuration.Builder}, set its 
settings
+ * (either
+ * with
+ * the
+ * setter methods like {@link 
Configuration.Builder#setTemplateLoader(TemplateLoader)} or by loading a
+ * {@code .properties} file and use that with {@link 
Configuration.Builder#setSettings(Properties)}}), and
+ * then
  * use that single instance everywhere in your application. Frequently 
re-creating {@link Configuration} is a typical
- * and grave mistake from performance standpoint, as the {@link Configuration} 
holds the template templateResolver, and often also
- * the class introspection templateResolver, which then will be lost. (Note 
that, naturally, having multiple long-lived instances,
+ * and grave mistake from performance standpoint, as the {@link Configuration} 
holds the template cache, and often also
+ * the class introspection cache, which then will be lost. (Note that, 
naturally, having multiple long-lived instances,
  * like one per component that internally uses FreeMarker is fine.)  
  * 
  * <p>The basic usage pattern is like:
  * 
  * <pre>
  *  // Where the application is initialized; in general you do this ONLY ONCE 
in the application life-cycle!
- *  Configuration cfg = new Configuration(VERSION_<i>X</i>_<i>Y</i>_<i>Z</i>));
- *  // Where X, Y, Z enables the not-100%-backward-compatible fixes introduced 
in
- *  // FreeMarker version X.Y.Z  and earlier (see {@link 
#Configuration(Version)}).
- *  cfg.set<i>SomeSetting</i>(...);
- *  cfg.set<i>OtherSetting</i>(...);
+ *  Configuration cfg = new 
Configuration.Builder(VERSION_<i>X</i>_<i>Y</i>_<i>Z</i>));
+ *          .<i>someSetting</i>(...)
+ *          .<i>otherSetting</i>(...)
+ *          .build()
+ *  // VERSION_<i>X</i>_<i>Y</i>_<i>Z</i> enables the 
not-100%-backward-compatible fixes introduced in
+ *  // FreeMarker version X.Y.Z and earlier (see {@link 
Configuration#getIncompatibleImprovements()}).
  *  ...
  *  
  *  // Later, whenever the application needs a template (so you may do this a 
lot, and from multiple threads):
@@ -126,205 +127,66 @@ import 
org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
  * 
  * <p>A couple of settings that you should not leave on its default value are:
  * <ul>
- *   <li>{@link #setTemplateLoader(TemplateLoader) template_loader}: The 
default value is deprecated and in fact quite
- *       useless. (For the most common cases you can use the convenience 
methods,
- *       {@link #setDirectoryForTemplateLoading(File)} and {@link 
#setClassForTemplateLoading(Class, String)} and
- *       {@link #setClassLoaderForTemplateLoading(ClassLoader, String)} too.)
- *   <li>{@link #setSourceEncoding(Charset) sourceEncoding}: The default value 
is system dependent, which makes it
+ *   <li>{@link #getTemplateLoader templateLoader}: The default value is 
{@code null}, so you won't be able to load
+ *       anything.
+ *   <li>{@link #getSourceEncoding sourceEncoding}: The default value is 
system dependent, which makes it
  *       fragile on servers, so it should be set explicitly, like to "UTF-8" 
nowadays. 
- *   <li>{@link #setTemplateExceptionHandler(TemplateExceptionHandler) 
template_exception_handler}: For developing
+ *   <li>{@link #getTemplateExceptionHandler() templateExceptionHandler}: For 
developing
  *       HTML pages, the most convenient value is {@link 
TemplateExceptionHandler#HTML_DEBUG_HANDLER}. For production,
  *       {@link TemplateExceptionHandler#RETHROW_HANDLER} is safer to use.
- *   <!-- 2.4: recommend the new object wrapper here -->
  * </ul>
  * 
- * <p>A {@link Configuration} object is thread-safe only after you have 
stopped modifying the configuration settings,
- * and you have <b>safely published</b> it (see JSR 133 and related 
literature) to other threads. Generally, you set
- * everything directly after you have instantiated the {@link Configuration} 
object, then you don't change the settings
- * anymore, so then it's safe to make it accessible (again, via a "safe 
publication" technique) from multiple threads.
- * The methods that aren't for modifying settings, like {@link 
#getTemplate(String)}, are thread-safe.
+ * <p>{@link Configuration} is thread-safe and (as of 3.0.0) immutable (apart 
from internal caches).
  */
-public final class Configuration extends 
MutableProcessingConfiguration<Configuration>
-        implements Cloneable, ParsingAndProcessingConfiguration, 
CustomStateScope {
+public final class Configuration
+        implements TopLevelConfiguration, CustomStateScope {
     
     private static final String VERSION_PROPERTIES_PATH = 
"org/apache/freemarker/core/version.properties";
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String SOURCE_ENCODING_KEY_SNAKE_CASE = 
"source_encoding";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String SOURCE_ENCODING_KEY_CAMEL_CASE = 
"sourceEncoding";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String SOURCE_ENCODING_KEY = 
SOURCE_ENCODING_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String LOCALIZED_LOOKUP_KEY_SNAKE_CASE = 
"localized_lookup";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String LOCALIZED_LOOKUP_KEY_CAMEL_CASE = 
"localizedLookup";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String LOCALIZED_LOOKUP_KEY = 
LOCALIZED_LOOKUP_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String WHITESPACE_STRIPPING_KEY_SNAKE_CASE = 
"whitespace_stripping";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String WHITESPACE_STRIPPING_KEY_CAMEL_CASE = 
"whitespaceStripping";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String WHITESPACE_STRIPPING_KEY = 
WHITESPACE_STRIPPING_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.24 */
-    public static final String OUTPUT_FORMAT_KEY_SNAKE_CASE = "output_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.24 */
-    public static final String OUTPUT_FORMAT_KEY_CAMEL_CASE = "outputFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String OUTPUT_FORMAT_KEY = 
OUTPUT_FORMAT_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.24 */
-    public static final String 
RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE = 
"recognize_standard_file_extensions";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.24 */
-    public static final String 
RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE = 
"recognizeStandardFileExtensions";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY
-            = RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.24 */
-    public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE 
= "registered_custom_output_formats";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.24 */
-    public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE 
= "registeredCustomOutputFormats";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY = 
REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.24 */
-    public static final String AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE = 
"auto_escaping_policy";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.24 */
-    public static final String AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE = 
"autoEscapingPolicy";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String AUTO_ESCAPING_POLICY_KEY = 
AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String CACHE_STORAGE_KEY_SNAKE_CASE = "cache_storage";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String CACHE_STORAGE_KEY_CAMEL_CASE = "cacheStorage";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String CACHE_STORAGE_KEY = 
CACHE_STORAGE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE = 
"template_update_delay";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE = 
"templateUpdateDelay";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String TEMPLATE_UPDATE_DELAY_KEY = 
TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String AUTO_INCLUDE_KEY_SNAKE_CASE = "auto_include";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String AUTO_INCLUDE_KEY_CAMEL_CASE = "autoInclude";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String AUTO_INCLUDE_KEY = AUTO_INCLUDE_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_LANGUAGE_KEY_SNAKE_CASE = 
"template_language";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_LANGUAGE_KEY_CAMEL_CASE = 
"templateLanguage";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String TEMPLATE_LANGUAGE_KEY = 
TEMPLATE_LANGUAGE_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TAG_SYNTAX_KEY_SNAKE_CASE = "tag_syntax";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TAG_SYNTAX_KEY_CAMEL_CASE = "tagSyntax";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String TAG_SYNTAX_KEY = TAG_SYNTAX_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String NAMING_CONVENTION_KEY_SNAKE_CASE = 
"naming_convention";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String NAMING_CONVENTION_KEY_CAMEL_CASE = 
"namingConvention";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String NAMING_CONVENTION_KEY = 
NAMING_CONVENTION_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.25 */
-    public static final String TAB_SIZE_KEY_SNAKE_CASE = "tab_size";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.25 */
-    public static final String TAB_SIZE_KEY_CAMEL_CASE = "tabSize";
-    /** Alias to the {@code ..._SNAKE_CASE} variation. @since 2.3.25 */
-    public static final String TAB_SIZE_KEY = TAB_SIZE_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_LOADER_KEY_SNAKE_CASE = 
"template_loader";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_LOADER_KEY_CAMEL_CASE = 
"templateLoader";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String TEMPLATE_LOADER_KEY = 
TEMPLATE_LOADER_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE = 
"template_lookup_strategy";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE = 
"templateLookupStrategy";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String TEMPLATE_LOOKUP_STRATEGY_KEY = 
TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE = 
"template_name_format";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE = 
"templateNameFormat";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String TEMPLATE_NAME_FORMAT_KEY = 
TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE;
-
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.24 */
-    public static final String TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE = 
"template_configurations";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.24 */
-    public static final String TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE = 
"templateConfigurations";
-    /** Alias to the {@code ..._SNAKE_CASE} variation. @since 2.3.24 */
-    public static final String TEMPLATE_CONFIGURATIONS_KEY = 
TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE;
-    
-    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
-    public static final String INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE = 
"incompatible_improvements";
-    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
-    public static final String INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE = 
"incompatibleImprovements";
-    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
-    public static final String INCOMPATIBLE_IMPROVEMENTS_KEY = 
INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE;
-    
+
     private static final String[] SETTING_NAMES_SNAKE_CASE = new String[] {
         // Must be sorted alphabetically!
-        AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE,
-        CACHE_STORAGE_KEY_SNAKE_CASE,
-        INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
-        LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
-        NAMING_CONVENTION_KEY_SNAKE_CASE,
-        OUTPUT_FORMAT_KEY_SNAKE_CASE,
-        RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE,
-        REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE,
-        SOURCE_ENCODING_KEY_SNAKE_CASE,
-        TAB_SIZE_KEY_SNAKE_CASE,
-        TAG_SYNTAX_KEY_SNAKE_CASE,
-        TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE,
-        TEMPLATE_LANGUAGE_KEY_SNAKE_CASE,
-        TEMPLATE_LOADER_KEY_SNAKE_CASE,
-        TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE,
-        TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE,
-        TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE,
-        WHITESPACE_STRIPPING_KEY_SNAKE_CASE,
+        ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE,
+        ExtendableBuilder.CACHE_STORAGE_KEY_SNAKE_CASE,
+        ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
+        ExtendableBuilder.LOCALIZED_LOOKUP_KEY_SNAKE_CASE,
+        ExtendableBuilder.NAMING_CONVENTION_KEY_SNAKE_CASE,
+        ExtendableBuilder.OUTPUT_FORMAT_KEY_SNAKE_CASE,
+        ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE,
+        ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE,
+        ExtendableBuilder.SHARED_VARIABLES_KEY_SNAKE_CASE,
+        ExtendableBuilder.SOURCE_ENCODING_KEY_SNAKE_CASE,
+        ExtendableBuilder.TAB_SIZE_KEY_SNAKE_CASE,
+        ExtendableBuilder.TAG_SYNTAX_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_LANGUAGE_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_LOADER_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE,
+        ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE,
+        ExtendableBuilder.WHITESPACE_STRIPPING_KEY_SNAKE_CASE,
     };
 
     private static final String[] SETTING_NAMES_CAMEL_CASE = new String[] {
         // Must be sorted alphabetically!
-        AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE,
-        CACHE_STORAGE_KEY_CAMEL_CASE,
-        INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
-        LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
-        NAMING_CONVENTION_KEY_CAMEL_CASE,
-        OUTPUT_FORMAT_KEY_CAMEL_CASE,
-        RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE,
-        REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
-        SOURCE_ENCODING_KEY_CAMEL_CASE,
-        TAB_SIZE_KEY_CAMEL_CASE,
-        TAG_SYNTAX_KEY_CAMEL_CASE,
-        TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE,
-        TEMPLATE_LANGUAGE_KEY_CAMEL_CASE,
-        TEMPLATE_LOADER_KEY_CAMEL_CASE,
-        TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE,
-        TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE,
-        TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE,
-        WHITESPACE_STRIPPING_KEY_CAMEL_CASE
+        ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE,
+        ExtendableBuilder.CACHE_STORAGE_KEY_CAMEL_CASE,
+        ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
+        ExtendableBuilder.LOCALIZED_LOOKUP_KEY_CAMEL_CASE,
+        ExtendableBuilder.NAMING_CONVENTION_KEY_CAMEL_CASE,
+        ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE,
+        ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE,
+        ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
+        ExtendableBuilder.SHARED_VARIABLES_KEY_CAMEL_CASE,
+        ExtendableBuilder.SOURCE_ENCODING_KEY_CAMEL_CASE,
+        ExtendableBuilder.TAB_SIZE_KEY_CAMEL_CASE,
+        ExtendableBuilder.TAG_SYNTAX_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_LANGUAGE_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_LOADER_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE,
+        ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE,
+        ExtendableBuilder.WHITESPACE_STRIPPING_KEY_CAMEL_CASE
     };
     
     private static final Map<String, OutputFormat> STANDARD_OUTPUT_FORMATS;
@@ -340,29 +202,8 @@ public final class Configuration extends 
MutableProcessingConfiguration<Configur
         STANDARD_OUTPUT_FORMATS.put(JavaScriptOutputFormat.INSTANCE.getName(), 
JavaScriptOutputFormat.INSTANCE);
         STANDARD_OUTPUT_FORMATS.put(JSONOutputFormat.INSTANCE.getName(), 
JSONOutputFormat.INSTANCE);
     }
-    
-    public static final int AUTO_DETECT_TAG_SYNTAX = 0;
-    public static final int ANGLE_BRACKET_TAG_SYNTAX = 1;
-    public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
-
-    public static final int AUTO_DETECT_NAMING_CONVENTION = 10;
-    public static final int LEGACY_NAMING_CONVENTION = 11;
-    public static final int CAMEL_CASE_NAMING_CONVENTION = 12;
 
-    /**
-     * Don't enable auto-escaping, regardless of what the {@link OutputFormat} 
is. Note that a {@code 
-     * <#ftl auto_esc=true>} in the template will override this.
-     */
-    public static final int DISABLE_AUTO_ESCAPING_POLICY = 20;
-    /**
-     * Enable auto-escaping if the output format supports it and {@link 
MarkupOutputFormat#isAutoEscapedByDefault()} is
-     * {@code true}.
-     */
-    public static final int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 21;
-    /** Enable auto-escaping if the {@link OutputFormat} supports it. */
-    public static final int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 22;
-    
-    /** FreeMarker version 3.0.0 (an {@link #Configuration(Version) 
incompatible improvements break-point}) */
+    /** FreeMarker version 3.0.0 */
     public static final Version VERSION_3_0_0 = new Version(3, 0, 0);
     
     /** The default of {@link #getIncompatibleImprovements()}, currently 
{@link #VERSION_3_0_0}. */
@@ -406,402 +247,298 @@ public final class Configuration extends 
MutableProcessingConfiguration<Configur
             throw new RuntimeException("Failed to load and parse " + 
VERSION_PROPERTIES_PATH, e);
         }
     }
-    
-    private volatile boolean localizedLookup = true;
-    private boolean whitespaceStripping = true;
-    private int autoEscapingPolicy = ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY;
-    private OutputFormat outputFormat = UndefinedOutputFormat.INSTANCE;
-    private boolean outputFormatExplicitlySet;
-    private Boolean recognizeStandardFileExtensions;
-    private Map<String, ? extends OutputFormat> registeredCustomOutputFormats 
= Collections.emptyMap(); 
-    private Version incompatibleImprovements;
-    private TemplateLanguage templateLanguage = TemplateLanguage.FTL;
-    private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX;
-    private int namingConvention = AUTO_DETECT_NAMING_CONVENTION;
-    private int tabSize = 8;  // Default from JavaCC 3.x 
-
-    private DefaultTemplateResolver templateResolver;
-    
-    private boolean templateLoaderExplicitlySet;
-    private boolean templateLookupStrategyExplicitlySet;
-    private boolean templateNameFormatExplicitlySet;
-    private boolean cacheStorageExplicitlySet;
-    
-    private boolean objectWrapperExplicitlySet;
-    private boolean templateExceptionHandlerExplicitlySet;
-    private boolean logTemplateExceptionsExplicitlySet;
-    private boolean localeExplicitlySet;
-    private boolean sourceEncodingExplicitlySet;
-    private boolean timeZoneExplicitlySet;
 
-    private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap();
+    // Configuration-specific settings:
+
+    private final Version incompatibleImprovements;
+    private final DefaultTemplateResolver templateResolver;
+    private final boolean localizedLookup;
+    private final List<OutputFormat> registeredCustomOutputFormats;
+    private final Map<String, OutputFormat> 
registeredCustomOutputFormatsByName;
+    private final Map<String, Object> sharedVariables;
+    private final Map<String, TemplateModel> wrappedSharedVariables;
+
+    // ParsingConfiguration settings:
+
+    private final TemplateLanguage templateLanguage;
+    private final int tagSyntax;
+    private final int namingConvention;
+    private final boolean whitespaceStripping;
+    private final int autoEscapingPolicy;
+    private final OutputFormat outputFormat;
+    private final Boolean recognizeStandardFileExtensions;
+    private final int tabSize;
+    private final Charset sourceEncoding;
+
+    // ProcessingConfiguration settings:
+
+    private final Locale locale;
+    private final String numberFormat;
+    private final String timeFormat;
+    private final String dateFormat;
+    private final String dateTimeFormat;
+    private final TimeZone timeZone;
+    private final TimeZone sqlDateAndTimeTimeZone;
+    private final String booleanFormat;
+    private final TemplateExceptionHandler templateExceptionHandler;
+    private final ArithmeticEngine arithmeticEngine;
+    private final ObjectWrapper objectWrapper;
+    private final Charset outputEncoding;
+    private final Charset urlEscapingCharset;
+    private final Boolean autoFlush;
+    private final TemplateClassResolver newBuiltinClassResolver;
+    private final Boolean showErrorTips;
+    private final Boolean apiBuiltinEnabled;
+    private final Boolean logTemplateExceptions;
+    private final Map<String, TemplateDateFormatFactory> customDateFormats;
+    private final Map<String, TemplateNumberFormatFactory> customNumberFormats;
+    private final Map<String, String> autoImports;
+    private final List<String> autoIncludes;
+    private final Boolean lazyImports;
+    private final Boolean lazyAutoImports;
+    private final Map<Object, Object> customAttributes;
+
+    // CustomStateScope:
 
     private final ConcurrentHashMap<CustomStateKey, Object> customStateMap = 
new ConcurrentHashMap<>(0);
     private final Object customStateMapLock = new Object();
 
-    /**
-     * Needed so that it doesn't mater in what order do you call {@link 
#setSharedVariables(Map)}
-     * and {@link #setObjectWrapper(ObjectWrapper)}. When the user configures 
FreeMarker from Spring XML, he has no
-     * control over the order, so it has to work on both ways.
-     */
-    private HashMap<String, Object> rewrappableSharedVariables = null;
-    
-    private Charset sourceEncoding = getDefaultSourceEncoding();
-
-    /**
-     * @deprecated Use {@link #Configuration(Version)} instead. Note that the 
version can be still modified later with
-     *     {@link Configuration#setIncompatibleImprovements(Version)} (or
-     *     {@link Configuration#setSettings(Properties)}).  
-     */
-    @Deprecated
-    public Configuration() {
-        this(DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
-    }
+    private <SelfT extends ExtendableBuilder<SelfT>> 
Configuration(ExtendableBuilder<SelfT> builder)
+            throws ConfigurationException {
+        // Configuration-specific settings:
 
-    /**
-     * Creates a new instance and sets which of the non-backward-compatible 
bugfixes/improvements should be enabled.
-     * Note that the specified versions corresponds to the {@code 
incompatible_improvements} configuration setting, and
-     * can be changed later, with {@link 
#setIncompatibleImprovements(Version)} for example. 
-     *
-     * <p><b>About the "incompatible improvements" setting</b>
-     *
-     * <p>This setting value is the FreeMarker version number where the not 
100% backward compatible bug fixes and
-     * improvements that you want to enable were already implemented. In new 
projects you should set this to the
-     * FreeMarker version that you are actually using. In older projects it's 
also usually better to keep this high,
-     * however you better check the changes activated (find them below), at 
least if not only the 3rd version number
-     * (the micro version) of {@code incompatibleImprovements} is increased. 
Generally, as far as you only increase the
-     * last version number of this setting, the changes are always low risk. 
The default value is 2.3.0 to maximize
-     * backward compatibility, but that value isn't recommended.
-     * 
-     * <p>Bugfixes and improvements that are fully backward compatible, also 
those that are important security fixes,
-     * are enabled regardless of the incompatible improvements setting.
-     * 
-     * <p>An important consequence of setting this setting is that now your 
application will check if the stated minimum
-     * FreeMarker version requirement is met. Like if you set this setting to 
2.3.22, but accidentally the application
-     * is deployed with FreeMarker 2.3.21, then FreeMarker will fail, telling 
that a higher version is required. After
-     * all, the fixes/improvements you have requested aren't available on a 
lower version.
-     * 
-     * <p>Note that as FreeMarker's minor (2nd) or major (1st) version number 
increments, it's possible that emulating
-     * some of the old bugs will become unsupported, that is, even if you set 
this setting to a low value, it silently
-     * wont bring back the old behavior anymore. Information about that will 
be present here.
-     * 
-     * <p>Currently the effects of this setting are:
-     * <ul>
-     *   <li><p>
-     *     3.0.0: This is the lowest supported value in FreeMarker 3.
-     *   </li>
-     * </ul>
-     * 
-     * @throws IllegalArgumentException
-     *             If {@code incompatibleImmprovements} refers to a version 
that wasn't released yet when the currently
-     *             used FreeMarker version was released, or is less than 
3.0.0, or is {@code null}.
-     * 
-     * @since 2.3.21
-     */
-    public Configuration(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
-        
-        _NullArgumentException.check("incompatibleImprovements", 
incompatibleImprovements);
-        this.incompatibleImprovements = incompatibleImprovements;
-        
-        createTemplateResolver();
-        loadBuiltInSharedVariables();
-    }
+        incompatibleImprovements = builder.getIncompatibleImprovements();
 
-    private void createTemplateResolver() {
         templateResolver = new DefaultTemplateResolver(
-                null,
-                getDefaultCacheStorage(),
-                getDefaultTemplateLookupStrategy(),
-                getDefaultTemplateNameFormat(),
-                null,
+                builder.getTemplateLoader(),
+                builder.getCacheStorage(), 
builder.getTemplateUpdateDelayMilliseconds(),
+                builder.getTemplateLookupStrategy(), 
builder.getLocalizedLookup(),
+                builder.getTemplateNameFormat(),
+                builder.getTemplateConfigurations(),
                 this);
-        templateResolver.clearTemplateCache(); // for fully BC behavior
-        templateResolver.setTemplateUpdateDelayMilliseconds(5000);
-    }
-
-    @Override
-    public TemplateExceptionHandler getTemplateExceptionHandler() {
-        return super.getTemplateExceptionHandler();
-    }
 
-    @Override
-    protected TemplateExceptionHandler getInheritedTemplateExceptionHandler() {
-        throw new BugException("Missing property value");
-    }
+        localizedLookup = builder.getLocalizedLookup();
+
+        {
+            Collection<OutputFormat> registeredCustomOutputFormats = 
builder.getRegisteredCustomOutputFormats();
+
+            _NullArgumentException.check(registeredCustomOutputFormats);
+            Map<String, OutputFormat> registeredCustomOutputFormatsByName = 
new LinkedHashMap<>(
+                    registeredCustomOutputFormats.size() * 4 / 3, 1f);
+            for (OutputFormat outputFormat : registeredCustomOutputFormats) {
+                String name = outputFormat.getName();
+                if (name.equals(UndefinedOutputFormat.INSTANCE.getName())) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The \"" + name + "\" output format can't be 
redefined",
+                            null);
+                }
+                if (name.equals(PlainTextOutputFormat.INSTANCE.getName())) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The \"" + name + "\" output format can't be 
redefined",
+                            null);
+                }
+                if (name.length() == 0) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't be 0 long",
+                            null);
+                }
+                if (!Character.isLetterOrDigit(name.charAt(0))) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name must start with letter or 
digit: " + name,
+                            null);
+                }
+                if (name.indexOf('+') != -1) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't contain \"+\" 
character: " + name,
+                            null);
+                }
+                if (name.indexOf('{') != -1) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't contain \"{\" 
character: " + name,
+                            null);
+                }
+                if (name.indexOf('}') != -1) {
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "The output format name can't contain \"}\" 
character: " + name,
+                            null);
+                }
 
-    @Override
-    protected ArithmeticEngine getInheritedArithmeticEngine() {
-        throw new BugException("Missing property value");
-    }
+                OutputFormat replaced = 
registeredCustomOutputFormatsByName.put(outputFormat.getName(), outputFormat);
+                if (replaced != null) {
+                    if (replaced == outputFormat) {
+                        throw new IllegalArgumentException(
+                                "Duplicate output format in the collection: " 
+ outputFormat);
+                    }
+                    throw new ConfigurationSettingValueException(
+                            
ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, null, false,
+                            "Clashing output format names between " + replaced 
+ " and " + outputFormat + ".",
+                            null);
+                }
+            }
 
-    private void recreateTemplateResolverWith(
-            TemplateLoader loader, CacheStorage storage,
-            TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat 
templateNameFormat,
-            TemplateConfigurationFactory templateConfigurations) {
-        DefaultTemplateResolver oldCache = templateResolver;
-        templateResolver = new DefaultTemplateResolver(
-                loader, storage, templateLookupStrategy, templateNameFormat, 
templateConfigurations, this);
-        templateResolver.clearTemplateCache(false);
-        
templateResolver.setTemplateUpdateDelayMilliseconds(oldCache.getTemplateUpdateDelayMilliseconds());
-        templateResolver.setLocalizedLookup(localizedLookup);
-    }
-    
-    private void recreateTemplateResolver() {
-        recreateTemplateResolverWith(templateResolver.getTemplateLoader(), 
templateResolver.getCacheStorage(),
-                templateResolver.getTemplateLookupStrategy(), 
templateResolver.getTemplateNameFormat(),
-                getTemplateConfigurations());
-    }
-    
-    private TemplateLookupStrategy getDefaultTemplateLookupStrategy() {
-        return getDefaultTemplateLookupStrategy(getIncompatibleImprovements());
-    }
-    
-    static TemplateLookupStrategy getDefaultTemplateLookupStrategy(Version 
incompatibleImprovements) {
-        return DefaultTemplateLookupStrategy.INSTANCE;
-    }
-    
-    private TemplateNameFormat getDefaultTemplateNameFormat() {
-        return getDefaultTemplateNameFormat(getIncompatibleImprovements());
-    }
-    
-    static TemplateNameFormat getDefaultTemplateNameFormat(Version 
incompatibleImprovements) {
-        return DefaultTemplateNameFormatFM2.INSTANCE;
-    }
-    
-    private CacheStorage getDefaultCacheStorage() {
-        return createDefaultCacheStorage(getIncompatibleImprovements(), 
getCacheStorage()); 
-    }
-    
-    static CacheStorage createDefaultCacheStorage(Version 
incompatibleImprovements, CacheStorage existingCacheStorage) {
-        if (existingCacheStorage instanceof DefaultSoftCacheStorage) {
-            return existingCacheStorage;
+            this.registeredCustomOutputFormatsByName = 
registeredCustomOutputFormatsByName;
+            this.registeredCustomOutputFormats = 
Collections.unmodifiableList(new
+                    ArrayList<OutputFormat>(registeredCustomOutputFormats));
         }
-        return new DefaultSoftCacheStorage(); 
-    }
 
-    private static class DefaultSoftCacheStorage extends SoftCacheStorage {
-        // Nothing to override
-    }
+        ObjectWrapper objectWrapper = builder.getObjectWrapper();
 
-    static TemplateExceptionHandler getDefaultTemplateExceptionHandler() {
-        return TemplateExceptionHandler.DEBUG_HANDLER; // [FM3] RETHROW
-    }
+        {
+            Map<String, Object> sharedVariables = builder.getSharedVariables();
 
-    private ObjectWrapper getDefaultObjectWrapper() {
-        return getDefaultObjectWrapper(getIncompatibleImprovements());
-    }
-    
-    @Override
-    public Object clone() {
-        try {
-            Configuration copy = (Configuration) super.clone();
-            copy.sharedVariables = new HashMap(sharedVariables);
-            copy.recreateTemplateResolverWith(
-                    templateResolver.getTemplateLoader(), 
templateResolver.getCacheStorage(),
-                    templateResolver.getTemplateLookupStrategy(), 
templateResolver.getTemplateNameFormat(),
-                    templateResolver.getTemplateConfigurations());
-            return copy;
-        } catch (CloneNotSupportedException e) {
-            throw new BugException("Cloning failed", e);
+            HashMap<String, TemplateModel> wrappedSharedVariables = new 
HashMap<>(
+                    (sharedVariables.size() + 5 /* [FM3] 5 legacy vars */) * 4 
/ 3 + 1, 0.75f);
+
+            // TODO [FM3] Get rid of this
+            wrappedSharedVariables.put("capture_output", new CaptureOutput());
+            wrappedSharedVariables.put("compress", StandardCompress.INSTANCE);
+            wrappedSharedVariables.put("html_escape", new HtmlEscape());
+            wrappedSharedVariables.put("normalize_newlines", new 
NormalizeNewlines());
+            wrappedSharedVariables.put("xml_escape", new XmlEscape());
+
+            // In case the inherited sharedVariables aren't empty, we want to 
merge the two maps:
+            wrapAndPutSharedVariables(wrappedSharedVariables, 
builder.getDefaultSharedVariables(),
+                    objectWrapper);
+            if (builder.isSharedVariablesSet()) {
+                wrapAndPutSharedVariables(wrappedSharedVariables, 
sharedVariables, objectWrapper);
+            }
+            this.wrappedSharedVariables = wrappedSharedVariables;
+            this.sharedVariables = Collections.unmodifiableMap(new 
LinkedHashMap<>(sharedVariables));
         }
-    }
-    
-    private void loadBuiltInSharedVariables() {
-        sharedVariables.put("capture_output", new CaptureOutput());
-        sharedVariables.put("compress", StandardCompress.INSTANCE);
-        sharedVariables.put("html_escape", new HtmlEscape());
-        sharedVariables.put("normalize_newlines", new NormalizeNewlines());
-        sharedVariables.put("xml_escape", new XmlEscape());
-    }
 
-    /**
-     * Sets a {@link TemplateLoader} that is used to look up and load 
templates;
-     * as a side effect the template templateResolver will be emptied.
-     * By providing your own {@link TemplateLoader} implementation, you can 
load templates from whatever kind of
-     * storages, like from relational databases, NoSQL-storages, etc.
-     * 
-     * <p>Convenience methods exists to install commonly used loaders, instead 
of using this method:
-     * {@link #setClassForTemplateLoading(Class, String)}, 
-     * {@link #setClassLoaderForTemplateLoading(ClassLoader, String)}, 
-     * {@link #setDirectoryForTemplateLoading(File)}, and
-     * {@link #setServletContextForTemplateLoading(Object, String)}.
-     * 
-     * <p>You can chain several {@link TemplateLoader}-s together with {@link 
MultiTemplateLoader}.
-     * 
-     * <p>Default value: You should always set the template loader instead of 
relying on the default value.
-     * (But if you still care what it is, before "incompatible improvements" 
2.3.21 it's a {@link FileTemplateLoader}
-     * that uses the current directory as its root; as it's hard tell what 
that directory will be, it's not very useful
-     * and dangerous. Starting with "incompatible improvements" 2.3.21 the 
default is {@code null}.)   
-     */
-    public void setTemplateLoader(TemplateLoader templateLoader) {
-        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
-        synchronized (this) {
-            if (templateResolver.getTemplateLoader() != templateLoader) {
-                recreateTemplateResolverWith(templateLoader, 
templateResolver.getCacheStorage(),
-                        templateResolver.getTemplateLookupStrategy(), 
templateResolver.getTemplateNameFormat(),
-                        templateResolver.getTemplateConfigurations());
+        // ParsingConfiguration settings:
+
+        templateLanguage = builder.getTemplateLanguage();
+        tagSyntax = builder.getTagSyntax();
+        namingConvention = builder.getNamingConvention();
+        whitespaceStripping = builder.getWhitespaceStripping();
+        autoEscapingPolicy = builder.getAutoEscapingPolicy();
+        outputFormat = builder.getOutputFormat();
+        recognizeStandardFileExtensions = 
builder.getRecognizeStandardFileExtensions();
+        tabSize = builder.getTabSize();
+        sourceEncoding = builder.getSourceEncoding();
+
+        // ProcessingConfiguration settings:
+
+        locale = builder.getLocale();
+        numberFormat = builder.getNumberFormat();
+        timeFormat = builder.getTimeFormat();
+        dateFormat = builder.getDateFormat();
+        dateTimeFormat = builder.getDateTimeFormat();
+        timeZone = builder.getTimeZone();
+        sqlDateAndTimeTimeZone = builder.getSQLDateAndTimeTimeZone();
+        booleanFormat = builder.getBooleanFormat();
+        templateExceptionHandler = builder.getTemplateExceptionHandler();
+        arithmeticEngine = builder.getArithmeticEngine();
+        this.objectWrapper = objectWrapper;
+        outputEncoding = builder.getOutputEncoding();
+        urlEscapingCharset = builder.getURLEscapingCharset();
+        autoFlush = builder.getAutoFlush();
+        newBuiltinClassResolver = builder.getNewBuiltinClassResolver();
+        showErrorTips = builder.getShowErrorTips();
+        apiBuiltinEnabled = builder.getAPIBuiltinEnabled();
+        logTemplateExceptions = builder.getLogTemplateExceptions();
+        customDateFormats = 
Collections.unmodifiableMap(builder.getCustomDateFormats());
+        customNumberFormats = 
Collections.unmodifiableMap(builder.getCustomNumberFormats());
+        autoImports = Collections.unmodifiableMap(builder.getAutoImports());
+        autoIncludes = Collections.unmodifiableList(builder.getAutoIncludes());
+        lazyImports = builder.getLazyImports();
+        lazyAutoImports = builder.getLazyAutoImports();
+        customAttributes = 
Collections.unmodifiableMap(builder.getCustomAttributes());
+    }
+
+    private <SelfT extends ExtendableBuilder<SelfT>> void 
wrapAndPutSharedVariables(
+            HashMap<String, TemplateModel> wrappedSharedVariables, Map<String, 
Object> rawSharedVariables,
+            ObjectWrapper objectWrapper) throws 
ConfigurationSettingValueException {
+        if (rawSharedVariables.isEmpty()) {
+            return;
+        }
+
+        for (Entry<String, Object> ent : rawSharedVariables.entrySet()) {
+            try {
+                wrappedSharedVariables.put(ent.getKey(), 
objectWrapper.wrap(ent.getValue()));
+            } catch (TemplateModelException e) {
+                throw new ConfigurationSettingValueException(
+                        ExtendableBuilder.SHARED_VARIABLES_KEY, null, false,
+                        "Failed to wrap shared variable " + 
_StringUtil.jQuote(ent.getKey()),
+                        e);
             }
-            templateLoaderExplicitlySet = true;
         }
     }
-    
-    /**
-     * Resets the setting to its default, as if it was never set. This means 
that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also 
change as appropriate. Also 
-     * {@link #isTemplateLoaderExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
-     */
-    public void unsetTemplateLoader() {
-        if (templateLoaderExplicitlySet) {
-            setTemplateLoader(null);
-            templateLoaderExplicitlySet = false;
-        }
+
+    @Override
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        return templateExceptionHandler;
     }
 
     /**
-     * Tells if {@link #setTemplateLoader(TemplateLoader)} (or equivalent) was 
already called on this instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public boolean isTemplateLoaderExplicitlySet() {
-        return templateLoaderExplicitlySet;
+    @Override
+    public boolean isTemplateExceptionHandlerSet() {
+        return true;
     }
 
-    /**
-     * The getter pair of {@link #setTemplateLoader(TemplateLoader)}.
-     */
+    private static class DefaultSoftCacheStorage extends SoftCacheStorage {
+        // Nothing to override
+    }
+
+    @Override
     public TemplateLoader getTemplateLoader() {
         if (templateResolver == null) {
             return null;
         }
         return templateResolver.getTemplateLoader();
     }
-    
-    /**
-     * Sets a {@link TemplateLookupStrategy} that is used to look up templates 
based on the requested name; as a side
-     * effect the template templateResolver will be emptied. The default value 
is
-     * {@link DefaultTemplateLookupStrategy#INSTANCE}.
-     * 
-     * @since 2.3.22
-     */
-    public void setTemplateLookupStrategy(TemplateLookupStrategy 
templateLookupStrategy) {
-        if (templateResolver.getTemplateLookupStrategy() != 
templateLookupStrategy) {
-            recreateTemplateResolverWith(templateResolver.getTemplateLoader(), 
templateResolver.getCacheStorage(),
-                    templateLookupStrategy, 
templateResolver.getTemplateNameFormat(),
-                    templateResolver.getTemplateConfigurations());
-        }
-        templateLookupStrategyExplicitlySet = true;
-    }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means 
that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also 
change as appropriate. Also 
-     * {@link #isTemplateLookupStrategyExplicitlySet()} will return {@code 
false}.
-     * 
-     * @since 2.3.22
-     */
-    public void unsetTemplateLookupStrategy() {
-        if (templateLookupStrategyExplicitlySet) {
-            setTemplateLookupStrategy(getDefaultTemplateLookupStrategy());
-            templateLookupStrategyExplicitlySet = false;
-        }
-    }
-    
-    /**
-     * Tells if {@link #setTemplateLookupStrategy(TemplateLookupStrategy)} (or 
equivalent) was already called on this
-     * instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public boolean isTemplateLookupStrategyExplicitlySet() {
-        return templateLookupStrategyExplicitlySet;
+    @Override
+    public boolean isTemplateLoaderSet() {
+        return true;
     }
-    
-    /**
-     * The getter pair of {@link 
#setTemplateLookupStrategy(TemplateLookupStrategy)}.
-     */
+
+    @Override
     public TemplateLookupStrategy getTemplateLookupStrategy() {
         if (templateResolver == null) {
             return null;
         }
         return templateResolver.getTemplateLookupStrategy();
     }
-    
-    /**
-     * Sets the template name format used. The default is {@link 
DefaultTemplateNameFormatFM2#INSTANCE}, while the
-     * recommended value for new projects is {@link 
DefaultTemplateNameFormat#INSTANCE}.
-     * 
-     * @since 2.3.22
-     */
-    public void setTemplateNameFormat(TemplateNameFormat templateNameFormat) {
-        if (templateResolver.getTemplateNameFormat() != templateNameFormat) {
-            recreateTemplateResolverWith(templateResolver.getTemplateLoader(), 
templateResolver.getCacheStorage(),
-                    templateResolver.getTemplateLookupStrategy(), 
templateNameFormat,
-                    templateResolver.getTemplateConfigurations());
-        }
-        templateNameFormatExplicitlySet = true;
-    }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means 
that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also 
change as appropriate. Also 
-     * {@link #isTemplateNameFormatExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void unsetTemplateNameFormat() {
-        if (templateNameFormatExplicitlySet) {
-            setTemplateNameFormat(getDefaultTemplateNameFormat());
-            templateNameFormatExplicitlySet = false;
-        }
+    @Override
+    public boolean isTemplateLookupStrategySet() {
+        return true;
     }
     
-    /**
-     * Tells if {@link #setTemplateNameFormat(TemplateNameFormat)} (or 
equivalent) was already called on this instance.
-     * 
-     * @since 2.3.22
-     */
-    public boolean isTemplateNameFormatExplicitlySet() {
-        return templateNameFormatExplicitlySet;
-    }
-
-    /**
-     * The getter pair of {@link #setTemplateNameFormat(TemplateNameFormat)}.
-     */
+    @Override
     public TemplateNameFormat getTemplateNameFormat() {
         if (templateResolver == null) {
             return null;
         }
         return templateResolver.getTemplateNameFormat();
     }
-    
+
     /**
-     * Sets a {@link TemplateConfigurationFactory} that will configure 
individual templates where their settings differ
-     * from those coming from the common {@link Configuration} object. A 
typical use case for that is specifying the
-     * {@link TemplateConfiguration.Builder#setOutputFormat(OutputFormat) 
outputFormat} for templates based on their
-     * file extension or parent directory.
-     * 
-     * <p>
-     * Note that the settings suggested by standard file extensions are 
stronger than that you set here. See
-     * {@link #setRecognizeStandardFileExtensions(boolean)} for more 
information about standard file extensions.
-     * 
-     * <p>See "Template configurations" in the FreeMarker Manual for examples.
-     * 
-     * @since 2.3.24
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void setTemplateConfigurations(TemplateConfigurationFactory 
templateConfigurations) {
-        if (templateResolver.getTemplateConfigurations() != 
templateConfigurations) {
-            recreateTemplateResolverWith(templateResolver.getTemplateLoader(), 
templateResolver.getCacheStorage(),
-                    templateResolver.getTemplateLookupStrategy(), 
templateResolver.getTemplateNameFormat(),
-                    templateConfigurations);
-        }
+    @Override
+    public boolean isTemplateNameFormatSet() {
+        return true;
     }
-    
-    /**
-     * The getter pair of {@link 
#setTemplateConfigurations(TemplateConfigurationFactory)}.
-     */
+
+    @Override
     public TemplateConfigurationFactory getTemplateConfigurations() {
         if (templateResolver == null) {
             return null;
@@ -810,1183 +547,669 @@ public final class Configuration extends 
MutableProcessingConfiguration<Configur
     }
 
     /**
-     * Sets the {@link CacheStorage} used for caching {@link Template}-s;
-     * the earlier content of the template templateResolver will be dropt.
-     * 
-     * The default is a {@link SoftCacheStorage}. If the total size of the 
{@link Template}
-     * objects is significant but most templates are used rarely, using a
-     * {@link MruCacheStorage} instead might be advisable. If you don't want 
caching at
-     * all, use {@link 
org.apache.freemarker.core.templateresolver.impl.NullCacheStorage} (you can't 
use {@code null}).
-     * 
-     * <p>Note that setting the templateResolver storage will re-invoke the 
template templateResolver, so
-     * all its content will be lost.
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void setCacheStorage(CacheStorage cacheStorage) {
-        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
-        synchronized (this) {
-            if (getCacheStorage() != cacheStorage) {
-                
recreateTemplateResolverWith(templateResolver.getTemplateLoader(), cacheStorage,
-                        templateResolver.getTemplateLookupStrategy(), 
templateResolver.getTemplateNameFormat(),
-                        templateResolver.getTemplateConfigurations());
-            }
-            cacheStorageExplicitlySet = true;
-        }
+    @Override
+    public boolean isTemplateConfigurationsSet() {
+        return true;
     }
-    
-    /**
-     * Resets the setting to its default, as if it was never set. This means 
that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also 
change as appropriate. Also 
-     * {@link #isCacheStorageExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
-     */
-    public void unsetCacheStorage() {
-        if (cacheStorageExplicitlySet) {
-            setCacheStorage(getDefaultCacheStorage());
-            cacheStorageExplicitlySet = false;
-        }
+
+    @Override
+    public CacheStorage getCacheStorage() {
+        return templateResolver.getCacheStorage();
     }
-    
+
     /**
-     * Tells if {@link #setCacheStorage(CacheStorage)} (or equivalent) was 
already called on this instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public boolean isCacheStorageExplicitlySet() {
-        return cacheStorageExplicitlySet;
+    @Override
+    public boolean isCacheStorageSet() {
+        return true;
     }
-    
-    /**
-     * The getter pair of {@link #setCacheStorage(CacheStorage)}.
-     * 
-     * @since 2.3.20
-     */
-    public CacheStorage getCacheStorage() {
-        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
-        synchronized (this) {
-            if (templateResolver == null) {
-                return null;
-            }
-            return templateResolver.getCacheStorage();
-        }
+
+    @Override
+    public long getTemplateUpdateDelayMilliseconds() {
+        return templateResolver.getTemplateUpdateDelayMilliseconds();
     }
 
     /**
-     * Sets the file system directory from which to load templates. This is 
equivalent to
-     * {@code setTemplateLoader(new FileTemplateLoader(dir))}, so see
-     * {@link FileTemplateLoader#FileTemplateLoader(File)} for more details.
-     * 
-     * <p>
-     * Note that FreeMarker can load templates from non-file-system sources 
too. See
-     * {@link #setTemplateLoader(TemplateLoader)} from more details.
-     * 
-     * <p>
-     * Note that this shouldn't be used for loading templates that are coming 
from a WAR; use
-     * {@link #setServletContextForTemplateLoading(Object, String)} then. 
Servlet containers might not unpack the WAR
-     * file, in which case you clearly can't access the contained files via 
{@link File}. Even if the WAR is unpacked,
-     * the servlet container might not expose the location as a {@link File}.
-     * {@link #setServletContextForTemplateLoading(Object, String)} on the 
other hand will work in all these cases.
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void setDirectoryForTemplateLoading(File dir) throws IOException {
-        TemplateLoader tl = getTemplateLoader();
-        if (tl instanceof FileTemplateLoader) {
-            String path = ((FileTemplateLoader) tl).baseDir.getCanonicalPath();
-            if (path.equals(dir.getCanonicalPath()))
-                return;
-        }
-        setTemplateLoader(new FileTemplateLoader(dir));
+    @Override
+    public boolean isTemplateUpdateDelayMillisecondsSet() {
+        return true;
     }
 
-    /**
-     * Sets the servlet context from which to load templates.
-     * This is equivalent to {@code setTemplateLoader(new 
WebAppTemplateLoader(sctxt, path))}
-     * or {@code setTemplateLoader(new WebAppTemplateLoader(sctxt))} if {@code 
path} was
-     * {@code null}, so see {@code 
org.apache.freemarker.servlet.WebAppTemplateLoader} for more details.
-     * 
-     * @param servletContext the {@code javax.servlet.ServletContext} object. 
(The declared type is {@link Object}
-     *        to prevent class loading error when using FreeMarker in an 
environment where
-     *        there's no servlet classes available.)
-     * @param path the path relative to the ServletContext.
-     *
-     * @see #setTemplateLoader(TemplateLoader)
-     */
-    public void setServletContextForTemplateLoading(Object servletContext, 
String path) {
-        try {
-            // Don't introduce linking-time dependency on servlets
-            final Class webappTemplateLoaderClass = _ClassUtil.forName(
-                    "org.apache.freemarker.servlet.WebAppTemplateLoader");
-            
-            // Don't introduce linking-time dependency on servlets
-            final Class servletContextClass = 
_ClassUtil.forName("javax.servlet.ServletContext");
-            
-            final Class[] constructorParamTypes;
-            final Object[] constructorParams;
-            if (path == null) {
-                constructorParamTypes = new Class[] { servletContextClass };
-                constructorParams = new Object[] { servletContext };
-            } else {
-                constructorParamTypes = new Class[] { servletContextClass, 
String.class };
-                constructorParams = new Object[] { servletContext, path };
-            }
-            
-            setTemplateLoader( (TemplateLoader)
-                    webappTemplateLoaderClass
-                            .getConstructor(constructorParamTypes)
-                                    .newInstance(constructorParams));
-        } catch (Exception e) {
-            throw new BugException(e);
-        }
+    @Override
+    public Version getIncompatibleImprovements() {
+        return incompatibleImprovements;
+    }
+
+    @Override
+    public boolean getWhitespaceStripping() {
+        return whitespaceStripping;
     }
 
     /**
-     * Sets the class whose {@link Class#getResource(String)} method will be 
used to load templates, from the inside the
-     * package specified. See {@link 
ClassTemplateLoader#ClassTemplateLoader(Class, String)} for more details.
-     * 
-     * @param basePackagePath
-     *            Separate steps with {@code "/"}, not {@code "."}, and note 
that it matters if this starts with
-     *            {@code /} or not. See {@link 
ClassTemplateLoader#ClassTemplateLoader(Class, String)} for more details.
-     * 
-     * @see #setClassLoaderForTemplateLoading(ClassLoader, String)
-     * @see #setTemplateLoader(TemplateLoader)
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void setClassForTemplateLoading(Class resourceLoaderClass, String 
basePackagePath) {
-        setTemplateLoader(new ClassTemplateLoader(resourceLoaderClass, 
basePackagePath));
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return true;
     }
-    
+
     /**
-     * Sets the {@link ClassLoader} whose {@link 
ClassLoader#getResource(String)} method will be used to load templates,
-     * from the inside the package specified. See {@link 
ClassTemplateLoader#ClassTemplateLoader(Class, String)} for
-     * more details.
-     * 
-     * @param basePackagePath
-     *            Separate steps with {@code "/"}, not {@code "."}. See
-     *            {@link ClassTemplateLoader#ClassTemplateLoader(Class, 
String)} for more details.
-     * 
-     * @see #setClassForTemplateLoading(Class, String)
-     * @see #setTemplateLoader(TemplateLoader)
-     * 
-     * @since 2.3.22
+     * When auto-escaping should be enabled depending on the current 
{@linkplain OutputFormat output format};
+     * default is {@link 
ParsingConfiguration#ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}. Note that the 
default output
+     * format, {@link UndefinedOutputFormat}, is a non-escaping format, so 
there auto-escaping will be off.
+     * Note that the templates can turn auto-escaping on/off locally with 
directives like {@code <#ftl auto_esc=...>},
+     * which will ignore the policy.
+     *
+     * <p><b>About auto-escaping</b></p>
+     *
+     * <p>
+     * Auto-escaping has significance when a value is printed with 
<code>${...}</code> (or <code>#{...}</code>). If
+     * auto-escaping is on, FreeMarker will assume that the value is plain 
text (as opposed to markup or some kind of
+     * rich text), so it will escape it according the current output format 
(see {@link #getOutputFormat()}
+     * and {@link 
TemplateConfiguration.Builder#setOutputFormat(OutputFormat)}). If auto-escaping 
is off, FreeMarker
+     * will assume that the string value is already in the output format, so 
it prints it as is to the output.
+     *
+     * <p>Further notes on auto-escaping:
+     * <ul>
+     *   <li>When printing numbers, dates, and other kind of non-string values 
with <code>${...}</code>, they will be
+     *       first converted to string (according the formatting settings and 
locale), then they are escaped just like
+     *       string values.
+     *   <li>When printing {@link TemplateMarkupOutputModel}-s, they aren't 
escaped again (they are already escaped).
+     *   <li>Auto-escaping doesn't do anything if the current output format 
isn't an {@link MarkupOutputFormat}.
+     *       That's the case for the default output format, {@link 
UndefinedOutputFormat}, and also for
+     *       {@link PlainTextOutputFormat}.
+     *   <li>The output format inside a string literal expression is always 
{@link PlainTextOutputFormat}
+     *       (regardless of the output format of the containing template), 
which is a non-escaping format. Thus for
+     *       example, with <code>&lt;#assign s = "foo${bar}"&gt;</code>, 
{@code bar} will always get into {@code s}
+     *       without escaping, but with <code>&lt;#assign 
s&gt;foo${bar}&lt;#assign&gt;</code> it may will be escaped.
+     * </ul>
+     *
+     * <p>Note that what you set here is just a default, which can be 
overridden for individual templates with the
+     * {@linkplain #getTemplateConfigurations() template configurations 
setting}. This setting is also overridden by
+     * the standard file extensions; see them at {@link 
#getRecognizeStandardFileExtensions()}.
+     *
+     * @see Configuration.Builder#setAutoEscapingPolicy(int)
+     * @see TemplateConfiguration.Builder#setAutoEscapingPolicy(int)
+     * @see Configuration.Builder#setOutputFormat(OutputFormat)
+     * @see TemplateConfiguration.Builder#setOutputFormat(OutputFormat)
      */
-    public void setClassLoaderForTemplateLoading(ClassLoader classLoader, 
String basePackagePath) {
-        setTemplateLoader(new ClassTemplateLoader(classLoader, 
basePackagePath));
+    @Override
+    public int getAutoEscapingPolicy() {
+        return autoEscapingPolicy;
     }
 
     /**
-     * Sets the time in milliseconds that must elapse before checking whether 
there is a newer version of a template
-     * "file" exists than the cached one. Defaults to 5000 ms.
-     * 
-     * <p>
-     * When you get a template via {@link #getTemplate(String)} (or some of 
its overloads). FreeMarker will try to get
-     * the template from the template templateResolver. If the template is 
found, and at least this amount of time was elapsed
-     * since the template last modification date was checked, FreeMarker will 
re-check the last modification date (this
-     * could mean I/O), possibly reloading the template and updating the 
templateResolver as a consequence (can mean even more
-     * I/O). The {@link #getTemplate(String)} (or some of its overloads) call 
will only return after this all is
-     * done, so it will return the fresh template.
-     * 
-     * @since 2.3.23
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void setTemplateUpdateDelayMilliseconds(long millis) {
-        templateResolver.setTemplateUpdateDelayMilliseconds(millis);
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return true;
     }
-    
+
+    @Override
+    public OutputFormat getOutputFormat() {
+        return outputFormat;
+    }
+
     /**
-     * The getter pair of {@link #setTemplateUpdateDelayMilliseconds(long)}.
-     * 
-     * @since 2.3.23
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public long getTemplateUpdateDelayMilliseconds() {
-        return templateResolver.getTemplateUpdateDelayMilliseconds();
-    }
-    
     @Override
-    public void setObjectWrapper(ObjectWrapper objectWrapper) {
-        ObjectWrapper prevObjectWrapper = getObjectWrapper();
-        super.setObjectWrapper(objectWrapper);
-        objectWrapperExplicitlySet = true;
-        if (objectWrapper != prevObjectWrapper) {
-            try {
-                setSharedVariablesFromRewrappableSharedVariables();
-            } catch (TemplateModelException e) {
-                throw new RuntimeException(
-                        "Failed to re-wrap earliearly set shared variables 
with the newly set object wrapper",
-                        e);
+    public boolean isOutputFormatSet() {
+        return true;
+    }
+
+    /**
+     * Returns the output format for a name.
+     * 
+     * @param name
+     *            Either the name of the output format as it was registered 
with the
+     *            {@link Configuration#getRegisteredCustomOutputFormats 
registeredCustomOutputFormats} setting,
+     *            or a combined output format name.
+     *            A combined output format is created ad-hoc from the 
registered formats. For example, if you need RTF
+     *            embedded into HTML, the name will be <code>HTML{RTF}</code>, 
where "HTML" and "RTF" refer to the
+     *            existing formats. This logic can be used recursively, so for 
example <code>XML{HTML{RTF}}</code> is
+     *            also valid.
+     * 
+     * @return Not {@code null}.
+     * 
+     * @throws UnregisteredOutputFormatException
+     *             If there's no output format registered with the given name.
+     * @throws IllegalArgumentException
+     *             If the usage of <code>{</code> and <code>}</code> in the 
name is syntactically wrong, or if not all
+     *             {@link OutputFormat}-s are {@link MarkupOutputFormat}-s in 
the <code>...{...}</code> expression.
+     */
+    public OutputFormat getOutputFormat(String name) throws 
UnregisteredOutputFormatException {
+        if (name.length() == 0) {
+            throw new IllegalArgumentException("0-length format name");
+        }
+        if (name.charAt(name.length() - 1) == '}') {
+            // Combined markup
+            int openBrcIdx = name.indexOf('{');
+            if (openBrcIdx == -1) {
+                throw new IllegalArgumentException("Missing opening '{' in: " 
+ name);
+            }
+            
+            MarkupOutputFormat outerOF = 
getMarkupOutputFormatForCombined(name.substring(0, openBrcIdx));
+            MarkupOutputFormat innerOF = getMarkupOutputFormatForCombined(
+                    name.substring(openBrcIdx + 1, name.length() - 1));
+            
+            return new CombinedMarkupOutputFormat(name, outerOF, innerOF);
+        } else {
+            OutputFormat custOF = 
registeredCustomOutputFormatsByName.get(name);
+            if (custOF != null) {
+                return custOF;
+            }
+            
+            OutputFormat stdOF = STANDARD_OUTPUT_FORMATS.get(name);
+            if (stdOF == null) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("Unregistered output format name, ");
+                sb.append(_StringUtil.jQuote(name));
+                sb.append(". The output formats registered in the 
Configuration are: ");
+                
+                Set<String> registeredNames = new TreeSet<>();
+                registeredNames.addAll(STANDARD_OUTPUT_FORMATS.keySet());
+                
registeredNames.addAll(registeredCustomOutputFormatsByName.keySet());
+                
+                boolean first = true;
+                for (String registeredName : registeredNames) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        sb.append(", ");
+                    }
+                    sb.append(_StringUtil.jQuote(registeredName));
+                }
+                
+                throw new UnregisteredOutputFormatException(sb.toString());
             }
+            return stdOF;
         }
     }
 
-    @Override
-    protected ObjectWrapper getInheritedObjectWrapper() {
-        throw new BugException("Missing property value");
+    private MarkupOutputFormat getMarkupOutputFormatForCombined(String 
outerName)
+            throws UnregisteredOutputFormatException {
+        OutputFormat of = getOutputFormat(outerName);
+        if (!(of instanceof MarkupOutputFormat)) {
+            throw new IllegalArgumentException("The \"" + outerName + "\" 
output format can't be used in "
+                    + "...{...} expression, because it's not a markup 
format.");
+        }
+        return (MarkupOutputFormat) of;
     }
-
-    @Override
-    protected Charset getInheritedOutputEncoding() {
-        throw new BugException("Missing property value");
+    
+    /**
+     * The custom output formats that can be referred by their unique name 
({@link OutputFormat#getName()}) from
+     * templates. Names are also used to look up the {@link OutputFormat} for 
standard file extensions; see them at
+     * {@link #getRecognizeStandardFileExtensions()}. Each must be different 
and has a unique name
+     * ({@link OutputFormat#getName()}) within this collection.
+     *
+     * <p>
+     * When there's a clash between a custom output format name and a standard 
output format name, the custom format
+     * will win, thus you can override the meaning of standard output format 
names. Except, it's not allowed to override
+     * {@link UndefinedOutputFormat} and {@link PlainTextOutputFormat}.
+     *
+     * <p>
+     * The default value is an empty collection.
+     *
+     * @throws IllegalArgumentException
+     *             When multiple different {@link OutputFormat}-s have the 
same name in the parameter collection. When
+     *             the same {@link OutputFormat} object occurs for multiple 
times in the collection. If an
+     *             {@link OutputFormat} name is 0 long. If an {@link 
OutputFormat} name doesn't start with letter or
+     *             digit. If an {@link OutputFormat} name contains {@code '+'} 
or <code>'{'</code> or <code>'}'</code>.
+     *             If an {@link OutputFormat} name equals to {@link 
UndefinedOutputFormat#getName()} or
+     *             {@link PlainTextOutputFormat#getName()}.
+     */
+    public Collection<OutputFormat> getRegisteredCustomOutputFormats() {
+        return registeredCustomOutputFormats;
     }
 
     @Override
-    protected Charset getInheritedURLEscapingCharset() {
-        throw new BugException("Missing property value");
+    public boolean getRecognizeStandardFileExtensions() {
+        return recognizeStandardFileExtensions == null
+                ? true
+                : recognizeStandardFileExtensions.booleanValue();
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected TemplateClassResolver getInheritedNewBuiltinClassResolver() {
-        throw new BugException("Missing property value");
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return true;
     }
 
     @Override
-    protected boolean getInheritedAutoFlush() {
-        throw new BugException("Missing property value");
+    public TemplateLanguage getTemplateLanguage() {
+        return templateLanguage;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected boolean getInheritedShowErrorTips() {
-        throw new BugException("Missing property value");
+    public boolean isTemplateLanguageSet() {
+        return true;
     }
 
     @Override
-    protected boolean getInheritedAPIBuiltinEnabled() {
-        throw new BugException("Missing property value");
+    public int getTagSyntax() {
+        return tagSyntax;
     }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means 
that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also 
change as appropriate. Also 
-     * {@link #isObjectWrapperExplicitlySet()} will return {@code false}.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void unsetObjectWrapper() {
-        if (objectWrapperExplicitlySet) {
-            setObjectWrapper(getDefaultObjectWrapper());
-            objectWrapperExplicitlySet = false;
-        }
-    }
-    
+
     /**
-     * Tells if {@link #setObjectWrapper(ObjectWrapper)} (or equivalent) was 
already called on this instance.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public boolean isObjectWrapperExplicitlySet() {
-        return objectWrapperExplicitlySet;
+    @Override
+    public boolean isTagSyntaxSet() {
+        return true;
     }
 
-    @Override
-    public void setLocale(Locale locale) {
-        super.setLocale(locale);
-        localeExplicitlySet = true;
+    // [FM3] Use enum; won't be needed
+    static void validateNamingConventionValue(int namingConvention) {
+        if (namingConvention != 
ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION
+                && namingConvention != 
ParsingConfiguration.LEGACY_NAMING_CONVENTION
+                && namingConvention != 
ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) {
+            throw new IllegalArgumentException("\"naming_convention\" can only 
be set to one of these: "
+                    + "Configuration.AUTO_DETECT_NAMING_CONVENTION, "
+                    + "or Configuration.LEGACY_NAMING_CONVENTION"
+                    + "or Configuration.CAMEL_CASE_NAMING_CONVENTION");
+        }
     }
 
     @Override
-    protected Locale getInheritedLocale() {
-        throw new BugException("Missing property value");
+    public int getNamingConvention() {
+        return namingConvention;
     }
 
     /**
-     * Resets the setting to its default, as if it was never set.
-     *
-     * @since 2.3.26
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void unsetLocale() {
-        if (localeExplicitlySet) {
-            setLocale(getDefaultLocale());
-            localeExplicitlySet = false;
-        }
+    @Override
+    public boolean isNamingConventionSet() {
+        return true;
     }
 
-    /**
-     * Tells if {@link #setLocale(Locale)} (or equivalent) was already called 
on this instance, or it just holds the
-     * default value.
-     *
-     * @since 2.3.26
-     */
-    public boolean isLocaleExplicitlySet() {
-        return localeExplicitlySet;
+    @Override
+    public int getTabSize() {
+        return tabSize;
     }
 
-    static Locale getDefaultLocale() {
-        return Locale.getDefault();
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
+    @Override
+    public boolean isTabSizeSet() {
+        return true;
     }
 
     @Override
-    public void setTimeZone(TimeZone timeZone) {
-        super.setTimeZone(timeZone);
-        timeZoneExplicitlySet = true;
+    public Locale getLocale() {
+        return locale;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected TimeZone getInheritedTimeZone() {
-        throw new BugException("Missing property value");
+    public boolean isLocaleSet() {
+        return true;
     }
 
     @Override
-    protected TimeZone getInheritedSQLDateAndTimeTimeZone() {
-        throw new BugException("Missing property value");
+    public TimeZone getTimeZone() {
+        return timeZone;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected String getInheritedNumberFormat() {
-        throw new BugException("Missing property value");
+    public boolean isTimeZoneSet() {
+        return true;
     }
 
     @Override
-    protected Map<String, TemplateNumberFormatFactory> 
getInheritedCustomNumberFormats() {
-        throw new BugException("Missing property value");
+    public TimeZone getSQLDateAndTimeTimeZone() {
+        return sqlDateAndTimeTimeZone;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected TemplateNumberFormatFactory 
getInheritedCustomNumberFormat(String name) {
-        return null;
+    public boolean isSQLDateAndTimeTimeZoneSet() {
+        return true;
     }
 
     @Override
-    protected String getInheritedBooleanFormat() {
-        throw new BugException("Missing property value");
+    public ArithmeticEngine getArithmeticEngine() {
+        return arithmeticEngine;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected String getInheritedTimeFormat() {
-        throw new BugException("Missing property value");
+    public boolean isArithmeticEngineSet() {
+        return true;
     }
 
     @Override
-    protected String getInheritedDateFormat() {
-        throw new BugException("Missing property value");
+    public String getNumberFormat() {
+        return numberFormat;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected String getInheritedDateTimeFormat() {
-        throw new BugException("Missing property value");
+    public boolean isNumberFormatSet() {
+        return true;
     }
 
     @Override
-    protected Map<String, TemplateDateFormatFactory> 
getInheritedCustomDateFormats() {
-        throw new BugException("Missing property value");
+    public Map<String, TemplateNumberFormatFactory> getCustomNumberFormats() {
+        return customNumberFormats;
     }
 
     @Override
-    protected TemplateDateFormatFactory getInheritedCustomDateFormat(String 
name) {
-        return null;
+    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+        return customNumberFormats.get(name);
     }
 
     /**
-     * Resets the setting to its default, as if it was never set.
-     *
-     * @since 2.3.26
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void unsetTimeZone() {
-        if (timeZoneExplicitlySet) {
-            setTimeZone(getDefaultTimeZone());
-            timeZoneExplicitlySet = false;
-        }
+    @Override
+    public boolean isCustomNumberFormatsSet() {
+        return true;
     }
 
-    /**
-     * Tells if {@link #setTimeZone(TimeZone)} (or equivalent) was already 
called on this instance, or it just holds the
-     * default value.
-     *
-     * @since 2.3.26
-     */
-    public boolean isTimeZoneExplicitlySet() {
-        return timeZoneExplicitlySet;
+    @Override
+    public String getBooleanFormat() {
+        return booleanFormat;
     }
 
-    static TimeZone getDefaultTimeZone() {
-        return TimeZone.getDefault();
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
+    @Override
+    public boolean isBooleanFormatSet() {
+        return true;
     }
 
     @Override
-    public void setTemplateExceptionHandler(TemplateExceptionHandler 
templateExceptionHandler) {
-        super.setTemplateExceptionHandler(templateExceptionHandler);
-        templateExceptionHandlerExplicitlySet = true;
+    public String getTimeFormat() {
+        return timeFormat;
     }
 
     /**
-     * Resets the setting to its default, as if it was never set. This means 
that when you change the
-     * {@code incompatibe_improvements} setting later, the default will also 
change as appropriate. Also 
-     * {@link #isTemplateExceptionHandlerExplicitlySet()} will return {@code 
false}.
-     * 
-     * @since 2.3.22
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
      */
-    public void unsetTemplateExceptionHandler() {
-        if (templateExceptionHandlerExplicitlySet) {
-            setTemplateExceptionHandler(getDefaultTemplateExceptionHandler());
-            templateExceptionHandlerExplicitlySet = false;
-        }
+    @Override
+    public boolean isTimeFormatSet() {
+        return true;
     }
-    
-    /**
-     * Tells if {@link #setTemplateExceptionHandler(TemplateExceptionHandler)} 
(or equivalent) was already called on
-     * this instance.
-     * 
-     * @since 2.3.22
-     */
-    public boolean isTemplateExceptionHandlerExplicitlySet() {
-        return templateExceptionHandlerExplicitlySet;
-    }    
-    
-    /**
-     * @since 2.3.22
-     */
+
     @Override
-    public void setLogTemplateExceptions(boolean value) {
-        super.setLogTemplateExceptions(value);
-        logTemplateExceptionsExplicitlySet = true;
+    public String getDateFormat() {
+        return dateFormat;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected boolean getInheritedLogTemplateExceptions() {
-        throw new BugException("Missing property value");
+    public boolean isDateFormatSet() {
+        return true;
     }
 
     @Override
-    protected boolean getInheritedLazyImports() {
-        throw new BugException("Missing property value");
+    public String getDateTimeFormat() {
+        return dateTimeFormat;
     }
 
+    /**
+     * Always {@code true} in {@link Configuration}-s, so calling the 
corresponding getter is always safe.
+     */
     @Override
-    protected Boolean getInheritedLazyAutoImports() {
-        throw new BugException("Missing property value");
+    public boolean isDateTimeFormatSet() {
+        return true;
     }
 
     @Override
-    protected Map<String, String> getInheritedAutoImports() {
-        throw new BugException("Missing property value");
+

<TRUNCATED>

Reply via email to