http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/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 new file mode 100644 index 0000000..84cbed2 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/Configuration.java @@ -0,0 +1,3201 @@ +/* + * 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.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +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; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.freemarker.core.ast.BugException; +import org.apache.freemarker.core.ast.CSSOutputFormat; +import org.apache.freemarker.core.ast.CombinedMarkupOutputFormat; +import org.apache.freemarker.core.ast.Configurable; +import org.apache.freemarker.core.ast.Environment; +import org.apache.freemarker.core.ast.HTMLOutputFormat; +import org.apache.freemarker.core.ast.JSONOutputFormat; +import org.apache.freemarker.core.ast.JavaScriptOutputFormat; +import org.apache.freemarker.core.ast.MarkupOutputFormat; +import org.apache.freemarker.core.ast.OutputFormat; +import org.apache.freemarker.core.ast.ParseException; +import org.apache.freemarker.core.ast.ParserConfiguration; +import org.apache.freemarker.core.ast.PlainTextOutputFormat; +import org.apache.freemarker.core.ast.RTFOutputFormat; +import org.apache.freemarker.core.ast.TemplateConfiguration; +import org.apache.freemarker.core.ast.TemplateMarkupOutputModel; +import org.apache.freemarker.core.ast.UndefinedOutputFormat; +import org.apache.freemarker.core.ast.UnregisteredOutputFormatException; +import org.apache.freemarker.core.ast.XHTMLOutputFormat; +import org.apache.freemarker.core.ast.XMLOutputFormat; +import org.apache.freemarker.core.ast._CoreAPI; +import org.apache.freemarker.core.ast._DelayedJQuote; +import org.apache.freemarker.core.ast._MiscTemplateException; +import org.apache.freemarker.core.ast._ObjectBuilderSettingEvaluator; +import org.apache.freemarker.core.ast._SettingEvaluationEnvironment; +import org.apache.freemarker.core.ast._SortedArraySet; +import org.apache.freemarker.core.ast._UnmodifiableCompositeSet; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateHashModelEx; +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.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder; +import org.apache.freemarker.core.model.impl.SimpleCollection; +import org.apache.freemarker.core.model.impl.SimpleDate; +import org.apache.freemarker.core.model.impl.SimpleHash; +import org.apache.freemarker.core.model.impl.SimpleObjectWrapper; +import org.apache.freemarker.core.model.impl.SimpleSequence; +import org.apache.freemarker.core.model.impl._StaticObjectWrappers; +import org.apache.freemarker.core.model.impl.beans.BeansWrapper; +import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder; +import org.apache.freemarker.core.templateresolver.CacheStorage; +import org.apache.freemarker.core.templateresolver.ClassTemplateLoader; +import org.apache.freemarker.core.templateresolver.FileTemplateLoader; +import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException; +import org.apache.freemarker.core.templateresolver.MruCacheStorage; +import org.apache.freemarker.core.templateresolver.MultiTemplateLoader; +import org.apache.freemarker.core.templateresolver.SoftCacheStorage; +import org.apache.freemarker.core.templateresolver.TemplateCache; +import org.apache.freemarker.core.templateresolver.TemplateCache.MaybeMissingTemplate; +import org.apache.freemarker.core.util.CaptureOutput; +import org.apache.freemarker.core.util.ClassUtil; +import org.apache.freemarker.core.util.Constants; +import org.apache.freemarker.core.util.HtmlEscape; +import org.apache.freemarker.core.util.NormalizeNewlines; +import org.apache.freemarker.core.util.NullArgumentException; +import org.apache.freemarker.core.util.SecurityUtilities; +import org.apache.freemarker.core.util.StandardCompress; +import org.apache.freemarker.core.util.StringUtil; +import org.apache.freemarker.core.util.XmlEscape; +import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory; +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.URLTemplateLoader; + +/** + * <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 create 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 + * 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 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>(...); + * ... + * + * // Later, whenever the application needs a template (so you may do this a lot, and from multiple threads): + * {@link Template Template} myTemplate = cfg.{@link #getTemplate(String) getTemplate}("myTemplate.html"); + * myTemplate.{@link Template#process(Object, java.io.Writer) process}(dataModel, out);</pre> + * + * <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 #setDefaultEncoding(String) default_encoding}: 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 + * 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. + */ +public class Configuration extends Configurable implements Cloneable, ParserConfiguration { + + 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 DEFAULT_ENCODING_KEY_SNAKE_CASE = "default_encoding"; + /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */ + public static final String DEFAULT_ENCODING_KEY_CAMEL_CASE = "defaultEncoding"; + /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */ + public static final String DEFAULT_ENCODING_KEY = DEFAULT_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 + * @deprecated Use {@link Configurable#AUTO_IMPORT_KEY_SNAKE_CASE} instead. + */ + @Deprecated + public static final String AUTO_IMPORT_KEY_SNAKE_CASE = "auto_import"; + /** + * Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 + * @deprecated Use {@link Configurable#AUTO_IMPORT_KEY_CAMEL_CASE} instead. + */ + @Deprecated + public static final String AUTO_IMPORT_KEY_CAMEL_CASE = "autoImport"; + /** + * Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. + * @deprecated Use {@link Configurable#AUTO_IMPORT_KEY_SNAKE_CASE} instead. + */ + @Deprecated + 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.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 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; + + /** @deprecated Use {@link #INCOMPATIBLE_IMPROVEMENTS_KEY} instead. */ + @Deprecated + public static final String INCOMPATIBLE_IMPROVEMENTS = INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE; + /** @deprecated Use {@link #INCOMPATIBLE_IMPROVEMENTS_KEY} instead. */ + @Deprecated + public static final String INCOMPATIBLE_ENHANCEMENTS = "incompatible_enhancements"; + + 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, + DEFAULT_ENCODING_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, + TAB_SIZE_KEY_SNAKE_CASE, + TAG_SYNTAX_KEY_SNAKE_CASE, + TEMPLATE_CONFIGURATIONS_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, + }; + + 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, + DEFAULT_ENCODING_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, + TAB_SIZE_KEY_CAMEL_CASE, + TAG_SYNTAX_KEY_CAMEL_CASE, + TEMPLATE_CONFIGURATIONS_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 + }; + + private static final Map<String, OutputFormat> STANDARD_OUTPUT_FORMATS; + static { + STANDARD_OUTPUT_FORMATS = new HashMap<String, OutputFormat>(); + STANDARD_OUTPUT_FORMATS.put(UndefinedOutputFormat.INSTANCE.getName(), UndefinedOutputFormat.INSTANCE); + STANDARD_OUTPUT_FORMATS.put(HTMLOutputFormat.INSTANCE.getName(), HTMLOutputFormat.INSTANCE); + STANDARD_OUTPUT_FORMATS.put(XHTMLOutputFormat.INSTANCE.getName(), XHTMLOutputFormat.INSTANCE); + STANDARD_OUTPUT_FORMATS.put(XMLOutputFormat.INSTANCE.getName(), XMLOutputFormat.INSTANCE); + STANDARD_OUTPUT_FORMATS.put(RTFOutputFormat.INSTANCE.getName(), RTFOutputFormat.INSTANCE); + STANDARD_OUTPUT_FORMATS.put(PlainTextOutputFormat.INSTANCE.getName(), PlainTextOutputFormat.INSTANCE); + STANDARD_OUTPUT_FORMATS.put(CSSOutputFormat.INSTANCE.getName(), CSSOutputFormat.INSTANCE); + 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 2.3.0 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_0 = new Version(2, 3, 0); + + /** FreeMarker version 2.3.19 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_19 = new Version(2, 3, 19); + + /** FreeMarker version 2.3.20 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_20 = new Version(2, 3, 20); + + /** FreeMarker version 2.3.21 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_21 = new Version(2, 3, 21); + + /** FreeMarker version 2.3.22 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_22 = new Version(2, 3, 22); + + /** FreeMarker version 2.3.23 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_23 = new Version(2, 3, 23); + + /** FreeMarker version 2.3.24 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_24 = new Version(2, 3, 24); + + /** FreeMarker version 2.3.25 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_25 = new Version(2, 3, 25); + + /** FreeMarker version 2.3.26 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_2_3_26 = new Version(2, 3, 26); + + /** FreeMarker version 3.0.0 (an {@link #Configuration(Version) incompatible improvements break-point}) */ + public static final Version VERSION_3_0_0 = new Version(3, 0, 0); + + /** The default of {@link #getIncompatibleImprovements()}, currently {@link #VERSION_2_3_0}. */ + public static final Version DEFAULT_INCOMPATIBLE_IMPROVEMENTS = Configuration.VERSION_2_3_0; + /** @deprecated Use {@link #DEFAULT_INCOMPATIBLE_IMPROVEMENTS} instead. */ + @Deprecated + public static final String DEFAULT_INCOMPATIBLE_ENHANCEMENTS = DEFAULT_INCOMPATIBLE_IMPROVEMENTS.toString(); + /** @deprecated Use {@link #DEFAULT_INCOMPATIBLE_IMPROVEMENTS} instead. */ + @Deprecated + public static final int PARSED_DEFAULT_INCOMPATIBLE_ENHANCEMENTS = DEFAULT_INCOMPATIBLE_IMPROVEMENTS.intValue(); + + private static final String NULL = "null"; + private static final String DEFAULT = "default"; + + private static final Version VERSION; + static { + try { + Properties vp = new Properties(); + InputStream ins = Configuration.class.getClassLoader() + .getResourceAsStream(VERSION_PROPERTIES_PATH); + if (ins == null) { + throw new RuntimeException("Version file is missing."); + } else { + try { + vp.load(ins); + } finally { + ins.close(); + } + + String versionString = getRequiredVersionProperty(vp, "version"); + + Date buildDate; + { + String buildDateStr = getRequiredVersionProperty(vp, "buildTimestamp"); + if (buildDateStr.endsWith("Z")) { + buildDateStr = buildDateStr.substring(0, buildDateStr.length() - 1) + "+0000"; + } + try { + buildDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US).parse(buildDateStr); + } catch (java.text.ParseException e) { + buildDate = null; + } + } + + final Boolean gaeCompliant = Boolean.valueOf(getRequiredVersionProperty(vp, "isGAECompliant")); + + VERSION = new Version(versionString, gaeCompliant, buildDate); + } + } catch (IOException e) { + throw new RuntimeException("Failed to load and parse " + VERSION_PROPERTIES_PATH, e); + } + } + + private static final String FM_24_DETECTION_CLASS_NAME = "org.apache.freemarker.core.ast._2_4_OrLaterMarker"; + private static final boolean FM_24_DETECTED; + static { + boolean fm24detected; + try { + Class.forName(FM_24_DETECTION_CLASS_NAME); + fm24detected = true; + } catch (ClassNotFoundException e) { + fm24detected = false; + } catch (LinkageError e) { + fm24detected = true; + } catch (Throwable e) { + // Unexpected. We assume that there's no clash. + fm24detected = false; + } + FM_24_DETECTED = fm24detected; + } + + private final static Object defaultConfigLock = new Object(); + + private static volatile Configuration defaultConfig; + + 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 int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX; + private int namingConvention = AUTO_DETECT_NAMING_CONVENTION; + private int tabSize = 8; // Default from JavaCC 3.x + + private TemplateCache cache; + + private boolean templateLoaderExplicitlySet; + private boolean templateLookupStrategyExplicitlySet; + private boolean templateNameFormatExplicitlySet; + private boolean cacheStorageExplicitlySet; + + private boolean objectWrapperExplicitlySet; + private boolean templateExceptionHandlerExplicitlySet; + private boolean logTemplateExceptionsExplicitlySet; + + private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap(); + + /** + * Needed so that it doesn't mater in what order do you call {@link #setSharedVaribles(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 String defaultEncoding = SecurityUtilities.getSystemProperty("file.encoding", "utf-8"); + private ConcurrentMap localeToCharsetMap = new ConcurrentHashMap(); + + /** + * @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); + } + + /** + * 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> + * 2.3.0: This is the lowest supported value, the version used in older projects. This is the default in the + * FreeMarker 2.3.x series. + * </li> + * <li><p> + * 2.3.19 (or higher): Bug fix: Wrong {@code #} tags were printed as static text instead of + * causing parsing error when there was no correct {@code #} or {@code @} tag earlier in the + * same template. + * </li> + * <li><p> + * 2.3.20 (or higher): {@code ?html} will escape apostrophe-quotes just like {@code ?xhtml} does. Utilizing + * this is highly recommended, because otherwise if interpolations are used inside attribute values that use + * apostrophe-quotation (<tt><foo bar='${val}'></tt>) instead of plain quotation mark + * (<tt><foo bar="${val}"></tt>), they might produce HTML/XML that's not well-formed. Note that + * {@code ?html} didn't do this because long ago there was no cross-browser way of doing this, but it's not a + * concern anymore. + * </li> + * <li><p> + * 2.3.21 (or higher): + * <ul> + * <li><p> + * The <em>default</em> of the {@code object_wrapper} setting ({@link #getObjectWrapper()}) changes from + * {@code ObjectWrapper.DEFAULT_WRAPPER} to another almost identical {@link DefaultObjectWrapper} singleton, + * returned by {@link DefaultObjectWrapperBuilder#build()}. The new default object wrapper's + * "incompatible improvements" version is set to the same as of the {@link Configuration}. + * See {@link BeansWrapper#BeansWrapper(Version)} for further details. Furthermore, the new default + * object wrapper doesn't allow changing its settings; setter methods throw {@link IllegalStateException}). + * (If anything tries to call setters on the old default in your application, that's a dangerous bug that + * won't remain hidden now. As the old default is a singleton too, potentially shared by independently + * developed components, most of them expects the out-of-the-box behavior from it (and the others are + * necessarily buggy). Also, then concurrency glitches can occur (and even pollute the class introspection + * cache) because the singleton is modified after publishing to other threads.) + * Furthermore the new default object wrapper shares class introspection cache with other + * {@link BeansWrapper}-s created with {@link BeansWrapperBuilder}, which has an impact as + * {@link BeansWrapper#clearClassIntrospecitonCache()} will be disallowed; see more about it there. + * </li> + * <li><p> + * The {@code ?iso_...} built-ins won't show the time zone offset for {@link java.sql.Time} values anymore, + * because most databases store time values that aren't in any time zone, but just store hour, minute, + * second, and decimal second field values. If you still want to show the offset (like for PostgreSQL + * "time with time zone" columns you should), you can force showing the time zone offset by using + * {@code myTime?string.iso_fz} (and its other variants). + * </li> + * <li><p>{@code ?is_enumerable} correctly returns {@code false} for Java methods get from Java objects that + * are wrapped with {@link BeansWrapper} and its subclasses, like {@link DefaultObjectWrapper}. Although + * method values implement {@link TemplateSequenceModel} (because of a historical design quirk in + * {@link BeansWrapper}), trying to {@code #list} them will cause error, hence they aren't enumerable. + * </li> + * <li><p> + * {@code ?c} will return {@code "INF"}, {@code "-INF"} and {@code "NaN"} for positive/negative infinity + * and IEEE floating point Not-a-Number, respectively. These are the XML Schema compatible representations + * of these special values. Earlier it has returned what {@link DecimalFormat} did with US locale, none of + * which was understood by any (common) computer language. + * </li> + * <li><p> + * FTL hash literals that repeat keys now only have the key once with {@code ?keys}, and only has the last + * value associated to that key with {@code ?values}. This is consistent with the behavior of + * {@code hash[key]} and how maps work in Java. + * </li> + * <li><p>In most cases (where FreeMarker is able to do that), for {@link TemplateLoader}-s that use + * {@link URLConnection}, {@code URLConnection#setUseCaches(boolean)} will called with {@code false}, + * so that only FreeMarker will do caching, not the URL scheme's handler. + * See {@link URLTemplateLoader#setURLConnectionUsesCaches(Boolean)} for more details. + * </li> + * <li><p> + * The default of the {@code template_loader} setting ({@link Configuration#getTemplateLoader()}) changes + * to {@code null}, which means that FreeMarker will not find any templates. Earlier + * the default was a {@link FileTemplateLoader} that used the current directory as the root. This was + * dangerous and fragile as you usually don't have good control over what the current directory will be. + * Luckily, the old default almost never looked for the templates at the right place + * anyway, so pretty much all applications had to set the {@code template_loader} setting, so it's unlikely + * that changing the default breaks your application. + * </li> + * <li><p> + * Right-unlimited ranges become readable (like listable), so {@code <#list 1.. as i>...</#list>} works. + * Earlier they were only usable for slicing (like {@code hits[10..]}). + * </li> + * <li><p> + * Empty ranges return {@link Constants#EMPTY_SEQUENCE} instead of an empty {@link SimpleSequence}. This + * is in theory backward compatible, as the API only promises to give something that implements + * {@link TemplateSequenceModel}. + * </li> + * <li><p> + * Unclosed comments ({@code <#-- ...}) and {@code #noparse}-s won't be silently closed at the end of + * template anymore, but cause a parsing error instead. + * </li> + * </ul> + * </li> + * <li><p> + * 2.3.22 (or higher): + * <ul> + * <li><p> + * {@link DefaultObjectWrapper} has some substantial changes with {@code incompatibleImprovements} 2.3.22; + * check them out at {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. It's important to know + * that if you set the {@code object_wrapper} setting (to an other value than {@code "default"}), rather + * than leaving it on its default value, the {@code object_wrapper} won't inherit the + * {@code incompatibleImprovements} of the {@link Configuration}. In that case, if you want the 2.3.22 + * improvements of {@link DefaultObjectWrapper}, you have to set it in the {@link DefaultObjectWrapper} + * object itself too! (Note that it's OK to use a {@link DefaultObjectWrapper} with a different + * {@code incompatibleImprovements} version number than that of the {@link Configuration}, if that's + * really what you want.) + * </li> + * <li><p> + * In templates, {@code .template_name} will <em>always</em> return the main (top level) template's name. + * It won't be affected by {@code #include} and {@code #nested} anymore. This is unintended, a bug with + * {@code incompatible_improvement} 2.3.22 (a consequence of the lower level fixing described in the next + * point). The old behavior of {@code .template_name} is restored if you set + * {@code incompatible_improvement} to 2.3.23 (while {@link Configurable#getParent()}) of + * {@link Environment} keeps the changed behavior shown in the next point). + * </li> + * <li><p> + * {@code #include} and {@code #nested} doesn't change the parent {@link Template} (see + * {@link Configurable#getParent()}) of the {@link Environment} anymore to the {@link Template} that's + * included or whose namespace {@code #nested} "returns" to. Thus, the parent of {@link Environment} will + * be now always the main {@link Template}. (The main {@link Template} is the {@link Template} whose + * {@code process} or {@code createProcessingEnvironment} method was called to initiate the output + * generation.) Note that apart from the effect on FTL's {@code .template_name} (see + * previous point), this should only matter if you have set settings directly on {@link Template} objects, + * and almost nobody does that. Also note that macro calls have never changed the {@link Environment} + * parent to the {@link Template} that contains the macro definition, so this mechanism was always broken. + * As now we consistently never change the parent, the behavior when calling macros didn't change. + * </li> + * <li><p> + * When using {@code org.apache.freemarker.servlet.FreemarkerServlet}: + * <ul> + * <li> + * <p>When using custom JSP tag libraries: Fixes bug where some kind of + * values, when put into the JSP <em>page</em> scope (via {@code #global} or via the JSP + * {@code PageContext} API) and later read back with the JSP {@code PageContext} API (typically in a + * custom JSP tag), might come back as FreeMarker {@link TemplateModel} objects instead of as objects + * with a standard Java type. Other Servlet scopes aren't affected. It's highly unlikely that + * something expects the presence of this bug. The affected values are of the FTL types listed below, + * and to trigger the bug, they either had to be created directly in the template (like as an FTL + * literal or with {@code ?date}/{@code time}/{@code datetime}), or you had to use + * {@link DefaultObjectWrapper} or {@link SimpleObjectWrapper} (or a subclass of them): + * + * <ul> + * <li>FTL date/time/date-time values may came back as {@link SimpleDate}-s, now they come back as + * {@link java.util.Date java.util.Date}-s instead.</li> + * + * <li>FTL sequence values may came back as {@link SimpleSequence}-s, now they come back as + * {@link java.util.List}-s as expected. This at least stands assuming that the + * {@link Configuration#setSetting(String, String) object_wrapper} configuration setting is a + * subclass of {@link BeansWrapper} (such as {@link DefaultObjectWrapper}, which is the default), + * but that's practically always the case in applications that use FreeMarker's JSP extension + * (otherwise it can still work, but it depends on the quality and capabilities of the + * {@link ObjectWrapper} implementation).</li> + * + * <li>FTL hash values may came back as {@link SimpleHash}-es, now they come back as + * {@link java.util.Map}-s as expected (again, assuming that the object wrapper is a subclass of + * {@link BeansWrapper}, like preferably {@link DefaultObjectWrapper}, which is also the default). + * </li> + * + * <li>FTL collection values may came back as {@link SimpleCollection}-s, now they come back as + * {@link java.util.Collection}-s as expected (again, assuming that the object wrapper is a subclass + * of {@link BeansWrapper}, like preferably {@link DefaultObjectWrapper}).</li> + * </ul> + * </li> + * <li><p> + * Initial {@code "["} in the {@code TemplatePath} init-param + * has special meaning; it's used for specifying multiple comma separated locations, like in + * {@code <param-value>[ WEB-INF/templates, classpath:com/example/myapp/templates ]</param-value>} + * </li> + * <li><p> + * Initial <tt>"{"</tt> in the {@code TemplatePath} init-param is reserved for future purposes, and + * thus will throw exception. + * </li> + * </ul> + * </li> + * </ul> + * </li> + * <li><p> + * 2.3.23 (or higher): + * <ul> + * <li><p> + * Fixed a loophole in the implementation of the long existing parse-time rule that says that + * {@code #break}, in the FTL source code itself, must occur nested inside a breakable directive, such as + * {@code #list} or {@code #switch}. This check could be circumvented with {@code #macro} or + * {@code #function}, like this: + * {@code <#list 1..1 as x><#macro callMeLater><#break></#macro></#list><@callMeLater />}. + * After activating this fix, this will be a parse time error. + * </li> + * <li><p> + * If you have used {@code incompatible_improvements} 2.3.22 earlier, know that there the behavior of the + * {@code .template_name} special variable used in templates was accidentally altered, but now it's + * restored to be backward compatible with 2.3.0. (Ironically, the restored legacy behavior itself is + * broken when it comes to macro invocations, we just keep it for backward compatibility. If you need fixed + * behavior, use {@code .current_template_name} or {@code .main_template_name} instead.) + * </li> + * </ul> + * </li> + * <li><p> + * 2.3.24 (or higher): + * <ul> + * <li><p> + * The default of the + * {@link #setRecognizeStandardFileExtensions(boolean) recognize_standard_file_extensions} + * setting changes to {@code true}, which means that templates whose name ends with {@code ".ftlh"} or + * {@code ".ftlx"} will automatically get {@link HTMLOutputFormat#INSTANCE} or + * {@link XMLOutputFormat#INSTANCE} output format respectively, in both cases with + * {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY} {@link #setAutoEscapingPolicy(int) auto_escaping_policy}. + * These "file" extensions aren't case sensitive. + * </li> + * <li><p> + * In number format and date format strings (like in the {@code number_format} setting, or in templates in + * {@code n?string("0.##")}), an initial {@code '@'} has special meaning; they refer to a custom format + * with the name given after the {@code @} (see: {@link #setCustomNumberFormats(Map)}, + * {@link #setCustomDateFormats(Map)}, {@link #setNumberFormat(String)}, and {@link #setDateTimeFormat}). + * If the custom format doesn't exist, that will be an error. To have a literal {@code @} as the first + * character in the output, it has to be written as {@code @@}. Again, all this only applies to the very + * first character of the format string, so {@code @} characters elsewhere must not be doubled. Also, if + * there are any custom formats defined, initial {@code '@'} will have the new meaning regardless of + * the value of the {@code incompatible_improvements} setting. So you don't need to set the + * {@code incompatible_improvements} only to use custom formats. + * </li> + * <li><p> + * Expressions inside interpolations that were inside <em>string literal expressions</em> + * (not <code>${...}</code>-s in general), like in <code><#assign s="Hello ${name}!"></code>, has + * always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect). Now it's fixed. + * </li> + * <li><p> + * {@link DefaultObjectWrapper} has some minor changes with {@code incompatibleImprovements} 2.3.24; + * check them out at {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. It's important to know + * that if you set the {@code object_wrapper} setting (to an other value than {@code "default"}), rather + * than leaving it on its default value, the {@code object_wrapper} won't inherit the + * {@code incompatibleImprovements} of the {@link Configuration}. In that case, if you want the 2.3.24 + * improvements of {@link DefaultObjectWrapper}, you have to set it in the {@link DefaultObjectWrapper} + * object itself too! (Note that it's OK to use a {@link DefaultObjectWrapper} with a different + * {@code incompatibleImprovements} version number than that of the {@link Configuration}, if that's + * really what you want.) + * </li> + * <li><p> + * Fixed bug: The {@code #import} directive meant to copy the library variable into a global variable if + * it's executed in the main namespace, but that haven't happened when the imported template was already + * imported earlier in another namespace. + * </li> + * <li><p> + * {@code ?is_sequence} doesn't return {@code true} for Java methods wrapped by {@link BeansWrapper} and + * its subclasses (most notably {@link DefaultObjectWrapper}) anymore, as they only implement the + * {@code [index]} operator, but not {@code ?size}, which causes {@code <#list ...>} to fail among others. + * (They shouldn't implement either, but this is historical heritage.) + * </ul> + * </li> + * <li><p> + * 2.3.25 (or higher): + * <ul> + * <li><p> + * When calling {@link Configurable#setAutoIncludes(List)} on a {@link Configuration}, it filters out + * duplicates from the list, similarly as repeatedly calling {@link Configurable#addAutoInclude(String)} + * would, hence avoiding repeated inclusions. Calling {@link Configurable#setAutoIncludes(List)} on other + * {@link Configurable}-s always do this filtering regardless of the incompatible improvements setting. + * </ul> + * </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 2.3.0, or is {@code null}. + * + * @since 2.3.21 + */ + public Configuration(Version incompatibleImprovements) { + super(incompatibleImprovements); + + // We postpone this until here (rather that doing this in static initializer) for two reason: + // - Class initialization errors are often not reported very well + // - This way we avoid the error if FM isn't actually used + checkFreeMarkerVersionClash(); + + NullArgumentException.check("incompatibleImprovements", incompatibleImprovements); + this.incompatibleImprovements = incompatibleImprovements; + + createTemplateCache(); + loadBuiltInSharedVariables(); + } + + private static void checkFreeMarkerVersionClash() { + if (FM_24_DETECTED) { + throw new RuntimeException("Clashing FreeMarker versions (" + VERSION + " and some post-2.3.x) detected: " + + "found post-2.3.x class " + FM_24_DETECTION_CLASS_NAME + ". You probably have two different " + + "freemarker.jar-s in the classpath."); + } + } + + private void createTemplateCache() { + cache = new TemplateCache( + null, + getDefaultCacheStorage(), + getDefaultTemplateLookupStrategy(), + getDefaultTemplateNameFormat(), + null, + this); + cache.clear(); // for fully BC behavior + cache.setDelay(5000); + } + + private void recreateTemplateCacheWith( + TemplateLoader loader, CacheStorage storage, + TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat, + TemplateConfigurationFactory templateConfigurations) { + TemplateCache oldCache = cache; + cache = new TemplateCache( + loader, storage, templateLookupStrategy, templateNameFormat, templateConfigurations, this); + cache.clear(false); + cache.setDelay(oldCache.getDelay()); + cache.setLocalizedLookup(localizedLookup); + } + + private void recreateTemplateCache() { + recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage(), + cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), + getTemplateConfigurations()); + } + + private TemplateLookupStrategy getDefaultTemplateLookupStrategy() { + return getDefaultTemplateLookupStrategy(getIncompatibleImprovements()); + } + + static TemplateLookupStrategy getDefaultTemplateLookupStrategy(Version incompatibleImprovements) { + return TemplateLookupStrategy.DEFAULT_2_3_0; + } + + private TemplateNameFormat getDefaultTemplateNameFormat() { + return getDefaultTemplateNameFormat(getIncompatibleImprovements()); + } + + static TemplateNameFormat getDefaultTemplateNameFormat(Version incompatibleImprovements) { + return TemplateNameFormat.DEFAULT_2_3_0; + } + + private CacheStorage getDefaultCacheStorage() { + return createDefaultCacheStorage(getIncompatibleImprovements(), getCacheStorage()); + } + + static CacheStorage createDefaultCacheStorage(Version incompatibleImprovements, CacheStorage existingCacheStorage) { + if (existingCacheStorage instanceof DefaultSoftCacheStorage) { + return existingCacheStorage; + } + return new DefaultSoftCacheStorage(); + } + + static CacheStorage createDefaultCacheStorage(Version incompatibleImprovements) { + return createDefaultCacheStorage(incompatibleImprovements, null); + } + + private static class DefaultSoftCacheStorage extends SoftCacheStorage { + // Nothing to override + } + + private TemplateExceptionHandler getDefaultTemplateExceptionHandler() { + return getDefaultTemplateExceptionHandler(getIncompatibleImprovements()); + } + + private boolean getDefaultLogTemplateExceptions() { + return getDefaultLogTemplateExceptions(getIncompatibleImprovements()); + } + + private ObjectWrapper getDefaultObjectWrapper() { + return getDefaultObjectWrapper(getIncompatibleImprovements()); + } + + // Package visible as Configurable needs this to initialize the field defaults. + final static TemplateExceptionHandler getDefaultTemplateExceptionHandler(Version incompatibleImprovements) { + return TemplateExceptionHandler.DEBUG_HANDLER; + } + + // Package visible as Configurable needs this to initialize the field defaults. + final static boolean getDefaultLogTemplateExceptions(Version incompatibleImprovements) { + return true; + } + + @Override + public Object clone() { + try { + Configuration copy = (Configuration) super.clone(); + copy.sharedVariables = new HashMap(sharedVariables); + copy.localeToCharsetMap = new ConcurrentHashMap(localeToCharsetMap); + copy.recreateTemplateCacheWith( + cache.getTemplateLoader(), cache.getCacheStorage(), + cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), + cache.getTemplateConfigurations()); + return copy; + } catch (CloneNotSupportedException e) { + throw new BugException("Cloning failed", e); + } + } + + 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()); + } + + /** + * Loads a preset language-to-encoding map, similarly as if you have called + * {@link #clearEncodingMap()} and then did multiple {@link #setEncoding(Locale, String)} calls. + * It assumes the usual character encodings for most languages. + * The previous content of the encoding map will be lost. + * This default map currently contains the following mappings: + * + * <table style="width: auto; border-collapse: collapse" border="1" summary="preset language to encoding mapping"> + * <tr><td>ar</td><td>ISO-8859-6</td></tr> + * <tr><td>be</td><td>ISO-8859-5</td></tr> + * <tr><td>bg</td><td>ISO-8859-5</td></tr> + * <tr><td>ca</td><td>ISO-8859-1</td></tr> + * <tr><td>cs</td><td>ISO-8859-2</td></tr> + * <tr><td>da</td><td>ISO-8859-1</td></tr> + * <tr><td>de</td><td>ISO-8859-1</td></tr> + * <tr><td>el</td><td>ISO-8859-7</td></tr> + * <tr><td>en</td><td>ISO-8859-1</td></tr> + * <tr><td>es</td><td>ISO-8859-1</td></tr> + * <tr><td>et</td><td>ISO-8859-1</td></tr> + * <tr><td>fi</td><td>ISO-8859-1</td></tr> + * <tr><td>fr</td><td>ISO-8859-1</td></tr> + * <tr><td>hr</td><td>ISO-8859-2</td></tr> + * <tr><td>hu</td><td>ISO-8859-2</td></tr> + * <tr><td>is</td><td>ISO-8859-1</td></tr> + * <tr><td>it</td><td>ISO-8859-1</td></tr> + * <tr><td>iw</td><td>ISO-8859-8</td></tr> + * <tr><td>ja</td><td>Shift_JIS</td></tr> + * <tr><td>ko</td><td>EUC-KR</td></tr> + * <tr><td>lt</td><td>ISO-8859-2</td></tr> + * <tr><td>lv</td><td>ISO-8859-2</td></tr> + * <tr><td>mk</td><td>ISO-8859-5</td></tr> + * <tr><td>nl</td><td>ISO-8859-1</td></tr> + * <tr><td>no</td><td>ISO-8859-1</td></tr> + * <tr><td>pl</td><td>ISO-8859-2</td></tr> + * <tr><td>pt</td><td>ISO-8859-1</td></tr> + * <tr><td>ro</td><td>ISO-8859-2</td></tr> + * <tr><td>ru</td><td>ISO-8859-5</td></tr> + * <tr><td>sh</td><td>ISO-8859-5</td></tr> + * <tr><td>sk</td><td>ISO-8859-2</td></tr> + * <tr><td>sl</td><td>ISO-8859-2</td></tr> + * <tr><td>sq</td><td>ISO-8859-2</td></tr> + * <tr><td>sr</td><td>ISO-8859-5</td></tr> + * <tr><td>sv</td><td>ISO-8859-1</td></tr> + * <tr><td>tr</td><td>ISO-8859-9</td></tr> + * <tr><td>uk</td><td>ISO-8859-5</td></tr> + * <tr><td>zh</td><td>GB2312</td></tr> + * <tr><td>zh_TW</td><td>Big5</td></tr> + * </table> + * + * @see #clearEncodingMap() + * @see #setEncoding(Locale, String) + * @see #setDefaultEncoding(String) + */ + public void loadBuiltInEncodingMap() { + localeToCharsetMap.clear(); + localeToCharsetMap.put("ar", "ISO-8859-6"); + localeToCharsetMap.put("be", "ISO-8859-5"); + localeToCharsetMap.put("bg", "ISO-8859-5"); + localeToCharsetMap.put("ca", "ISO-8859-1"); + localeToCharsetMap.put("cs", "ISO-8859-2"); + localeToCharsetMap.put("da", "ISO-8859-1"); + localeToCharsetMap.put("de", "ISO-8859-1"); + localeToCharsetMap.put("el", "ISO-8859-7"); + localeToCharsetMap.put("en", "ISO-8859-1"); + localeToCharsetMap.put("es", "ISO-8859-1"); + localeToCharsetMap.put("et", "ISO-8859-1"); + localeToCharsetMap.put("fi", "ISO-8859-1"); + localeToCharsetMap.put("fr", "ISO-8859-1"); + localeToCharsetMap.put("hr", "ISO-8859-2"); + localeToCharsetMap.put("hu", "ISO-8859-2"); + localeToCharsetMap.put("is", "ISO-8859-1"); + localeToCharsetMap.put("it", "ISO-8859-1"); + localeToCharsetMap.put("iw", "ISO-8859-8"); + localeToCharsetMap.put("ja", "Shift_JIS"); + localeToCharsetMap.put("ko", "EUC-KR"); + localeToCharsetMap.put("lt", "ISO-8859-2"); + localeToCharsetMap.put("lv", "ISO-8859-2"); + localeToCharsetMap.put("mk", "ISO-8859-5"); + localeToCharsetMap.put("nl", "ISO-8859-1"); + localeToCharsetMap.put("no", "ISO-8859-1"); + localeToCharsetMap.put("pl", "ISO-8859-2"); + localeToCharsetMap.put("pt", "ISO-8859-1"); + localeToCharsetMap.put("ro", "ISO-8859-2"); + localeToCharsetMap.put("ru", "ISO-8859-5"); + localeToCharsetMap.put("sh", "ISO-8859-5"); + localeToCharsetMap.put("sk", "ISO-8859-2"); + localeToCharsetMap.put("sl", "ISO-8859-2"); + localeToCharsetMap.put("sq", "ISO-8859-2"); + localeToCharsetMap.put("sr", "ISO-8859-5"); + localeToCharsetMap.put("sv", "ISO-8859-1"); + localeToCharsetMap.put("tr", "ISO-8859-9"); + localeToCharsetMap.put("uk", "ISO-8859-5"); + localeToCharsetMap.put("zh", "GB2312"); + localeToCharsetMap.put("zh_TW", "Big5"); + } + + /** + * Clears language-to-encoding map. + * @see #loadBuiltInEncodingMap + * @see #setEncoding + */ + public void clearEncodingMap() { + localeToCharsetMap.clear(); + } + + /** + * Returns the default (singleton) Configuration object. Note that you can + * create as many separate configurations as you wish; this global instance + * is provided for convenience, or when you have no reason to use a separate + * instance. + * + * @deprecated The usage of the static singleton (the "default") + * {@link Configuration} instance can easily cause erroneous, unpredictable + * behavior. This is because multiple independent software components may use + * FreeMarker internally inside the same application, so they will interfere + * because of the common {@link Configuration} instance. Each such component + * should use its own private {@link Configuration} object instead, that it + * typically creates with <code>new Configuration()</code> when the component + * is initialized. + */ + @Deprecated + static public Configuration getDefaultConfiguration() { + Configuration defaultConfig = Configuration.defaultConfig; + if (defaultConfig == null) { + synchronized (defaultConfigLock) { + defaultConfig = Configuration.defaultConfig; + if (defaultConfig == null) { + defaultConfig = new Configuration(); + Configuration.defaultConfig = defaultConfig; + } + } + } + return defaultConfig; + } + + /** + * Sets the Configuration object that will be retrieved from future calls + * to {@link #getDefaultConfiguration()}. + * + * @deprecated Using the "default" {@link Configuration} instance can + * easily lead to erroneous, unpredictable behaviour. + * See more {@link Configuration#getDefaultConfiguration() here...}. + */ + @Deprecated + static public void setDefaultConfiguration(Configuration config) { + synchronized (defaultConfigLock) { + defaultConfig = config; + } + } + + /** + * Sets a {@link TemplateLoader} that is used to look up and load templates; + * as a side effect the template cache 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 (cache.getTemplateLoader() != templateLoader) { + recreateTemplateCacheWith(templateLoader, cache.getCacheStorage(), + cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), + cache.getTemplateConfigurations()); + } + 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; + } + } + + /** + * Tells if {@link #setTemplateLoader(TemplateLoader)} (or equivalent) was already called on this instance. + * + * @since 2.3.22 + */ + public boolean isTemplateLoaderExplicitlySet() { + return templateLoaderExplicitlySet; + } + + /** + * The getter pair of {@link #setTemplateLoader(TemplateLoader)}. + */ + public TemplateLoader getTemplateLoader() { + if (cache == null) { + return null; + } + return cache.getTemplateLoader(); + } + + /** + * Sets a {@link TemplateLookupStrategy} that is used to look up templates based on the requested name; as a side + * effect the template cache will be emptied. The default value is {@link TemplateLookupStrategy#DEFAULT_2_3_0}. + * + * @since 2.3.22 + */ + public void setTemplateLookupStrategy(TemplateLookupStrategy templateLookupStrategy) { + if (cache.getTemplateLookupStrategy() != templateLookupStrategy) { + recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage(), + templateLookupStrategy, cache.getTemplateNameFormat(), + cache.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 + */ + public boolean isTemplateLookupStrategyExplicitlySet() { + return templateLookupStrategyExplicitlySet; + } + + /** + * The getter pair of {@link #setTemplateLookupStrategy(TemplateLookupStrategy)}. + */ + public TemplateLookupStrategy getTemplateLookupStrategy() { + if (cache == null) { + return null; + } + return cache.getTemplateLookupStrategy(); + } + + /** + * Sets the template name format used. The default is {@link TemplateNameFormat#DEFAULT_2_3_0}, while the + * recommended value for new projects is {@link TemplateNameFormat#DEFAULT_2_4_0}. + * + * @since 2.3.22 + */ + public void setTemplateNameFormat(TemplateNameFormat templateNameFormat) { + if (cache.getTemplateNameFormat() != templateNameFormat) { + recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage(), + cache.getTemplateLookupStrategy(), templateNameFormat, + cache.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 + */ + public void unsetTemplateNameFormat() { + if (templateNameFormatExplicitlySet) { + setTemplateNameFormat(getDefaultTemplateNameFormat()); + templateNameFormatExplicitlySet = false; + } + } + + /** + * 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)}. + */ + public TemplateNameFormat getTemplateNameFormat() { + if (cache == null) { + return null; + } + return cache.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#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 + */ + public void setTemplateConfigurations(TemplateConfigurationFactory templateConfigurations) { + if (cache.getTemplateConfigurations() != templateConfigurations) { + if (templateConfigurations != null) { + templateConfigurations.setConfiguration(this); + } + recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage(), + cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), + templateConfigurations); + } + } + + /** + * The getter pair of {@link #setTemplateConfigurations(TemplateConfigurationFactory)}. + */ + public TemplateConfigurationFactory getTemplateConfigurations() { + if (cache == null) { + return null; + } + return cache.getTemplateConfigurations(); + } + + /** + * Sets the {@link CacheStorage} used for caching {@link Template}-s; + * the earlier content of the template cache 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.NullCacheStorage} (you can't use {@code null}). + * + * <p>Note that setting the cache storage will re-create the template cache, so + * all its content will be lost. + */ + 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) { + recreateTemplateCacheWith(cache.getTemplateLoader(), cacheStorage, + cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), + cache.getTemplateConfigurations()); + } + cacheStorageExplicitlySet = 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; + } + } + + /** + * Tells if {@link #setCacheStorage(CacheStorage)} (or equivalent) was already called on this instance. + * + * @since 2.3.22 + */ + public boolean isCacheStorageExplicitlySet() { + return cacheStorageExplicitlySet; + } + + /** + * 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 (cache == null) { + return null; + } + return cache.getCacheStorage(); + } + } + + /** + * 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. + */ + 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)); + } + + /** + * 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); + } + } + + /** + * 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) + */ + public void setClassForTemplateLoading(Class resourceLoaderClass, String basePackagePath) { + setTemplateLoader(new ClassTemplateLoader(resourceLoaderClass, basePackagePath)); + } + + /** + * 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 + */ + public void setClassLoaderForTemplateLoading(ClassLoader classLoader, String basePackagePath) { + setTemplateLoader(new ClassTemplateLoader(classLoader, basePackagePath)); + } + + /** + * Sets the time in seconds that must elapse before checking whether there is a newer version of a template "file" + * than the cached one. + * + * <p> + * Historical note: Despite what the API documentation said earlier, this method is <em>not</em> thread-safe. While + * it works well on most hardware, it's not guaranteed that FreeMarker will see the update in all threads, and + * theoretically it's also possible that it will see a value that's a binary mixture of the new and the old one. + * + * @deprecated Use {@link #setTemplateUpdateDelayMilliseconds(long)} instead, because the time granularity of this method + * is often misunderstood to be milliseconds. + */ + @Deprecated + public void setTemplateUpdateDelay(int seconds) { + cache.setDelay(1000L * seconds); + } + + /** + * 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 cache. 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 cache 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 + */ + public void setTemplateUpdateDelayMilliseconds(long millis) { + cache.setDelay(millis); + } + + /** + * The getter pair of {@link #setTemplateUpdateDelayMilliseconds(long)}. + * + * @since 2.3.23 + */ + public long getTemplateUpdateDelayMilliseconds() { + return cache.getDelay(); + } + + @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); + } + } + } + + /** + * 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 + */ + 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 + */ + public boolean isObjectWrapperExplicitlySet() { + return objectWrapperExplicitlySet; + } + + @Override + public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) { + super.setTemplateExceptionHandler(templateExceptionHandler); + templateExceptionHandlerExplicitlySet = 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 #isTemplateExceptionHandlerExplicitlySet()} will return {@code false}. + * + * @since 2.3.22 + */ + public void unsetTemplateExceptionHandler() { + if (templateExceptionHandlerExplicitlySet) { + setTemplateExceptionHandler(getDefaultTemplateExceptionHandler()); + templateExceptionHandlerExplicitlySet = false; + } + } + + /** + * 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; + } + + /** + * 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 + */ + public void unsetLogTemplateExceptions() { + if (logTemplateExceptionsExplicitlySet) { + setLogTemplateExceptions(getDefaultLogTemplateExceptions()); + logTemplateExceptionsExplicitlySet = false; + } + } + + /** + * Tells if {@link #setLogTemplateExceptions(boolean)} (or equivalent) was already called on this instance. + * + * @since 2.3.22 + */ + public boolean isLogTemplateExceptionsExplicitlySet() { + return logTemplateExceptionsExplicitlySet; + } + + /** + * Use {@link #Configuration(Version)} instead if possible; see the meaning of the parameter there. + * If the default value of a setting depends on the {@code incompatibleImprovements} and t
<TRUNCATED>
