freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [3/6] incubator-freemarker git commit: Various refactorings of Configurable and its subclasses. This is part of the preparation for making such classes immutable, and offer builders to create them.
Date Mon, 27 Mar 2017 11:48:52 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/053afbf5/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
new file mode 100644
index 0000000..eef4188
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -0,0 +1,2890 @@
+/*
+ * 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.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
+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.RestrictedObjectWrapper;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
+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.OptInTemplateClassResolver;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._CollectionUtil;
+import org.apache.freemarker.core.util._NullArgumentException;
+import org.apache.freemarker.core.util._SortedArraySet;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+
+/**
+ * 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>MutableProcessingConfiguration</code> objects inherits its value from its parent
+ * <code>MutableProcessingConfiguration</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 abstract class MutableProcessingConfiguration<SelfT extends MutableProcessingConfiguration<SelfT>>
+        implements ProcessingConfiguration {
+    static final String C_TRUE_FALSE = "true,false";
+    
+    public static final String NULL_VALUE = "null";
+    public static final String DEFAULT_VALUE = "default";
+    public static final String JVM_DEFAULT_VALUE = "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 MutableProcessingConfiguration parent;
+    private Map<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 MutableProcessingConfiguration(Version incompatibleImprovements) {
+        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+        parent = null;
+        locale = Configuration.getDefaultLocale();
+        timeZone = Configuration.getDefaultTimeZone();
+        sqlDataAndTimeTimeZone = null;
+        numberFormat = "number";
+        timeFormat = "";
+        dateFormat = "";
+        dateTimeFormat = "";
+        templateExceptionHandler = Configuration.getDefaultTemplateExceptionHandler();
+        arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE;
+        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);
+
+        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>MutableProcessingConfiguration</code> directly, but its subclasses.
+     */
+    protected MutableProcessingConfiguration(MutableProcessingConfiguration parent) {
+        this.parent = parent;
+        locale = null;
+        numberFormat = null;
+        templateExceptionHandler = null;
+    }
+    
+    /**
+     * Returns the parent {@link MutableProcessingConfiguration} object of this object. The parent stores the default setting values for
+     * this {@link MutableProcessingConfiguration}. 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 MutableProcessingConfiguration} object, or {@code null} if this is the root {@link MutableProcessingConfiguration} object
+     *         (i.e, if it's the {@link Configuration} object).
+     */
+    public final MutableProcessingConfiguration 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(MutableProcessingConfiguration 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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setLocale(Locale)}
+     */
+    public SelfT locale(Locale value) {
+        setLocale(value);
+        return self();
+    }
+
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTimeZone(TimeZone)}
+     */
+    public SelfT timeZone(TimeZone value) {
+        setTimeZone(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setTimeZone(TimeZone)}. 
+     */
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setSQLDateAndTimeTimeZone(TimeZone)}
+     */
+    public SelfT sqlDateAndTimeTimeZone(TimeZone value) {
+        setSQLDateAndTimeTimeZone(value);
+        return self();
+    }
+
+
+    /**
+     * 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
+     */
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setNumberFormat(String)}
+     */
+    public SelfT numberFormat(String value) {
+        setNumberFormat(value);
+        return self();
+    }
+
+    /**
+     * Getter pair of {@link #setNumberFormat(String)}. 
+     */
+    @Override
+    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
+     */
+    @Override
+    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 MutableProcessingConfiguration} object, this simply
+     * returns that value, otherwise it returns the value from the parent {@link MutableProcessingConfiguration}. 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 MutableProcessingConfiguration} 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}.
+     *
+     * @since 2.3.24
+     */
+    @Override
+    public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats() {
+        return customNumberFormats == null ? parent.getCustomNumberFormats() : 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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setCustomNumberFormats(Map)}
+     */
+    public SelfT customNumberFormats(Map<String, ? extends TemplateNumberFormatFactory> value) {
+        setCustomNumberFormats(value);
+        return self();
+    }
+
+    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
+     */
+    @Override
+    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);
+        }
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setBooleanFormat(String)}
+     */
+    public SelfT booleanFormat(String value) {
+        setBooleanFormat(value);
+        return self();
+    }
+    
+    /**
+     * The getter pair of {@link #setBooleanFormat(String)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTimeFormat(String)}
+     */
+    public SelfT timeFormat(String value) {
+        setTimeFormat(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setTimeFormat(String)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setDateFormat(String)}
+     */
+    public SelfT dateFormat(String value) {
+        setDateFormat(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setDateFormat(String)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setDateTimeFormat(String)}
+     */
+    public SelfT dateTimeFormat(String value) {
+        setDateTimeFormat(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setDateTimeFormat(String)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    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 MutableProcessingConfiguration} object, this simply returns
+     * that value, otherwise it returns the value from the parent {@link MutableProcessingConfiguration}. 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 MutableProcessingConfiguration} 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}.
+     * 
+     * @since 2.3.24
+     */
+    @Override
+    public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats() {
+        return customDateFormats == null ? parent.getCustomDateFormats() : 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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setCustomDateFormats(Map)}
+     */
+    public SelfT customDateFormats(Map<String, ? extends TemplateDateFormatFactory> value) {
+        setCustomDateFormats(value);
+        return self();
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     * 
+     * @since 2.3.24
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}
+     */
+    public SelfT templateExceptionHandler(TemplateExceptionHandler value) {
+        setTemplateExceptionHandler(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    public boolean isTemplateExceptionHandlerSet() {
+        return templateExceptionHandler != null;
+    }
+
+    /**
+     * Sets the arithmetic engine used to perform arithmetic operations.
+     * The default is {@link BigDecimalArithmeticEngine#INSTANCE}.
+     */
+    public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
+        _NullArgumentException.check("arithmeticEngine", arithmeticEngine);
+        this.arithmeticEngine = arithmeticEngine;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setArithmeticEngine(ArithmeticEngine)}
+     */
+    public SelfT arithmeticEngine(ArithmeticEngine value) {
+        setArithmeticEngine(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setArithmeticEngine(ArithmeticEngine)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    public boolean isArithmeticEngineSet() {
+        return arithmeticEngine != null;
+    }
+
+    /**
+     * Sets the object wrapper used to wrap objects to {@link TemplateModel}-s.
+     * The default is {@link DefaultObjectWrapper.Builder#build()}.
+     */
+    public void setObjectWrapper(ObjectWrapper objectWrapper) {
+        _NullArgumentException.check("objectWrapper", objectWrapper);
+        this.objectWrapper = objectWrapper;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setObjectWrapper(ObjectWrapper)}
+     */
+    public SelfT objectWrapper(ObjectWrapper value) {
+        setObjectWrapper(value);
+        return self();
+    }
+
+    /**
+     * The getter pair of {@link #setObjectWrapper(ObjectWrapper)}.
+     */
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setOutputEncoding(String)}
+     */
+    public SelfT outputEncoding(String value) {
+        setOutputEncoding(value);
+        return self();
+    }
+
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setURLEscapingCharset(String)}
+     */
+    public SelfT urlEscapingCharset(String value) {
+        setURLEscapingCharset(value);
+        return self();
+    }
+
+    @Override
+    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
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setNewBuiltinClassResolver(TemplateClassResolver)}
+     */
+    public SelfT newBuiltinClassResolver(TemplateClassResolver value) {
+        setNewBuiltinClassResolver(value);
+        return self();
+    }
+
+    /**
+     * Retrieves the {@link TemplateClassResolver} used
+     * to resolve classes when "SomeClassName"?new is called in a template.
+     * 
+     * @see #setNewBuiltinClassResolver(TemplateClassResolver)
+     * 
+     * @since 2.3.17
+     */
+    @Override
+    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
+     */
+    @Override
+    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);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setAutoFlush(boolean)}
+     */
+    public SelfT autoFlush(boolean value) {
+        setAutoFlush(value);
+        return self();
+    }
+
+    /**
+     * See {@link #setAutoFlush(boolean)}
+     * 
+     * @since 2.3.17
+     */
+    @Override
+    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
+     */
+    @Override
+    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);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setShowErrorTips(boolean)}
+     */
+    public SelfT showErrorTips(boolean value) {
+        setShowErrorTips(value);
+        return self();
+    }
+
+    /**
+     * See {@link #setShowErrorTips(boolean)}
+     * 
+     * @since 2.3.21
+     */
+    @Override
+    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
+     */
+    @Override
+    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);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setAPIBuiltinEnabled(boolean)}
+     */
+    public SelfT apiBuiltinEnabled(boolean value) {
+        setAPIBuiltinEnabled(value);
+        return self();
+    }
+
+    /**
+     * 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
+     */
+    @Override
+    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
+     */
+    @Override
+    public boolean isLogTemplateExceptionsSet() {
+        return logTemplateExceptions != null;
+    }
+    
+    /**
+     * The getter pair of {@link #setLazyImports(boolean)}.
+     * 
+     * @since 2.3.25
+     */
+    @Override
+    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 MutableProcessingConfiguration#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
+     */
+    @Override
+    public boolean isLazyImportsSet() {
+        return lazyImports != null;
+    }
+    
+    /**
+     * The getter pair of {@link #setLazyAutoImports(Boolean)}.
+     * 
+     * @since 2.3.25
+     */
+    @Override
+    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
+     */
+    @Override
+    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 invoke 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 MutableProcessingConfiguration} 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 MutableProcessingConfiguration} 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 MutableProcessingConfiguration} 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) " + value.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 MutableProcessingConfiguration} object, this simply returns that
+     * value, otherwise it returns the value from the parent {@link MutableProcessingConfiguration}. 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 MutableProcessingConfiguration} 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}.
+     *
+     * @since 2.3.25
+     */
+    @Override
+    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
+     */
+    @Override
+    public boolean isAutoImportsSet() {
+        return autoImports != null;
+    }
+
+    /**
+     * 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 MutableProcessingConfiguration} 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 MutableProcessingConfiguration} 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 MutableProcessingConfiguration} level). This works even if the same template name appears
+     * on different {@link MutableProcessingConfiguration} 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);
+        

<TRUNCATED>


Mime
View raw message