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