http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configurable.java 
b/src/main/java/org/apache/freemarker/core/Configurable.java
new file mode 100644
index 0000000..a5ef09b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/Configurable.java
@@ -0,0 +1,2751 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
+import org.apache.freemarker.core.model.impl.SimpleObjectWrapper;
+import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
+import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.templateresolver.AndMatcher;
+import 
org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import 
org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
+import 
org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.NotMatcher;
+import org.apache.freemarker.core.templateresolver.OrMatcher;
+import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
+import org.apache.freemarker.core.templateresolver.PathRegexMatcher;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
+import org.apache.freemarker.core.util.FTLUtil;
+import org.apache.freemarker.core.util.GenericParseException;
+import org.apache.freemarker.core.util._NullArgumentException;
+import org.apache.freemarker.core.util._SortedArraySet;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * This is a common superclass of {@link 
org.apache.freemarker.core.Configuration},
+ * {@link org.apache.freemarker.core.Template}, and {@link Environment} 
classes.
+ * It provides settings that are common to each of them. FreeMarker
+ * uses a three-level setting hierarchy - the return value of every setting
+ * getter method on <code>Configurable</code> objects inherits its value from 
its parent 
+ * <code>Configurable</code> object, unless explicitly overridden by a call to 
a 
+ * corresponding setter method on the object itself. The parent of an 
+ * <code>Environment</code> object is a <code>Template</code> object, the
+ * parent of a <code>Template</code> object is a <code>Configuration</code>
+ * object.
+ */
+public class Configurable {
+    static final String C_TRUE_FALSE = "true,false";
+    
+    private static final String NULL = "null";
+    private static final String DEFAULT = "default";
+    private static final String JVM_DEFAULT = "JVM default";
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String LOCALE_KEY_SNAKE_CASE = "locale";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String LOCALE_KEY_CAMEL_CASE = "locale";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String LOCALE_KEY = LOCALE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String NUMBER_FORMAT_KEY_SNAKE_CASE = "number_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String NUMBER_FORMAT_KEY_CAMEL_CASE = "numberFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String NUMBER_FORMAT_KEY = 
NUMBER_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE = 
"custom_number_formats";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE = 
"customNumberFormats";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String CUSTOM_NUMBER_FORMATS_KEY = 
CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String TIME_FORMAT_KEY_SNAKE_CASE = "time_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String TIME_FORMAT_KEY_CAMEL_CASE = "timeFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String TIME_FORMAT_KEY = TIME_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String DATE_FORMAT_KEY_SNAKE_CASE = "date_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String DATE_FORMAT_KEY_CAMEL_CASE = "dateFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String DATE_FORMAT_KEY = DATE_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE = 
"custom_date_formats";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE = 
"customDateFormats";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String CUSTOM_DATE_FORMATS_KEY = 
CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String DATETIME_FORMAT_KEY_SNAKE_CASE = 
"datetime_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String DATETIME_FORMAT_KEY_CAMEL_CASE = 
"datetimeFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String DATETIME_FORMAT_KEY = 
DATETIME_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String TIME_ZONE_KEY_SNAKE_CASE = "time_zone";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String TIME_ZONE_KEY_CAMEL_CASE = "timeZone";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String TIME_ZONE_KEY = TIME_ZONE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE = 
"sql_date_and_time_time_zone";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE = 
"sqlDateAndTimeTimeZone";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String SQL_DATE_AND_TIME_TIME_ZONE_KEY = 
SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE = 
"template_exception_handler";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE = 
"templateExceptionHandler";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY = 
TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String ARITHMETIC_ENGINE_KEY_SNAKE_CASE = 
"arithmetic_engine";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String ARITHMETIC_ENGINE_KEY_CAMEL_CASE = 
"arithmeticEngine";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String ARITHMETIC_ENGINE_KEY = 
ARITHMETIC_ENGINE_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String OBJECT_WRAPPER_KEY_SNAKE_CASE = 
"object_wrapper";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String OBJECT_WRAPPER_KEY_CAMEL_CASE = "objectWrapper";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String OBJECT_WRAPPER_KEY = 
OBJECT_WRAPPER_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String BOOLEAN_FORMAT_KEY_SNAKE_CASE = 
"boolean_format";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String BOOLEAN_FORMAT_KEY_CAMEL_CASE = "booleanFormat";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String BOOLEAN_FORMAT_KEY = 
BOOLEAN_FORMAT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String OUTPUT_ENCODING_KEY_SNAKE_CASE = 
"output_encoding";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String OUTPUT_ENCODING_KEY_CAMEL_CASE = 
"outputEncoding";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String OUTPUT_ENCODING_KEY = 
OUTPUT_ENCODING_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String URL_ESCAPING_CHARSET_KEY_SNAKE_CASE = 
"url_escaping_charset";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String URL_ESCAPING_CHARSET_KEY_CAMEL_CASE = 
"urlEscapingCharset";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String URL_ESCAPING_CHARSET_KEY = 
URL_ESCAPING_CHARSET_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String AUTO_FLUSH_KEY_SNAKE_CASE = "auto_flush";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String AUTO_FLUSH_KEY_CAMEL_CASE = "autoFlush";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. @since 2.3.17 */
+    public static final String AUTO_FLUSH_KEY = AUTO_FLUSH_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE = 
"new_builtin_class_resolver";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE = 
"newBuiltinClassResolver";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. @since 2.3.17 */
+    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY = 
NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String SHOW_ERROR_TIPS_KEY_SNAKE_CASE = 
"show_error_tips";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String SHOW_ERROR_TIPS_KEY_CAMEL_CASE = 
"showErrorTips";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. @since 2.3.21 */
+    public static final String SHOW_ERROR_TIPS_KEY = 
SHOW_ERROR_TIPS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String API_BUILTIN_ENABLED_KEY_SNAKE_CASE = 
"api_builtin_enabled";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String API_BUILTIN_ENABLED_KEY_CAMEL_CASE = 
"apiBuiltinEnabled";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. @since 2.3.22 */
+    public static final String API_BUILTIN_ENABLED_KEY = 
API_BUILTIN_ENABLED_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.23 */
+    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE = 
"log_template_exceptions";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.23 */
+    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE = 
"logTemplateExceptions";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. @since 2.3.22 */
+    public static final String LOG_TEMPLATE_EXCEPTIONS_KEY = 
LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE;
+
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.25 */
+    public static final String LAZY_IMPORTS_KEY_SNAKE_CASE = "lazy_imports";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.25 */
+    public static final String LAZY_IMPORTS_KEY_CAMEL_CASE = "lazyImports";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String LAZY_IMPORTS_KEY = LAZY_IMPORTS_KEY_SNAKE_CASE;
+
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.25 */
+    public static final String LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE = 
"lazy_auto_imports";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.25 */
+    public static final String LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE = 
"lazyAutoImports";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String LAZY_AUTO_IMPORTS_KEY = 
LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.25 */
+    public static final String AUTO_IMPORT_KEY_SNAKE_CASE = "auto_import";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.25 */
+    public static final String AUTO_IMPORT_KEY_CAMEL_CASE = "autoImport";
+    /** Alias to the {@code ..._SNAKE_CASE} variation due to backward 
compatibility constraints. */
+    public static final String AUTO_IMPORT_KEY = AUTO_IMPORT_KEY_SNAKE_CASE;
+    
+    /** Legacy, snake case ({@code like_this}) variation of the setting name. 
@since 2.3.25 */
+    public static final String AUTO_INCLUDE_KEY_SNAKE_CASE = "auto_include";
+    /** Modern, camel case ({@code likeThis}) variation of the setting name. 
@since 2.3.25 */
+    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;
+    
+    private static final String[] SETTING_NAMES_SNAKE_CASE = new String[] {
+        // Must be sorted alphabetically!
+        API_BUILTIN_ENABLED_KEY_SNAKE_CASE,
+        ARITHMETIC_ENGINE_KEY_SNAKE_CASE,
+        AUTO_FLUSH_KEY_SNAKE_CASE,
+        AUTO_IMPORT_KEY_SNAKE_CASE,
+        AUTO_INCLUDE_KEY_SNAKE_CASE,
+        BOOLEAN_FORMAT_KEY_SNAKE_CASE,
+        CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
+        CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
+        DATE_FORMAT_KEY_SNAKE_CASE,
+        DATETIME_FORMAT_KEY_SNAKE_CASE,
+        LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE,
+        LAZY_IMPORTS_KEY_SNAKE_CASE,
+        LOCALE_KEY_SNAKE_CASE,
+        LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE,
+        NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE,
+        NUMBER_FORMAT_KEY_SNAKE_CASE,
+        OBJECT_WRAPPER_KEY_SNAKE_CASE,
+        OUTPUT_ENCODING_KEY_SNAKE_CASE,
+        SHOW_ERROR_TIPS_KEY_SNAKE_CASE,
+        SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE,
+        TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE,
+        TIME_FORMAT_KEY_SNAKE_CASE,
+        TIME_ZONE_KEY_SNAKE_CASE,
+        URL_ESCAPING_CHARSET_KEY_SNAKE_CASE
+    };
+    
+    private static final String[] SETTING_NAMES_CAMEL_CASE = new String[] {
+        // Must be sorted alphabetically!
+        API_BUILTIN_ENABLED_KEY_CAMEL_CASE,
+        ARITHMETIC_ENGINE_KEY_CAMEL_CASE,
+        AUTO_FLUSH_KEY_CAMEL_CASE,
+        AUTO_IMPORT_KEY_CAMEL_CASE,
+        AUTO_INCLUDE_KEY_CAMEL_CASE,
+        BOOLEAN_FORMAT_KEY_CAMEL_CASE,
+        CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
+        CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
+        DATE_FORMAT_KEY_CAMEL_CASE,
+        DATETIME_FORMAT_KEY_CAMEL_CASE,
+        LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE,
+        LAZY_IMPORTS_KEY_CAMEL_CASE,
+        LOCALE_KEY_CAMEL_CASE,
+        LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE,
+        NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE,
+        NUMBER_FORMAT_KEY_CAMEL_CASE,
+        OBJECT_WRAPPER_KEY_CAMEL_CASE,
+        OUTPUT_ENCODING_KEY_CAMEL_CASE,
+        SHOW_ERROR_TIPS_KEY_CAMEL_CASE,
+        SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
+        TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE,
+        TIME_FORMAT_KEY_CAMEL_CASE,
+        TIME_ZONE_KEY_CAMEL_CASE,
+        URL_ESCAPING_CHARSET_KEY_CAMEL_CASE
+    };
+
+    private Configurable parent;
+    private HashMap<Object, Object> customAttributes;
+    
+    private Locale locale;
+    private String numberFormat;
+    private String timeFormat;
+    private String dateFormat;
+    private String dateTimeFormat;
+    private TimeZone timeZone;
+    private TimeZone sqlDataAndTimeTimeZone;
+    private boolean sqlDataAndTimeTimeZoneSet;
+    private String booleanFormat;
+    private String trueStringValue;  // deduced from booleanFormat
+    private String falseStringValue;  // deduced from booleanFormat
+    private TemplateExceptionHandler templateExceptionHandler;
+    private ArithmeticEngine arithmeticEngine;
+    private ObjectWrapper objectWrapper;
+    private String outputEncoding;
+    private boolean outputEncodingSet;
+    private String urlEscapingCharset;
+    private boolean urlEscapingCharsetSet;
+    private Boolean autoFlush;
+    private TemplateClassResolver newBuiltinClassResolver;
+    private Boolean showErrorTips;
+    private Boolean apiBuiltinEnabled;
+    private Boolean logTemplateExceptions;
+    private Map<String, ? extends TemplateDateFormatFactory> customDateFormats;
+    private Map<String, ? extends TemplateNumberFormatFactory> 
customNumberFormats;
+    private LinkedHashMap<String, String> autoImports;
+    private ArrayList<String> autoIncludes;
+    private Boolean lazyImports;
+    private Boolean lazyAutoImports;
+    private boolean lazyAutoImportsSet;
+    
+    /**
+     * Intended to be called from inside FreeMarker only.
+     * Creates a top-level configurable, one that doesn't inherit from a 
parent, and thus stores the default values.
+     * Called by the {@link Configuration} constructor.
+     */
+    protected Configurable(Version incompatibleImprovements) {
+        _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+        parent = null;
+        locale = Locale.getDefault();
+        timeZone = TimeZone.getDefault();
+        sqlDataAndTimeTimeZone = null;
+        numberFormat = "number";
+        timeFormat = "";
+        dateFormat = "";
+        dateTimeFormat = "";
+        templateExceptionHandler = 
_TemplateAPI.getDefaultTemplateExceptionHandler(
+                incompatibleImprovements);
+        arithmeticEngine = ArithmeticEngine.BIGDECIMAL_ENGINE;
+        objectWrapper = 
Configuration.getDefaultObjectWrapper(incompatibleImprovements);
+        autoFlush = Boolean.TRUE;
+        newBuiltinClassResolver = TemplateClassResolver.UNRESTRICTED_RESOLVER;
+        showErrorTips = Boolean.TRUE;
+        apiBuiltinEnabled = Boolean.FALSE;
+        logTemplateExceptions = Boolean.FALSE;
+        // outputEncoding and urlEscapingCharset defaults to null,
+        // which means "not specified"
+        setBooleanFormat(C_TRUE_FALSE);
+        
+        customAttributes = new HashMap();
+        
+        customDateFormats = Collections.emptyMap();
+        customNumberFormats = Collections.emptyMap();
+        
+        lazyImports = false;
+        lazyAutoImportsSet = true;
+        
+        initAutoImportsMap();
+        initAutoIncludesList();
+    }
+
+    /**
+     * Creates a new instance. Normally you do not need to use this 
constructor,
+     * as you don't use <code>Configurable</code> directly, but its subclasses.
+     */
+    protected Configurable(Configurable parent) {
+        this.parent = parent;
+        locale = null;
+        numberFormat = null;
+        templateExceptionHandler = null;
+        customAttributes = new HashMap(0);
+    }
+    
+    /**
+     * Returns the parent {@link Configurable} object of this object. The 
parent stores the default setting values for
+     * this {@link Configurable}. For example, the parent of a {@link 
org.apache.freemarker.core.Template} object is a
+     * {@link Configuration} object, so values not specified on {@link 
Template}-level are get from the
+     * {@link Configuration} object.
+     * 
+     * <p>
+     * Note on the parent of {@link Environment}: If you set {@link 
Configuration#setIncompatibleImprovements(Version)
+     * incompatible_improvements} to at least 2.3.22, it will be always the 
"main" {@link Template}, that is, the
+     * template for whose processing the {@link Environment} was created. With 
lower {@code incompatible_improvements},
+     * the current parent can temporary change <em>during template 
execution</em>, for example when your are inside an
+     * {@code #include}-d template (among others). Thus, don't build on which 
{@link Template} the parent of
+     * {@link Environment} is during template execution, unless you set {@code 
incompatible_improvements} to 2.3.22 or
+     * higher.
+     *
+     * @return The parent {@link Configurable} object, or {@code null} if this 
is the root {@link Configurable} object
+     *         (i.e, if it's the {@link Configuration} object).
+     */
+    public final Configurable getParent() {
+        return parent;
+    }
+    
+    /**
+     * Reparenting support. This is used by Environment when it includes a
+     * template - the included template becomes the parent configurable during
+     * its evaluation.
+     */
+    void setParent(Configurable parent) {
+        this.parent = parent;
+    }
+    
+    /**
+     * Sets the default locale used for number and date formatting (among 
others), also the locale used for searching
+     * localized template variations when no locale was explicitly requested.
+     * 
+     * @see Configuration#getTemplate(String, Locale)
+     */
+    public void setLocale(Locale locale) {
+        _NullArgumentException.check("locale", locale);
+        this.locale = locale;
+    }
+
+    /**
+     * Returns the assumed locale when searching for template files with no
+     * explicit requested locale. Defaults to system locale.
+     */
+    public Locale getLocale() {
+        return locale != null ? locale : parent.getLocale();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isLocaleSet() {
+        return locale != null;
+    }
+    
+    /**
+     * Sets the time zone to use when formatting date/time values.
+     * Defaults to the system time zone ({@link TimeZone#getDefault()}), 
regardless of the "locale" FreeMarker setting,
+     * so in a server application you probably want to set it explicitly in 
the {@link Environment} to match the
+     * preferred time zone of target audience (like the Web page visitor).
+     * 
+     * <p>If you or the templates set the time zone, you should probably also 
set
+     * {@link #setSQLDateAndTimeTimeZone(TimeZone)}!
+     * 
+     * @see #setSQLDateAndTimeTimeZone(TimeZone)
+     */
+    public void setTimeZone(TimeZone timeZone) {
+        _NullArgumentException.check("timeZone", timeZone);
+        this.timeZone = timeZone;
+    }
+
+    /**
+     * The getter pair of {@link #setTimeZone(TimeZone)}. 
+     */
+    public TimeZone getTimeZone() {
+        return timeZone != null ? timeZone : parent.getTimeZone();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isTimeZoneSet() {
+        return timeZone != null;
+    }
+    
+    /**
+     * Sets the time zone used when dealing with {@link java.sql.Date 
java.sql.Date} and
+     * {@link java.sql.Time java.sql.Time} values. It defaults to {@code null} 
for backward compatibility, but in most
+     * application this should be set to the JVM default time zone (server 
default time zone), because that's what
+     * most JDBC drivers will use when constructing the {@link java.sql.Date 
java.sql.Date} and
+     * {@link java.sql.Time java.sql.Time} values. If this setting is {@code 
null}, FreeMarker will use the value of
+     * ({@link #getTimeZone()}) for {@link java.sql.Date java.sql.Date} and 
{@link java.sql.Time java.sql.Time} values,
+     * which often gives bad results.
+     * 
+     * <p>This setting doesn't influence the formatting of other kind of 
values (like of
+     * {@link java.sql.Timestamp java.sql.Timestamp} or plain {@link 
java.util.Date java.util.Date} values).
+     * 
+     * <p>To decide what value you need, a few things has to be understood:
+     * <ul>
+     *   <li>Date-only and time-only values in SQL-oriented databases are 
usually store calendar and clock field
+     *   values directly (year, month, day, or hour, minute, seconds (with 
decimals)), as opposed to a set of points
+     *   on the physical time line. Thus, unlike SQL timestamps, these values 
usually aren't meant to be shown
+     *   differently depending on the time zone of the audience.
+     *   
+     *   <li>When a JDBC query has to return a date-only or time-only value, 
it has to convert it to a point on the
+     *   physical time line, because that's what {@link java.util.Date} and 
its subclasses store (milliseconds since
+     *   the epoch). Obviously, this is impossible to do. So JDBC just chooses 
a physical time which, when rendered
+     *   <em>with the JVM default time zone</em>, will give the same field 
values as those stored
+     *   in the database. (Actually, you can give JDBC a calendar, and so it 
can use other time zones too, but most
+     *   application won't care using those overloads.) For example, assume 
that the system time zone is GMT+02:00.
+     *   Then, 2014-07-12 in the database will be translated to physical time 
2014-07-11 22:00:00 UTC, because that
+     *   rendered in GMT+02:00 gives 2014-07-12 00:00:00. Similarly, 11:57:00 
in the database will be translated to
+     *   physical time 1970-01-01 09:57:00 UTC. Thus, the physical time stored 
in the returned value depends on the
+     *   default system time zone of the JDBC client, not just on the content 
in the database. (This used to be the
+     *   default behavior of ORM-s, like Hibernate, too.)
+     *   
+     *   <li>The value of the {@code time_zone} FreeMarker configuration 
setting sets the time zone used for the
+     *   template output. For example, when a web page visitor has a preferred 
time zone, the web application framework
+     *   may calls {@link Environment#setTimeZone(TimeZone)} with that time 
zone. Thus, the visitor will
+     *   see {@link java.sql.Timestamp java.sql.Timestamp} and plain {@link 
java.util.Date java.util.Date} values as
+     *   they look in his own time zone. While
+     *   this is desirable for those types, as they meant to represent 
physical points on the time line, this is not
+     *   necessarily desirable for date-only and time-only values. When {@code 
sql_date_and_time_time_zone} is
+     *   {@code null}, {@code time_zone} is used for rendering all kind of 
date/time/dateTime values, including
+     *   {@link java.sql.Date java.sql.Date} and {@link java.sql.Time 
java.sql.Time}, and then if, for example,
+     *   {@code time_zone} is GMT+00:00, the
+     *   values from the earlier examples will be shown as 2014-07-11 (one day 
off) and 09:57:00 (2 hours off). While
+     *   those are the time zone correct renderings, those values are probably 
meant to be shown "as is".
+     *   
+     *   <li>You may wonder why this setting isn't simply "SQL time zone", 
since the time zone related behavior of JDBC
+     *   applies to {@link java.sql.Timestamp java.sql.Timestamp} too. 
FreeMarker assumes that you have set up your
+     *   application so that time stamps coming from the database go through 
the necessary conversion to store the
+     *   correct distance from the epoch (1970-01-01 00:00:00 UTC), as 
requested by {@link java.util.Date}. In that case
+     *   the time stamp can be safely rendered in different time zones, and 
thus it needs no special treatment.
+     * </ul>
+     * 
+     * @param tz Maybe {@code null}, in which case {@link java.sql.Date 
java.sql.Date} and
+     *          {@link java.sql.Time java.sql.Time} values will be formatted 
in the time zone returned by
+     *          {@link #getTimeZone()}.
+     *          (Note that since {@code null} is an allowed value for this 
setting, it will not cause
+     *          {@link #getSQLDateAndTimeTimeZone()} to fall back to the 
parent configuration.)
+     * 
+     * @see #setTimeZone(TimeZone)
+     * 
+     * @since 2.3.21
+     */
+    public void setSQLDateAndTimeTimeZone(TimeZone tz) {
+        sqlDataAndTimeTimeZone = tz;
+        sqlDataAndTimeTimeZoneSet = true;
+    }
+    
+    /**
+     * The getter pair of {@link #setSQLDateAndTimeTimeZone(TimeZone)}.
+     * 
+     * @return {@code null} if the value of {@link #getTimeZone()} should be 
used for formatting
+     *     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time 
java.sql.Time} values, otherwise the time zone
+     *     that should be used to format the values of those two types.  
+     * 
+     * @since 2.3.21
+     */
+    public TimeZone getSQLDateAndTimeTimeZone() {
+        return sqlDataAndTimeTimeZoneSet
+                ? sqlDataAndTimeTimeZone
+                : (parent != null ? parent.getSQLDateAndTimeTimeZone() : null);
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isSQLDateAndTimeTimeZoneSet() {
+        return sqlDataAndTimeTimeZoneSet;
+    }
+
+    /**
+     * Sets the default number format used to convert numbers to strings. 
Currently, this is one of these:
+     * <ul>
+     *   <li>{@code "number"}: The number format returned by {@link 
NumberFormat#getNumberInstance(Locale)}</li>
+     *   <li>{@code "currency"}: The number format returned by {@link 
NumberFormat#getCurrencyInstance(Locale)}</li>
+     *   <li>{@code "percent"}: The number format returned by {@link 
NumberFormat#getPercentInstance(Locale)}</li>
+     *   <li>{@code "computer"}: The number format used by FTL's {@code c} 
built-in (like in {@code someNumber?c}).</li>
+     *   <li>{@link java.text.DecimalFormat} pattern (like {@code "0.##"}). 
This syntax has a FreeMarker-specific
+     *       extension, so that you can specify options like the rounding mode 
and the symbols used in this string. For
+     *       example, {@code ",000;; roundingMode=halfUp groupingSeparator=_"} 
will format numbers like {@code ",000"}
+     *       would, but with half-up rounding mode, and {@code _} as the group 
separator. See more about "extended Java
+     *       decimal format" in the FreeMarker Manual.
+     *       </li>
+     *   <li>If the string starts with {@code @} character followed by a 
letter then it's interpreted as a custom number
+     *       format, but only if either {@link 
Configuration#getIncompatibleImprovements()} is at least 2.3.24, or
+     *       there's any custom formats defined (even if custom 
date/time/dateTime format). The format of a such string
+     *       is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> 
<i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by 
{@link #setCustomNumberFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom {@link 
TemplateNumberFormat}.
+     *   </li>
+     * </ul>
+     * 
+     *   
+     * <p>Defaults to <tt>"number"</tt>.
+     */
+    public void setNumberFormat(String numberFormat) {
+        _NullArgumentException.check("numberFormat", numberFormat);
+        this.numberFormat = numberFormat;
+    }
+    
+    /**
+     * Getter pair of {@link #setNumberFormat(String)}. 
+     */
+    public String getNumberFormat() {
+        return numberFormat != null ? numberFormat : parent.getNumberFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isNumberFormatSet() {
+        return numberFormat != null;
+    }
+    
+    /**
+     * Getter pair of {@link #setCustomNumberFormats(Map)}; do not modify the 
returned {@link Map}! To be consistent
+     * with other setting getters, if this setting was set directly on this 
{@link Configurable} object, this simply
+     * returns that value, otherwise it returns the value from the parent 
{@link Configurable}. So beware, the returned
+     * value doesn't reflect the {@link Map} key granularity fallback logic 
that FreeMarker actually uses for this
+     * setting (for that, use {@link #getCustomNumberFormat(String)}). The 
returned value isn't a snapshot; it may or
+     * may not shows the changes later made to this setting on this {@link 
Configurable} level (but usually it's well
+     * defined if until what point settings are possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link 
Configuration} (top) level, it defaults to an empty
+     * {@link Map}.
+     * 
+     * @see #getCustomNumberFormatsWithoutFallback()
+     * 
+     * @since 2.3.24
+     */
+    public Map<String, ? extends TemplateNumberFormatFactory> 
getCustomNumberFormats() {
+        return customNumberFormats == null ? parent.getCustomNumberFormats() : 
customNumberFormats;
+    }
+
+    /**
+     * Like {@link #getCustomNumberFormats()}, but doesn't fall back to the 
parent {@link Configurable}, nor does it
+     * provide a non-{@code null} default when called as the method of a 
{@link Configuration}.
+     * 
+     * @since 2.3.25
+     */
+    public Map<String, ? extends TemplateNumberFormatFactory> 
getCustomNumberFormatsWithoutFallback() {
+        return customNumberFormats;
+    }
+    
+    /**
+     * Associates names with formatter factories, which then can be referred 
by the {@link #setNumberFormat(String)
+     * number_format} setting with values starting with 
<code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} followed by a letter will have 
special meaning in number/date/time/datetime
+     * format strings, even if {@link 
Configuration#getIncompatibleImprovements() incompatible_improvements} is less
+     * than 2.3.24 (starting with {@link 
Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
+     * {@code @} always has special meaning).
+     * 
+     * @param customNumberFormats
+     *            Can't be {@code null}. The name must start with an UNICODE 
letter, and can only contain UNICODE
+     *            letters and digits (not {@code _}).
+     * 
+     * @since 2.3.24
+     */
+    public void setCustomNumberFormats(Map<String, ? extends 
TemplateNumberFormatFactory> customNumberFormats) {
+        _NullArgumentException.check("customNumberFormats", 
customNumberFormats);
+        validateFormatNames(customNumberFormats.keySet());
+        this.customNumberFormats = customNumberFormats;
+    }
+    
+    private void validateFormatNames(Set<String> keySet) {
+        for (String name : keySet) {
+            if (name.length() == 0) {
+                throw new IllegalArgumentException("Format names can't be 0 
length");
+            }
+            char firstChar = name.charAt(0);
+            if (firstChar == '@') {
+                throw new IllegalArgumentException(
+                        "Format names can't start with '@'. '@' is only used 
when referring to them from format "
+                        + "strings. In: " + name);
+            }
+            if (!Character.isLetter(firstChar)) {
+                throw new IllegalArgumentException("Format name must start 
with letter: " + name);
+            }
+            for (int i = 1; i < name.length(); i++) {
+                // Note that we deliberately don't allow "_" here.
+                if (!Character.isLetterOrDigit(name.charAt(i))) {
+                    throw new IllegalArgumentException("Format name can only 
contain letters and digits: " + name);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isCustomNumberFormatsSet() {
+        return customNumberFormats != null;
+    }
+
+    /**
+     * Gets the custom name format registered for the name.
+     * 
+     * @since 2.3.24
+     */
+    public TemplateNumberFormatFactory getCustomNumberFormat(String name) {
+        TemplateNumberFormatFactory r;
+        if (customNumberFormats != null) {
+            r = customNumberFormats.get(name);
+            if (r != null) {
+                return r;
+            }
+        }
+        return parent != null ? parent.getCustomNumberFormat(name) : null;
+    }
+    
+    /**
+     * Tells if this configurable object or its parent defines any custom 
formats.
+     * 
+     * @since 2.3.24
+     */
+    public boolean hasCustomFormats() {
+        return customNumberFormats != null && !customNumberFormats.isEmpty()
+                || customDateFormats != null && !customDateFormats.isEmpty()
+                || getParent() != null && getParent().hasCustomFormats(); 
+    }
+    
+    /**
+     * The string value for the boolean {@code true} and {@code false} values, 
intended for human audience (not for a
+     * computer language), separated with comma. For example, {@code 
"yes,no"}. Note that white-space is significant,
+     * so {@code "yes, no"} is WRONG (unless you want that leading space 
before "no").
+     * 
+     * <p>For backward compatibility the default is {@code "true,false"}, but 
using that value is denied for automatic
+     * boolean-to-string conversion (like <code>${myBoolean}</code> will fail 
with it), only {@code myBool?string} will
+     * allow it, which is deprecated since FreeMarker 2.3.20.
+     * 
+     * <p>Note that automatic boolean-to-string conversion only exists since 
FreeMarker 2.3.20. Earlier this setting
+     * only influenced the result of {@code myBool?string}. 
+     */
+    public void setBooleanFormat(String booleanFormat) {
+        _NullArgumentException.check("booleanFormat", booleanFormat);
+        
+        int commaIdx = booleanFormat.indexOf(',');
+        if (commaIdx == -1) {
+            throw new IllegalArgumentException(
+                    "Setting value must be string that contains two 
comma-separated values for true and false, " +
+                    "respectively.");
+        }
+        
+        this.booleanFormat = booleanFormat; 
+        
+        if (booleanFormat.equals(C_TRUE_FALSE)) {
+            // C_TRUE_FALSE is the default for BC, but it's not a good default 
for human audience formatting, so we
+            // pretend that it wasn't set.
+            trueStringValue = null; 
+            falseStringValue = null;
+        } else {
+            trueStringValue = booleanFormat.substring(0, commaIdx); 
+            falseStringValue = booleanFormat.substring(commaIdx + 1);
+        }
+    }
+    
+    /**
+     * The getter pair of {@link #setBooleanFormat(String)}.
+     */
+    public String getBooleanFormat() {
+        return booleanFormat != null ? booleanFormat : 
parent.getBooleanFormat(); 
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isBooleanFormatSet() {
+        return booleanFormat != null;
+    }
+        
+    String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws 
TemplateException {
+        if (value) {
+            String s = getTrueStringValue();
+            if (s == null) {
+                if (fallbackToTrueFalse) {
+                    return MiscUtil.C_TRUE;
+                } else {
+                    throw new 
_MiscTemplateException(getNullBooleanFormatErrorDescription());
+                }
+            } else {
+                return s;
+            }
+        } else {
+            String s = getFalseStringValue();
+            if (s == null) {
+                if (fallbackToTrueFalse) {
+                    return MiscUtil.C_FALSE;
+                } else {
+                    throw new 
_MiscTemplateException(getNullBooleanFormatErrorDescription());
+                }
+            } else {
+                return s;
+            }
+        }
+    }
+
+    private _ErrorDescriptionBuilder getNullBooleanFormatErrorDescription() {
+        return new _ErrorDescriptionBuilder(
+                "Can't convert boolean to string automatically, because the 
\"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
+                new _DelayedJQuote(getBooleanFormat()), 
+                (getBooleanFormat().equals(C_TRUE_FALSE)
+                    ? ", which is the legacy default computer-language format, 
and hence isn't accepted."
+                    : ".")
+                ).tips(
+                     "If you just want \"true\"/\"false\" result as you are 
generting computer-language output, "
+                     + "use \"?c\", like ${myBool?c}.",
+                     "You can write myBool?string('yes', 'no') and like to 
specify boolean formatting in place.",
+                     new Object[] {
+                         "If you need the same two values on most places, the 
programmers should set the \"",
+                         BOOLEAN_FORMAT_KEY ,"\" setting to something like 
\"yes,no\"." }
+                 );
+    }
+
+    /**
+     * Returns the string to which {@code true} is converted to for human 
audience, or {@code null} if automatic
+     * coercion to string is not allowed. The default value is {@code null}.
+     * 
+     * <p>This value is deduced from the {@code "boolean_format"} setting.
+     * Confusingly, for backward compatibility (at least until 2.4) that 
defaults to {@code "true,false"}, yet this
+     * defaults to {@code null}. That's so because {@code "true,false"} is 
treated exceptionally, as that default is a
+     * historical mistake in FreeMarker, since it targets computer language 
output, not human writing. Thus it's
+     * ignored.
+     * 
+     * @since 2.3.20
+     */
+    String getTrueStringValue() {
+        // The first step deliberately tests booleanFormat instead of 
trueStringValue! 
+        return booleanFormat != null ? trueStringValue : (parent != null ? 
parent.getTrueStringValue() : null); 
+    }
+
+    /**
+     * Same as {@link #getTrueStringValue()} but with {@code false}. 
+     * @since 2.3.20
+     */
+    String getFalseStringValue() {
+        // The first step deliberately tests booleanFormat instead of 
falseStringValue! 
+        return booleanFormat != null ? falseStringValue : (parent != null ? 
parent.getFalseStringValue() : null); 
+    }
+
+    /**
+     * Sets the format used to convert {@link java.util.Date}-s to string-s 
that are time (no date part) values,
+     * also the format that {@code someString?time} will use to parse strings.
+     * 
+     * <p>For the possible values see {@link #setDateTimeFormat(String)}.
+     *   
+     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", 
which is currently {@code "medium"}.
+     */
+    public void setTimeFormat(String timeFormat) {
+        _NullArgumentException.check("timeFormat", timeFormat);
+        this.timeFormat = timeFormat;
+    }
+
+    /**
+     * The getter pair of {@link #setTimeFormat(String)}.
+     */
+    public String getTimeFormat() {
+        return timeFormat != null ? timeFormat : parent.getTimeFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isTimeFormatSet() {
+        return timeFormat != null;
+    }
+    
+    /**
+     * Sets the format used to convert {@link java.util.Date}-s to string-s 
that are date (no time part) values,
+     * also the format that {@code someString?date} will use to parse strings.
+     * 
+     * <p>For the possible values see {@link #setDateTimeFormat(String)}.
+     *   
+     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", 
which is currently {@code "medium"}.
+     */
+    public void setDateFormat(String dateFormat) {
+        _NullArgumentException.check("dateFormat", dateFormat);
+        this.dateFormat = dateFormat;
+    }
+
+    /**
+     * The getter pair of {@link #setDateFormat(String)}.
+     */
+    public String getDateFormat() {
+        return dateFormat != null ? dateFormat : parent.getDateFormat();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isDateFormatSet() {
+        return dateFormat != null;
+    }
+    
+    /**
+     * Sets the format used to convert {@link java.util.Date}-s to string-s 
that are date-time (timestamp) values,
+     * also the format that {@code someString?datetime} will use to parse 
strings.
+     * 
+     * <p>The possible setting values are (the quotation marks aren't part of 
the value itself):
+     * 
+     * <ul>
+     *   <li><p>Patterns accepted by Java's {@link SimpleDateFormat}, for 
example {@code "dd.MM.yyyy HH:mm:ss"} (where
+     *       {@code HH} means 24 hours format) or {@code "MM/dd/yyyy hh:mm:ss 
a"} (where {@code a} prints AM or PM, if
+     *       the current language is English).
+     *   
+     *   <li><p>{@code "xs"} for XML Schema format, or {@code "iso"} for ISO 
8601:2004 format.
+     *       These formats allow various additional options, separated with 
space, like in
+     *       {@code "iso m nz"} (or with {@code _}, like in {@code 
"iso_m_nz"}; this is useful in a case like
+     *       {@code lastModified?string.iso_m_nz}). The options and their 
meanings are:
+     *       
+     *       <ul>
+     *         <li><p>Accuracy options:<br>
+     *             {@code ms} = Milliseconds, always shown with all 3 digits, 
even if it's all 0-s.
+     *                     Example: {@code 13:45:05.800}<br>
+     *             {@code s} = Seconds (fraction seconds are dropped even if 
non-0), like {@code 13:45:05}<br>
+     *             {@code m} = Minutes, like {@code 13:45}. This isn't allowed 
for "xs".<br>
+     *             {@code h} = Hours, like {@code 13}. This isn't allowed for 
"xs".<br>
+     *             Neither = Up to millisecond accuracy, but trailing 
millisecond 0-s are removed, also the whole
+     *                     milliseconds part if it would be 0 otherwise. 
Example: {@code 13:45:05.8}
+     *                     
+     *         <li><p>Time zone offset visibility options:<br>
+     *             {@code fz} = "Force Zone", always show time zone offset 
(even for for
+     *                     {@link java.sql.Date java.sql.Date} and {@link 
java.sql.Time java.sql.Time} values).
+     *                     But, because ISO 8601 doesn't allow for dates 
(means date without time of the day) to
+     *                     show the zone offset, this option will have no 
effect in the case of {@code "iso"} with
+     *                     dates.<br>
+     *             {@code nz} = "No Zone", never show time zone offset<br>
+     *             Neither = always show time zone offset, except for {@link 
java.sql.Date java.sql.Date}
+     *                     and {@link java.sql.Time java.sql.Time}, and for 
{@code "iso"} date values.
+     *                     
+     *         <li><p>Time zone options:<br>
+     *             {@code u} = Use UTC instead of what the {@code time_zone} 
setting suggests. However,
+     *                     {@link java.sql.Date java.sql.Date} and {@link 
java.sql.Time java.sql.Time} aren't affected
+     *                     by this (see {@link 
#setSQLDateAndTimeTimeZone(TimeZone)} to understand why)<br>
+     *             {@code fu} = "Force UTC", that is, use UTC instead of what 
the {@code time_zone} or the
+     *                     {@code sql_date_and_time_time_zone} setting 
suggests. This also effects
+     *                     {@link java.sql.Date java.sql.Date} and {@link 
java.sql.Time java.sql.Time} values<br>
+     *             Neither = Use the time zone suggested by the {@code 
time_zone} or the
+     *                     {@code sql_date_and_time_time_zone} configuration 
setting ({@link #setTimeZone(TimeZone)} and
+     *                     {@link #setSQLDateAndTimeTimeZone(TimeZone)}).
+     *       </ul>
+     *       
+     *       <p>The options can be specified in any order.</p>
+     *       
+     *       <p>Options from the same category are mutually exclusive, like 
using {@code m} and {@code s}
+     *       together is an error.
+     *       
+     *       <p>The accuracy and time zone offset visibility options don't 
influence parsing, only formatting.
+     *       For example, even if you use "iso m nz", 
"2012-01-01T15:30:05.125+01" will be parsed successfully and with
+     *       milliseconds accuracy.
+     *       The time zone options (like "u") influence what time zone is 
chosen only when parsing a string that doesn't
+     *       contain time zone offset.
+     *       
+     *       <p>Parsing with {@code "iso"} understands both extend format and 
basic format, like
+     *       {@code 20141225T235018}. It doesn't, however, support the parsing 
of all kind of ISO 8601 strings: if
+     *       there's a date part, it must use year, month and day of the month 
values (not week of the year), and the
+     *       day can't be omitted.
+     *       
+     *       <p>The output of {@code "iso"} is deliberately so that it's also 
a good representation of the value with
+     *       XML Schema format, except for 0 and negative years, where it's 
impossible. Also note that the time zone
+     *       offset is omitted for date values in the {@code "iso"} format, 
while it's preserved for the {@code "xs"}
+     *       format.
+     *       
+     *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code 
"full"}, which that has locale-dependent
+     *       meaning defined by the Java platform (see in the documentation of 
{@link java.text.DateFormat}).
+     *       For date-time values, you can specify the length of the date and 
time part independently, be separating
+     *       them with {@code _}, like {@code "short_medium"}. ({@code 
"medium"} means
+     *       {@code "medium_medium"} for date-time values.)
+     *       
+     *   <li><p>Anything that starts with {@code "@"} followed by a letter is 
interpreted as a custom
+     *       date/time/dateTime format, but only if either {@link 
Configuration#getIncompatibleImprovements()}
+     *       is at least 2.3.24, or there's any custom formats defined (even 
if custom number format). The format of
+     *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> 
<i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by 
{@link #setCustomDateFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom number 
format.
+     *       
+     * </ul> 
+     * 
+     * <p>Defaults to {@code ""}, which means "use the FreeMarker default", 
which is currently {@code "medium_medium"}.
+     */
+    public void setDateTimeFormat(String dateTimeFormat) {
+        _NullArgumentException.check("dateTimeFormat", dateTimeFormat);
+        this.dateTimeFormat = dateTimeFormat;
+    }
+
+    /**
+     * The getter pair of {@link #setDateTimeFormat(String)}.
+     */
+    public String getDateTimeFormat() {
+        return dateTimeFormat != null ? dateTimeFormat : 
parent.getDateTimeFormat();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isDateTimeFormatSet() {
+        return dateTimeFormat != null;
+    }
+    
+    /**
+     * Getter pair of {@link #setCustomDateFormats(Map)}; do not modify the 
returned {@link Map}! To be consistent with
+     * other setting getters, if this setting was set directly on this {@link 
Configurable} object, this simply returns
+     * that value, otherwise it returns the value from the parent {@link 
Configurable}. So beware, the returned value
+     * doesn't reflect the {@link Map} key granularity fallback logic that 
FreeMarker actually uses for this setting
+     * (for that, use {@link #getCustomDateFormat(String)}). The returned 
value isn't a snapshot; it may or may not
+     * shows the changes later made to this setting on this {@link 
Configurable} level (but usually it's well defined if
+     * until what point settings are possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link 
Configuration} (top) level, it defaults to an empty
+     * {@link Map}.
+     * 
+     * @see #getCustomDateFormatsWithoutFallback()
+     * 
+     * @since 2.3.24
+     */
+    public Map<String, ? extends TemplateDateFormatFactory> 
getCustomDateFormats() {
+        return customDateFormats == null ? parent.getCustomDateFormats() : 
customDateFormats;
+    }
+
+    /**
+     * Like {@link #getCustomDateFormats()}, but doesn't fall back to the 
parent {@link Configurable}, nor does it
+     * provide a non-{@code null} default when called as the method of a 
{@link Configuration}.
+     * 
+     * @since 2.3.25
+     */
+    public Map<String, ? extends TemplateDateFormatFactory> 
getCustomDateFormatsWithoutFallback() {
+        return customDateFormats;
+    }
+    
+    /**
+     * Associates names with formatter factories, which then can be referred 
by the {@link #setDateTimeFormat(String)
+     * date_format}, {@link #setDateTimeFormat(String) time_format}, and 
{@link #setDateTimeFormat(String)
+     * datetime_format} settings with values starting with 
<code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} followed by a letter will have 
special meaning in number/date/time/datetime
+     * format strings, even if {@link 
Configuration#getIncompatibleImprovements() incompatible_improvements} is less
+     * than 2.3.24 (starting with {@link 
Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
+     * {@code @} always has special meaning).
+     *
+     * @param customDateFormats
+     *            Can't be {@code null}. The name must start with an UNICODE 
letter, and can only contain UNICODE
+     *            letters and digits.
+     * 
+     * @since 2.3.24
+     */
+    public void setCustomDateFormats(Map<String, ? extends 
TemplateDateFormatFactory> customDateFormats) {
+        _NullArgumentException.check("customDateFormats", customDateFormats);
+        validateFormatNames(customDateFormats.keySet());
+        this.customDateFormats = customDateFormats;
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.24
+     */
+    public boolean isCustomDateFormatsSet() {
+        return customDateFormats != null;
+    }
+
+    /**
+     * Gets the custom name format registered for the name.
+     * 
+     * @since 2.3.24
+     */
+    public TemplateDateFormatFactory getCustomDateFormat(String name) {
+        TemplateDateFormatFactory r;
+        if (customDateFormats != null) {
+            r = customDateFormats.get(name);
+            if (r != null) {
+                return r;
+            }
+        }
+        return parent != null ? parent.getCustomDateFormat(name) : null;
+    }
+    
+    /**
+     * Sets the exception handler used to handle exceptions occurring inside 
templates.
+     * The default is {@link TemplateExceptionHandler#DEBUG_HANDLER}. The 
recommended values are:
+     * 
+     * <ul>
+     *   <li>In production systems: {@link 
TemplateExceptionHandler#RETHROW_HANDLER}
+     *   <li>During development of HTML templates: {@link 
TemplateExceptionHandler#HTML_DEBUG_HANDLER}
+     *   <li>During development of non-HTML templates: {@link 
TemplateExceptionHandler#DEBUG_HANDLER}
+     * </ul>
+     * 
+     * <p>All of these will let the exception propagate further, so that you 
can catch it around
+     * {@link Template#process(Object, Writer)} for example. The difference is 
in what they print on the output before
+     * they do that.
+     * 
+     * <p>Note that the {@link TemplateExceptionHandler} is not meant to be 
used for generating HTTP error pages.
+     * Neither is it meant to be used to roll back the printed output. These 
should be solved outside template
+     * processing when the exception raises from {@link 
Template#process(Object, Writer) Template.process}.
+     * {@link TemplateExceptionHandler} meant to be used if you want to 
include special content <em>in</em> the template
+     * output, or if you want to suppress certain exceptions. 
+     */
+    public void setTemplateExceptionHandler(TemplateExceptionHandler 
templateExceptionHandler) {
+        _NullArgumentException.check("templateExceptionHandler", 
templateExceptionHandler);
+        this.templateExceptionHandler = templateExceptionHandler;
+    }
+
+    /**
+     * The getter pair of {@link 
#setTemplateExceptionHandler(TemplateExceptionHandler)}.
+     */
+    public TemplateExceptionHandler getTemplateExceptionHandler() {
+        return templateExceptionHandler != null
+                ? templateExceptionHandler : 
parent.getTemplateExceptionHandler();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isTemplateExceptionHandlerSet() {
+        return templateExceptionHandler != null;
+    }
+
+    /**
+     * Sets the arithmetic engine used to perform arithmetic operations.
+     * The default is {@link ArithmeticEngine#BIGDECIMAL_ENGINE}.
+     */
+    public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
+        _NullArgumentException.check("arithmeticEngine", arithmeticEngine);
+        this.arithmeticEngine = arithmeticEngine;
+    }
+
+    /**
+     * The getter pair of {@link #setArithmeticEngine(ArithmeticEngine)}.
+     */
+    public ArithmeticEngine getArithmeticEngine() {
+        return arithmeticEngine != null
+                ? arithmeticEngine : parent.getArithmeticEngine();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isArithmeticEngineSet() {
+        return arithmeticEngine != null;
+    }
+
+    /**
+     * Sets the object wrapper used to wrap objects to {@link TemplateModel}-s.
+     * The default is {@link DefaultObjectWrapperBuilder#build()}.
+     */
+    public void setObjectWrapper(ObjectWrapper objectWrapper) {
+        _NullArgumentException.check("objectWrapper", objectWrapper);
+        this.objectWrapper = objectWrapper;
+    }
+
+    /**
+     * The getter pair of {@link #setObjectWrapper(ObjectWrapper)}.
+     */
+    public ObjectWrapper getObjectWrapper() {
+        return objectWrapper != null
+                ? objectWrapper : parent.getObjectWrapper();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isObjectWrapperSet() {
+        return objectWrapper != null;
+    }
+    
+    /**
+     * Informs FreeMarker about the charset used for the output. As FreeMarker 
outputs character stream (not
+     * byte stream), it's not aware of the output charset unless the software 
that encloses it tells it
+     * with this setting. Some templates may use FreeMarker features that 
require this information.
+     * Setting this to {@code null} means that the output encoding is not 
known.
+     * 
+     * <p>Defaults to {@code null} (unknown).
+     */
+    public void setOutputEncoding(String outputEncoding) {
+        this.outputEncoding = outputEncoding;
+        outputEncodingSet = true;
+    }
+    
+    public String getOutputEncoding() {
+        return outputEncodingSet
+                ? outputEncoding
+                : (parent != null ? parent.getOutputEncoding() : null);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isOutputEncodingSet() {
+        return outputEncodingSet;
+    }
+    
+    /**
+     * Sets the URL escaping charset. If not set ({@code null}), the output 
encoding
+     * ({@link #setOutputEncoding(String)}) will be used for URL escaping.
+     * 
+     * Defaults to {@code null}.
+     */
+    public void setURLEscapingCharset(String urlEscapingCharset) {
+        this.urlEscapingCharset = urlEscapingCharset;
+        urlEscapingCharsetSet = true;
+    }
+    
+    public String getURLEscapingCharset() {
+        return urlEscapingCharsetSet
+                ? urlEscapingCharset
+                : (parent != null ? parent.getURLEscapingCharset() : null);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isURLEscapingCharsetSet() {
+        return urlEscapingCharsetSet;
+    }
+
+    /**
+     * Sets the {@link TemplateClassResolver} that is used when the
+     * <code>new</code> built-in is called in a template. That is, when
+     * a template contains the <code>"com.example.SomeClassName"?new</code>
+     * expression, this object will be called to resolve the
+     * <code>"com.example.SomeClassName"</code> string to a class. The default
+     * value is {@link TemplateClassResolver#UNRESTRICTED_RESOLVER}. If you 
allow
+     * users to upload templates, it's important to use a custom restrictive
+     * {@link TemplateClassResolver} or {@link 
TemplateClassResolver#ALLOWS_NOTHING_RESOLVER}.
+     * 
+     * @since 2.3.17
+     */
+    public void setNewBuiltinClassResolver(TemplateClassResolver 
newBuiltinClassResolver) {
+        _NullArgumentException.check("newBuiltinClassResolver", 
newBuiltinClassResolver);
+        this.newBuiltinClassResolver = newBuiltinClassResolver;
+    }
+
+    /**
+     * Retrieves the {@link TemplateClassResolver} used
+     * to resolve classes when "SomeClassName"?new is called in a template.
+     * 
+     * @see #setNewBuiltinClassResolver(TemplateClassResolver)
+     * 
+     * @since 2.3.17
+     */
+    public TemplateClassResolver getNewBuiltinClassResolver() {
+        return newBuiltinClassResolver != null
+                ? newBuiltinClassResolver : 
parent.getNewBuiltinClassResolver();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isNewBuiltinClassResolverSet() {
+        return newBuiltinClassResolver != null;
+    }
+    
+    /**
+     * Sets whether the output {@link Writer} is automatically flushed at
+     * the end of {@link Template#process(Object, Writer)} (and its
+     * overloads). The default is {@code true}.
+     * 
+     * <p>Using {@code false} is needed for example when a Web page is composed
+     * from several boxes (like portlets, GUI panels, etc.) that aren't 
inserted
+     * with <tt>#include</tt> (or with similar directives) into a master
+     * FreeMarker template, rather they are all processed with a separate
+     * {@link Template#process(Object, Writer)} call. In a such scenario the
+     * automatic flushes would commit the HTTP response after each box, hence
+     * interfering with full-page buffering, and also possibly decreasing
+     * performance with too frequent and too early response buffer flushes.
+     * 
+     * @since 2.3.17
+     */
+    public void setAutoFlush(boolean autoFlush) {
+        this.autoFlush = Boolean.valueOf(autoFlush);
+    }
+    
+    /**
+     * See {@link #setAutoFlush(boolean)}
+     * 
+     * @since 2.3.17
+     */
+    public boolean getAutoFlush() {
+        return autoFlush != null 
+            ? autoFlush.booleanValue()
+            : (parent != null ? parent.getAutoFlush() : true);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isAutoFlushSet() {
+        return autoFlush != null;
+    }
+    
+    /**
+     * Sets if tips should be shown in error messages of errors arising during 
template processing.
+     * The default is {@code true}. 
+     * 
+     * @since 2.3.21
+     */
+    public void setShowErrorTips(boolean showTips) {
+        showErrorTips = Boolean.valueOf(showTips);
+    }
+    
+    /**
+     * See {@link #setShowErrorTips(boolean)}
+     * 
+     * @since 2.3.21
+     */
+    public boolean getShowErrorTips() {
+        return showErrorTips != null 
+            ? showErrorTips.booleanValue()
+            : (parent != null ? parent.getShowErrorTips() : true);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isShowErrorTipsSet() {
+        return showErrorTips != null;
+    }
+    
+    /**
+     * Specifies if {@code ?api} can be used in templates. Defaults to {@code 
false} so that updating FreeMarker won't
+     * decrease the security of existing applications.
+     * 
+     * @since 2.3.22
+     */
+    public void setAPIBuiltinEnabled(boolean value) {
+        apiBuiltinEnabled = Boolean.valueOf(value);
+    }
+
+    /**
+     * See {@link #setAPIBuiltinEnabled(boolean)}
+     * 
+     * @since 2.3.22
+     */
+    public boolean isAPIBuiltinEnabled() {
+        return apiBuiltinEnabled != null 
+                ? apiBuiltinEnabled.booleanValue()
+                : (parent != null ? parent.isAPIBuiltinEnabled() : false);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isAPIBuiltinEnabledSet() {
+        return apiBuiltinEnabled != null;
+    }
+    
+    /**
+     * Specifies if {@link TemplateException}-s thrown by template processing 
are logged by FreeMarker or not. The
+     * default is {@code true} for backward compatibility, but that results in 
logging the exception twice in properly
+     * written applications, because there the {@link TemplateException} 
thrown by the public FreeMarker API is also
+     * logged by the caller (even if only as the cause exception of a higher 
level exception). Hence, in modern
+     * applications it should be set to {@code false}. Note that this setting 
has no effect on the logging of exceptions
+     * caught by {@code #attempt}; those are always logged, no mater what 
(because those exceptions won't bubble up
+     * until the API caller).
+     * 
+     * @since 2.3.22
+     */
+    public void setLogTemplateExceptions(boolean value) {
+        logTemplateExceptions = Boolean.valueOf(value);
+    }
+
+    /**
+     * See {@link #setLogTemplateExceptions(boolean)}
+     * 
+     * @since 2.3.22
+     */
+    public boolean getLogTemplateExceptions() {
+        return logTemplateExceptions != null 
+                ? logTemplateExceptions.booleanValue()
+                : (parent != null ? parent.getLogTemplateExceptions() : true);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.24
+     */
+    public boolean isLogTemplateExceptionsSet() {
+        return logTemplateExceptions != null;
+    }
+    
+    /**
+     * The getter pair of {@link #setLazyImports(boolean)}.
+     * 
+     * @since 2.3.25
+     */
+    public boolean getLazyImports() {
+        return lazyImports != null ? lazyImports.booleanValue() : 
parent.getLazyImports();
+    }
+    
+    /**
+     * Specifies if {@code <#import ...>} (and {@link 
Environment#importLib(String, String)}) should delay the loading
+     * and processing of the imported templates until the content of the 
imported namespace is actually accessed. This
+     * makes the overhead of <em>unused</em> imports negligible. A drawback is 
that importing a missing or otherwise
+     * broken template will be successful, and the problem will remain hidden 
until (and if) the namespace content is
+     * actually used. Also, you lose the strict control over when the 
namespace initializing code in the imported
+     * template will be executed, though it shouldn't mater for well written 
imported templates anyway. Note that the
+     * namespace initializing code will run with the same {@linkplain 
Configurable#getLocale() locale} as it was at the
+     * point of the {@code <#import ...>} call (other settings won't be 
handled specially like that).
+     * 
+     * <p>
+     * The default is {@code false} (and thus imports are eager) for backward 
compatibility, which can cause
+     * perceivable overhead if you have many imports and only a few of them is 
used.
+     * 
+     * <p>
+     * This setting also affects {@linkplain #setAutoImports(Map) 
auto-imports}, unless you have set a non-{@code null}
+     * value with {@link #setLazyAutoImports(Boolean)}.
+     * 
+     * @see #setLazyAutoImports(Boolean)
+     * 
+     * @since 2.3.25
+     */
+    public void setLazyImports(boolean lazyImports) {
+        this.lazyImports = Boolean.valueOf(lazyImports);
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.25
+     */
+    public boolean isLazyImportsSet() {
+        return lazyImports != null;
+    }
+    
+    /**
+     * The getter pair of {@link #setLazyAutoImports(Boolean)}.
+     * 
+     * @since 2.3.25
+     */
+    public Boolean getLazyAutoImports() {
+        return lazyAutoImportsSet ? lazyAutoImports : 
parent.getLazyAutoImports();
+    }
+
+    /**
+     * Specifies if {@linkplain #setAutoImports(Map) auto-imports} will be
+     * {@link #setLazyImports(boolean) lazy imports}. This is useful to make 
the overhead of <em>unused</em>
+     * auto-imports negligible. If this is set to {@code null}, {@link 
#getLazyImports()} specifies the behavior of
+     * auto-imports too. The default value is {@code null}.
+     * 
+     * @since 2.3.25
+     */
+    public void setLazyAutoImports(Boolean lazyAutoImports) {
+        this.lazyAutoImports = lazyAutoImports;
+        lazyAutoImportsSet = true;
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     *  
+     * @since 2.3.25
+     */
+    public boolean isLazyAutoImportsSet() {
+        return lazyAutoImportsSet;
+    }
+    
+    /**
+     * Adds an invisible <code>#import <i>templateName</i> as 
<i>namespaceVarName</i></code> at the beginning of the
+     * main template (that's the top-level template that wasn't 
included/imported from another template). While it only
+     * affects the main template directly, as the imports will create a global 
variable there, the imports will be
+     * visible from the further imported templates too (note that {@link 
Configuration#getIncompatibleImprovements()}
+     * set to 2.3.24 fixes a rarely surfacing bug with that).
+     * 
+     * <p>
+     * It's recommended to set the {@code auto_impots_lazy} setting ({@link 
Configuration#setLazyAutoImports(Boolean)})
+     * to {@code true} when using this, so that auto-imports that are unused 
in a template won't degrade performance by
+     * unnecessary loading and initializing the imported library.
+     * 
+     * <p>
+     * If the imports aren't lazy, the order of the imports will be the same 
as the order in which they were added with
+     * this method. (Calling this method with an already added {@code 
namespaceVarName} will move that to the end
+     * of the auto-import order.)
+     * 
+     * <p>
+     * The auto-import is added directly to the {@link Configurable} on which 
this method is called (not to the parents
+     * or children), but when the main template is processed, the auto-imports 
are collected from all the
+     * {@link Configurable} levels, in parent-to-child order: {@link 
Configuration}, {@link Template} (the main
+     * template), {@link Environment}. If the same {@code namespaceVarName} 
occurs on multiple levels, the one on the
+     * child level is used, and the clashing import from the parent level is 
skipped.
+     * 
+     * <p>If there are also auto-includes (see {@link 
#addAutoInclude(String)}), those will be executed after
+     * the auto-imports.
+     * 
+     * @see #setAutoImports(Map)
+     */
+    public void addAutoImport(String namespaceVarName, String templateName) {
+        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
+        synchronized (this) {
+            if (autoImports == null) {
+                initAutoImportsMap();
+            } else {
+                // This was a List earlier, so re-inserted items must go to 
the end, hence we remove() before put().
+                autoImports.remove(namespaceVarName);
+            }
+            autoImports.put(namespaceVarName, templateName);
+        }
+    }
+
+    private void initAutoImportsMap() {
+        autoImports = new LinkedHashMap<>(4);
+    }
+    
+    /**
+     * Removes an auto-import from this {@link Configurable} level (not from 
the parents or children);
+     * see {@link #addAutoImport(String, String)}. Does nothing if the 
auto-import doesn't exist.
+     */
+    public void removeAutoImport(String namespaceVarName) {
+        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
+        synchronized (this) {
+            if (autoImports != null) {
+                autoImports.remove(namespaceVarName);
+            }
+        }
+    }
+    
+    /**
+     * Removes all auto-imports, then calls {@link #addAutoImport(String, 
String)} for each {@link Map}-entry (the entry
+     * key is the {@code namespaceVarName}). The order of the auto-imports 
will be the same as {@link Map#keySet()}
+     * returns the keys (but the order of imports doesn't mater for properly 
designed libraries anyway).
+     * 
+     * @param map
+     *            Maps the namespace variable names to the template names; not 
{@code null}
+     */
+    public void setAutoImports(Map map) {
+        _NullArgumentException.check("map", map);
+        
+        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
+        synchronized (this) {
+            if (autoImports != null) {
+                autoImports.clear();
+            }
+            for (Map.Entry<?, ?> entry : ((Map<?, ?>) map).entrySet()) {
+                Object key = entry.getKey();
+                if (!(key instanceof String)) {
+                    throw new IllegalArgumentException(
+                            "Key in Map wasn't a String, but a(n) " + 
key.getClass().getName() + ".");
+                }
+                
+                Object value = entry.getValue();
+                if (!(value instanceof String)) {
+                    throw new IllegalArgumentException(
+                            "Value in Map wasn't a String, but a(n) " + 
key.getClass().getName() + ".");
+                }
+                
+                addAutoImport((String) key, (String) value);
+            }
+        }
+    }
+    
+    /**
+     * Getter pair of {@link #setAutoImports(Map)}; do not modify the returned 
{@link Map}! To be consistent with other
+     * setting getters, if this setting was set directly on this {@link 
Configurable} object, this simply returns that
+     * value, otherwise it returns the value from the parent {@link 
Configurable}. So beware, the returned value doesn't
+     * reflect the {@link Map} key granularity fallback logic that FreeMarker 
actually uses for this setting. The
+     * returned value is not the same {@link Map} object that was set with 
{@link #setAutoImports(Map)}, only its
+     * content is the same. The returned value isn't a snapshot; it may or may 
not shows the changes later made to this
+     * setting on this {@link Configurable} level (but usually it's well 
defined if until what point settings are
+     * possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link 
Configuration} (top) level, it defaults to an empty
+     * {@link Map}.
+     * 
+     * @see #getAutoImportsWithoutFallback()
+     * 
+     * @since 2.3.25
+     */
+    public Map<String, String> getAutoImports() {
+        return autoImports != null ? autoImports : parent.getAutoImports();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.25
+     */
+    public boolean isAutoImportsSet() {
+        return autoImports != null;
+    }
+
+    /**
+     * Like {@link #getAutoImports()}, but doesn't fall back to the parent 
{@link Configurable} (and so it can be
+     * {@code null}).
+     *  
+     * @since 2.3.25
+     */
+    public Map<String, String> getAutoImportsWithoutFallback() {
+        return autoImports;
+    }
+    
+    /**
+     * Adds an invisible <code>#include <i>templateName</i></code> at the 
beginning of the main template (that's the
+     * top-level template that wasn't included/imported from another template).
+     * 
+     * <p>
+     * The order of the inclusions will be the same as the order in which they 
were added with this method.
+     * 
+     * <p>
+     * The auto-include is added directly to the {@link Configurable} on which 
this method is called (not to the parents
+     * or children), but when the main template is processed, the 
auto-includes are collected from all the
+     * {@link Configurable} levels, in parent-to-child order: {@link 
Configuration}, {@link Template} (the main
+     * template), {@link Environment}.
+     * 
+     * <p>
+     * If there are also auto-imports ({@link #addAutoImport(String, 
String)}), those imports will be executed before
+     * the auto-includes, hence the namespace variables are accessible for the 
auto-included templates.
+     * 
+     * <p>
+     * Calling {@link #addAutoInclude(String)} with an already added template 
name will just move that to the end of the
+     * auto-include list (within the same {@link Configurable} level). This 
works even if the same template name appears
+     * on different {@link Configurable} levels, in which case only the 
inclusion on the lowest (child) level will be
+     * executed.
+     * 
+     * @see #setAutoIncludes(List)
+     */
+    public void addAutoInclude(String templateName) {
+        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
+        synchronized (this) {
+            if (autoIncludes == null) {
+                initAutoIncludesList();
+            } else {
+                autoIncludes.remove(templateName);
+            }
+            autoIncludes.add(templateName);
+        }
+    }
+
+    private void initAutoIncludesList() {
+        autoIncludes = new ArrayList<>(4);
+    }
+    
+    /**
+     * Removes all auto-includes, then calls {@link #addAutoInclude(String)} 
for each {@link List} items.
+     * 
+     * <p>Before {@linkplain Configuration#Configuration(Version) incompatible 
improvements} 2.3.25 it doesn't filter
+     * out duplicates from the list if this method was called on a {@link 
Configuration} instance.
+     */
+    public void setAutoIncludes(List templateNames) {
+        _NullArgumentException.check("templateNames", templateNames);
+        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
+        synchronized (this) {
+            if (autoIncludes != null) {
+                autoIncludes.clear();
+            }
+            for (Object templateName : templateNames) {
+                if (!(templateName instanceof String)) {
+                    throw new IllegalArgumentException("List items must be 
String-s.");
+                }
+                addAutoInclude((String) templateName);
+            }
+        }
+    }
+    
+    /**
+     * Getter pair of {@link #setAutoIncludes(List)}; do not modify the 
returned {@link List}! To be consistent with
+     * other setting getters, if this setting was set directly on this {@link 
Configurable} object, this simply returns
+     * that value, otherwise it returns the value from the parent {@link 
Configurable}. So beware, the returned value
+     * doesn't reflect the {@link List} concatenation logic that FreeMarker 
actually uses for this setting. The returned
+     * value is not the same {@link List} object that was set with {@link 
#setAutoIncludes(List)}, only its content is
+     * the same (except that duplicate are removed). The returned value isn't 
a snapshot; it may or may not shows the
+     * changes later made to this setting on this {@link Configurable} level 
(but usually it's well defined if until
+     * what point settings are possibly modified).
+     * 
+     * <p>
+     * The return value is never {@code null}; called on the {@link 
Configuration} (top) level, it defaults to an empty
+     * {@link List}.
+     * 
+     * @see #getAutoIncludesWithoutFallback()
+     * 
+     * @since 2.3.25
+     */
+    public List<String> getAutoIncludes() {
+        return autoIncludes != null ? autoIncludes : parent.getAutoIncludes();
+    }
+    
+    /**
+     * Tells if this setting is set directly in this object or its value is 
coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.25
+     */
+    public boolean isAutoIncludesSet() {
+        return autoIncludes != null;
+    }
+    
+    /**
+     * Like {@link #getAutoIncludes()}, but doesn't fall back to the parent 
{@link Configurable} (and so it can be
+     * {@code null}).
+     *  
+     * @since 2.3.25
+     */
+    public List<String> getAutoIncludesWithoutFallback() {
+        return autoIncludes;
+    }
+    
+    /**
+     * Removes the auto-include from this {@link Configurable} level (not from 
the parents or children); see
+     * {@link #addAutoInclude(String)}. Does nothing if the template is not 
there.
+     */
+    public void removeAutoInclude(String templateName) {
+        // "synchronized" is removed from the API as it's not safe to set 
anything after publishing the Configuration
+        synchronized (this) {
+            if (autoIncludes != null) {
+                autoIncludes.remove(templateName);
+            }
+        }
+    }
+    
+    private static final String ALLOWED_CLASSES = "allowed_classes";
+    private static final String TRUSTED_TEMPLATES = "trusted_templates";
+    
+    /**
+     * Sets a FreeMarker setting by a name and string value. If you can 
configure FreeMarker directly with Java (or
+     * other programming language), you should use the dedicated setter 
methods instead (like
+     * {@link #setObjectWrapper(ObjectWrapper)}. This meant to be used only 
when you get settings from somewhere
+     * as {@link String}-{@link String} name-value pairs (typically, as a 
{@link Properties} object). Below you find an
+     * overview of the settings available.
+     * 
+     * <p>Note: As of FreeMarker 2.3.23, setting names can be written in camel 
case too. For example, instead of
+     * {@code date_format} you can also use {@code dateFormat}. It's likely 
that camel case will become to the
+     * recommended convention in the future.
+     * 
+     * <p>The list of settings commonly supported in all {@link Configurable} 
subclasses:
+     * <ul>
+     *   <li><p>{@code "locale"}:
+     *       See {@link #setLocale(Locale)}.
+     *       <br>String value: local codes with the usual format in Java, such 
as {@code "en_US"}.
+     *
+     *   <li><p>{@code "custom_number_formats"}: See {@link 
#setCustomNumberFormats(Map)}.
+     *   <br>String value: Interpreted as an <a href="#fm_obe">object builder 
expression</a>.
+     *   <br>Example: <code>{ "hex": 
com.example.HexTemplateNumberFormatFactory,
+     *   "gps": com.example.GPSTemplateNumberFormatFactory }</code>
+     *
+     *   <li><p>{@code "custom_date_formats"}: See {@link 
#setCustomDateFormats(Map)}.
+     *   <br>String value: Interpreted as an <a href="#fm_obe">object builder 
expression</

<TRUNCATED>

Reply via email to