freemarker.template.Configuration Maven / Gradle / Ivy
Show all versions of freemarker-gae Show documentation
/*
* 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 freemarker.template;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
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.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import freemarker.cache.CacheStorage;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MruCacheStorage;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.SoftCacheStorage;
import freemarker.cache.TemplateCache;
import freemarker.cache.TemplateCache.MaybeMissingTemplate;
import freemarker.cache.TemplateConfigurationFactory;
import freemarker.cache.TemplateLoader;
import freemarker.cache.TemplateLookupContext;
import freemarker.cache.TemplateLookupStrategy;
import freemarker.cache.TemplateNameFormat;
import freemarker.cache.URLTemplateLoader;
import freemarker.core.BugException;
import freemarker.core.CSSOutputFormat;
import freemarker.core.CombinedMarkupOutputFormat;
import freemarker.core.Configurable;
import freemarker.core.Environment;
import freemarker.core.HTMLOutputFormat;
import freemarker.core.JSONOutputFormat;
import freemarker.core.JavaScriptOutputFormat;
import freemarker.core.MarkupOutputFormat;
import freemarker.core.OutputFormat;
import freemarker.core.ParseException;
import freemarker.core.ParserConfiguration;
import freemarker.core.PlainTextOutputFormat;
import freemarker.core.RTFOutputFormat;
import freemarker.core.TemplateConfiguration;
import freemarker.core.TemplateMarkupOutputModel;
import freemarker.core.UndefinedOutputFormat;
import freemarker.core.UnregisteredOutputFormatException;
import freemarker.core.XHTMLOutputFormat;
import freemarker.core.XMLOutputFormat;
import freemarker.core._CoreAPI;
import freemarker.core._DelayedJQuote;
import freemarker.core._MiscTemplateException;
import freemarker.core._ObjectBuilderSettingEvaluator;
import freemarker.core._SettingEvaluationEnvironment;
import freemarker.core._SortedArraySet;
import freemarker.core._UnmodifiableCompositeSet;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.log.Logger;
import freemarker.template.utility.CaptureOutput;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.Constants;
import freemarker.template.utility.HtmlEscape;
import freemarker.template.utility.NormalizeNewlines;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.SecurityUtilities;
import freemarker.template.utility.StandardCompress;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.XmlEscape;
/**
* The main entry point into the FreeMarker API; encapsulates the configuration settings of FreeMarker,
* also serves as a central template-loading and caching service.
*
* 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.)
*
*
The basic usage pattern is like:
*
*
* // Where the application is initialized; in general you do this ONLY ONCE in the application life-cycle!
* Configuration cfg = new Configuration(VERSION_X_Y_Z));
* // Where VERSION_X_Y_Z enables the not-100%-backward-compatible fixes introduced in
* // FreeMarker version X.Y.Z and earlier (see {@link #Configuration(Version)}).
* cfg.setSomeSetting(...);
* cfg.setOtherSetting(...);
* ...
*
* // 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.ftlh");
* myTemplate.{@link Template#process(Object, java.io.Writer) process}(dataModel, out);
*
* A couple of settings that you should not leave on its default value are:
*
* - {@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.)
*
- {@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.
*
- {@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.
*
*
*
* A {@link Configuration} object is thread-safe only after you have stopped modifying the configuration settings,
* and you have safely published 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 Logger CACHE_LOG = Logger.getLogger("freemarker.cache");
private static final String VERSION_PROPERTIES_PATH = "/freemarker/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 STRICT_SYNTAX_KEY_SNAKE_CASE = "strict_syntax";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
public static final String STRICT_SYNTAX_KEY_CAMEL_CASE = "strictSyntax";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String STRICT_SYNTAX_KEY = STRICT_SYNTAX_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.
*/
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.
*/
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.
*/
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.28 */
public static final String INTERPOLATION_SYNTAX_KEY_SNAKE_CASE = "interpolation_syntax";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.28 */
public static final String INTERPOLATION_SYNTAX_KEY_CAMEL_CASE = "interpolationSyntax";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String INTERPOLATION_SYNTAX_KEY = INTERPOLATION_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";
/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.29 */
public static final String FALLBACK_ON_NULL_LOOP_VARIABLE_KEY_SNAKE_CASE = "fallback_on_null_loop_variable";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.29 */
public static final String FALLBACK_ON_NULL_LOOP_VARIABLE_KEY_CAMEL_CASE = "fallbackOnNullLoopVariable";
/** Alias to the {@code ..._SNAKE_CASE} variation. @since 2.3.25 */
public static final String FALLBACK_ON_NULL_LOOP_VARIABLE_KEY = FALLBACK_ON_NULL_LOOP_VARIABLE_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,
DEFAULT_ENCODING_KEY_SNAKE_CASE,
FALLBACK_ON_NULL_LOOP_VARIABLE_KEY_SNAKE_CASE,
INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE,
INTERPOLATION_SYNTAX_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,
STRICT_SYNTAX_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,
FALLBACK_ON_NULL_LOOP_VARIABLE_KEY_CAMEL_CASE,
INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE,
INTERPOLATION_SYNTAX_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,
STRICT_SYNTAX_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 STANDARD_OUTPUT_FORMATS;
static {
STANDARD_OUTPUT_FORMATS = new HashMap<>();
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);
}
/**
* The parser decides between {@link #ANGLE_BRACKET_TAG_SYNTAX} and {@link #SQUARE_BRACKET_TAG_SYNTAX} based on the
* first tag (like {@code [#if x]} or {@code <#if x>}) it mets. Note that {@code [=...]} is not a tag, but
* an interpolation, so it's not used for tag syntax auto-detection.
*/
public static final int AUTO_DETECT_TAG_SYNTAX = 0;
/** For example {@code <#if x><@foo />#if>} */
public static final int ANGLE_BRACKET_TAG_SYNTAX = 1;
/**
* For example {@code [#if x][@foo /][/#if]}.
* It does not change ${x}
to {@code [=x]}; that's square bracket interpolation
* syntax ({@link #SQUARE_BRACKET_INTERPOLATION_SYNTAX}).
*/
public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
/** ${expression}
and the deprecated #{expression; numFormat}
@since 2.3.28 */
public static final int LEGACY_INTERPOLATION_SYNTAX = 20;
/** ${expression}
only (not #{expression; numFormat}
) @since 2.3.28 */
public static final int DOLLAR_INTERPOLATION_SYNTAX = 21;
/**
* [=expression]
instead of ${expression}
.
* It does not change {@code <#if x>} to {@code [#if x]}; that's square bracket tag syntax
* ({@link #SQUARE_BRACKET_TAG_SYNTAX}).
* @since 2.3.28
*/
public static final int SQUARE_BRACKET_INTERPOLATION_SYNTAX = 22;
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 2.3.27 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_27 = new Version(2, 3, 27);
/** FreeMarker version 2.3.28 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_28 = new Version(2, 3, 28);
/** FreeMarker version 2.3.29 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_29 = new Version(2, 3, 29);
/** FreeMarker version 2.3.30 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_30 = new Version(2, 3, 30);
/** FreeMarker version 2.3.31 (an {@link #Configuration(Version) incompatible improvements break-point}) */
public static final Version VERSION_2_3_31 = new Version(2, 3, 31);
/** 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 String JVM_DEFAULT = "JVM default";
private static final Version VERSION;
static {
try {
Properties props = ClassUtil.loadProperties(Configuration.class, VERSION_PROPERTIES_PATH);
String versionString = getRequiredVersionProperty(props, "version");
Date buildDate;
{
String buildDateStr = getRequiredVersionProperty(props, "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(props, "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 = "freemarker.core._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 boolean strictSyntax = true;
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 registeredCustomOutputFormats = Collections.emptyMap();
private Version incompatibleImprovements;
private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX;
private int interpolationSyntax = LEGACY_INTERPOLATION_SYNTAX;
private int namingConvention = AUTO_DETECT_NAMING_CONVENTION;
private int tabSize = 8; // Default from JavaCC 3.x
private boolean fallbackOnNullLoopVariable = true; // Default for backward compatibility
private boolean preventStrippings;
private TemplateCache cache;
private boolean templateLoaderExplicitlySet;
private boolean templateLookupStrategyExplicitlySet;
private boolean templateNameFormatExplicitlySet;
private boolean cacheStorageExplicitlySet;
private boolean objectWrapperExplicitlySet;
private boolean templateExceptionHandlerExplicitlySet;
private boolean attemptExceptionReporterExplicitlySet;
private boolean logTemplateExceptionsExplicitlySet;
private boolean wrapUncheckedExceptionsExplicitlySet;
private boolean localeExplicitlySet;
private boolean defaultEncodingExplicitlySet;
private boolean timeZoneExplicitlySet;
private HashMap/**/ 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/**/ rewrappableSharedVariables = null;
private String defaultEncoding = getDefaultDefaultEncoding();
private ConcurrentMap localeToCharsetMap = new ConcurrentHashMap();
/**
* Same as {@link #Configuration(Version) Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS)}.
*
* @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.
*
* About the "incompatible improvements" setting
*
*
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 fixed
* FreeMarker version that you start the development with. In older projects it's also usually better to keep
* this high, however you should check the changes activated (find them below), especially 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 low risk. The default value is 2.3.0 to
* maximize backward compatibility, but that value isn't recommended.
*
*
Bugfixes and improvements that are fully backward compatible, also those that are important security fixes,
* are enabled regardless of the incompatible improvements setting.
*
*
Do NOT ever use {@link #getVersion()} to set the "incompatible improvements". Always use a fixed value, like
* {@link #VERSION_2_3_30}. Otherwise your application can break as you upgrade FreeMarker. (As of 2.3.30, doing
* this will be logged as an error. As of 2.4.0, it will be probably disallowed, by throwing exception.)
*
*
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.
*
*
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.
*
*
Currently the effects of this setting are:
*
*
* 2.3.0: This is the lowest supported value, the version used in very old projects. This is the default in the
* FreeMarker 2.3.x series (the one used by the deprecated {@link #Configuration()} constructor) for maximum
* backward compatibility.
*
*
* 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.
*
*
* 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 (<foo bar='${val}'>) instead of plain quotation mark
* (<foo bar="${val}">), 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.
*
*
* 2.3.21 (or higher):
*
*
* The default of the {@code object_wrapper} setting ({@link #getObjectWrapper()}) changes from
* {@link 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#clearClassIntrospectionCache()} will be disallowed; see more about it there.
*
*
* 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).
*
* {@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.
*
*
* {@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.
*
*
* 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.
*
* 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.
*
*
* 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.
*
*
* 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..]}).
*
*
* 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}.
*
*
* Unclosed comments ({@code <#-- ...}) and {@code #noparse}-s won't be silently closed at the end of
* template anymore, but cause a parsing error instead.
*
*
*
*
* 2.3.22 (or higher):
*
*
* {@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.)
*
*
* In templates, {@code .template_name} will always 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).
*
*
* {@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.
*
*
* When using {@code freemarker.ext.servlet.FreemarkerServlet}:
*
* -
*
When using custom JSP tag libraries: Fixes bug where some kind of
* values, when put into the JSP page 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):
*
*
* - 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.
*
* - 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).
*
* - 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).
*
*
* - 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}).
*
*
*
* Initial {@code "["} in the {@code TemplatePath} init-param
* has special meaning; it's used for specifying multiple comma separated locations, like in
* {@code [ WEB-INF/templates, classpath:com/example/myapp/templates ] }
*
*
* Initial "{" in the {@code TemplatePath} init-param is reserved for future purposes, and
* thus will throw exception.
*
*
*
*
*
*
* 2.3.23 (or higher):
*
*
* 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.
*
*
* 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.)
*
*
*
*
* 2.3.24 (or higher):
*
*
* 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.
*
*
* 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.
*
*
* Expressions inside interpolations that were inside string literal expressions
* (not ${...}
-s in general), like in <#assign s="Hello ${name}!">
, has
* always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect). Now it's fixed.
*
*
* {@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.)
*
*
* 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.
*
*
* {@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.)
*
*
*
* 2.3.25 (or higher):
*
*
* 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.
*
*
*
* 2.3.26 (or higher):
*
*
* {@link BeansWrapper} and {@link DefaultObjectWrapper} now exposes Java 8 default methods (and the bean
* properties they define); see {@link BeansWrapper#BeansWrapper(Version)}.
*
*
*
* 2.3.27 (or higher):
*
*
* {@link BeansWrapper} and {@link DefaultObjectWrapper} now prefers the non-indexed JavaBean property
* read method over the indexed read method when Java 8 exposes both;
* see {@link BeansWrapper#BeansWrapper(Version)}.
*
* The following unchecked exceptions (but not their subclasses) will be wrapped into
* {@link TemplateException}-s when thrown during evaluating expressions or calling directives:
* {@link NullPointerException}, {@link ClassCastException}, {@link IndexOutOfBoundsException}, and
* {@link InvocationTargetException}. The goal of this is the same as of setting
* {@link #setWrapUncheckedExceptions(boolean) wrap_unchecked_exceptions} to {@code true} (see more there),
* but this is more backward compatible, as it avoids wrapping unchecked exceptions that the calling
* application is likely to catch specifically (like application-specific unchecked exceptions).
*
* When the {@link Writer} returned by {@link TemplateTransformModel#getWriter(Writer, Map)} implements
* {@link TransformControl}, exceptions that are used internally by FreeMarker for flow control (for
* {@code <#return>}, {@code <#break>}, etc.) won't be passed to
* {@link TransformControl#onError(Throwable)} anymore. Earlier, if {@code onError} didn't rethrow the
* exception (though almost all implementation does), you couldn't use said directives inside the
* transformed block. It's very unlikely that user code is affected by this, partially because these aren't
* commonly implemented interfaces (especially not {@link TransformControl}), and because it's unlikely
* that templates utilize the the bug that's not fixed.
*
*
*
* 2.3.28 (or higher):
*
* When calling a macro or function (things defined in a template, not directly in Java) and the
* argument list contains {@code .current_template_name}, now it will correctly evaluate to the template
* that contains the call, rather than to the template that contains the macro or function definition.
* (Of course, the parameter default value expression is still evaluated in the context of the called
* macro or function.) Similarly, {@code .macro_caller_template_name} (which itself was added in 2.3.28),
* when used in a macro call argument, won't be incorrectly evaluated in the context of the called macro.
*
Fixed legacy parser glitch where a tag can be closed with an illegal {@code ]} (when it's not part
* of an expression) despite that the tag syntax is set to angle brackets. For example {@code <#if x]}
* worked just like {@code <#if x>}. Note that it doesn't affect the legal usage of {@code ]}, like
* {@code <#if x[0]>} works correctly without this fix as well.
*
*
*
* 2.3.31 (or higher):
*
* When you set the {@code number_format} setting to {@code "computer"} (or you call
* {@link Environment#getCNumberFormat()}), the format now matches the behavior of {@code ?c}, when formatting
* infinite (positive and negative), and NaN. Matching the behavior of {@code ?c} was always the intent,
* but before this incompatible improvement, the {@code "computer"} format always behaved like {@code ?c}
* before Incompatible Improvements 2.3.21, where instead of INF, and NaN, the results used unicode characters
* U+221E, and U+FFFD.
*
*
*
*
* @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);
checkCurrentVersionNotRecycled(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(
getDefaultTemplateLoader(),
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(); // for fully BC behavior
cache.setDelay(oldCache.getDelay());
cache.setLocalizedLookup(localizedLookup);
}
private void recreateTemplateCache() {
recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage(),
cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(),
getTemplateConfigurations());
}
private TemplateLoader getDefaultTemplateLoader() {
return createDefaultTemplateLoader(getIncompatibleImprovements(), getTemplateLoader());
}
static TemplateLoader createDefaultTemplateLoader(Version incompatibleImprovements) {
return createDefaultTemplateLoader(incompatibleImprovements, null);
}
private static TemplateLoader createDefaultTemplateLoader(
Version incompatibleImprovements, TemplateLoader existingTemplateLoader) {
if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
if (existingTemplateLoader instanceof LegacyDefaultFileTemplateLoader) {
return existingTemplateLoader;
}
try {
return new LegacyDefaultFileTemplateLoader();
} catch (Exception e) {
CACHE_LOG.warn("Couldn't create legacy default TemplateLoader which accesses the current directory. "
+ "(Use new Configuration(Configuration.VERSION_2_3_21) or higher to avoid this.)", e);
return null;
}
} else {
return null;
}
}
private static class LegacyDefaultFileTemplateLoader extends FileTemplateLoader {
public LegacyDefaultFileTemplateLoader() throws IOException {
super();
}
}
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 AttemptExceptionReporter getDefaultAttemptExceptionReporter() {
return getDefaultAttemptExceptionReporter(getIncompatibleImprovements());
}
private boolean getDefaultLogTemplateExceptions() {
return getDefaultLogTemplateExceptions(getIncompatibleImprovements());
}
private boolean getDefaultWrapUncheckedExceptions() {
return getDefaultWrapUncheckedExceptions(getIncompatibleImprovements());
}
private ObjectWrapper getDefaultObjectWrapper() {
return getDefaultObjectWrapper(getIncompatibleImprovements());
}
// Package visible as Configurable needs this to initialize the field defaults.
static TemplateExceptionHandler getDefaultTemplateExceptionHandler(Version incompatibleImprovements) {
return TemplateExceptionHandler.DEBUG_HANDLER;
}
// Package visible as Configurable needs this to initialize the field defaults.
static AttemptExceptionReporter getDefaultAttemptExceptionReporter(Version incompatibleImprovements) {
return AttemptExceptionReporter.LOG_ERROR_REPORTER;
}
// Package visible as Configurable needs this to initialize the field defaults.
static boolean getDefaultLogTemplateExceptions(Version incompatibleImprovements) {
return true;
}
// Package visible as Configurable needs this to initialize the field defaults.
static boolean getDefaultWrapUncheckedExceptions(Version incompatibleImprovements) {
return false;
}
@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:
*
*
* ar ISO-8859-6
* be ISO-8859-5
* bg ISO-8859-5
* ca ISO-8859-1
* cs ISO-8859-2
* da ISO-8859-1
* de ISO-8859-1
* el ISO-8859-7
* en ISO-8859-1
* es ISO-8859-1
* et ISO-8859-1
* fi ISO-8859-1
* fr ISO-8859-1
* hr ISO-8859-2
* hu ISO-8859-2
* is ISO-8859-1
* it ISO-8859-1
* iw ISO-8859-8
* ja Shift_JIS
* ko EUC-KR
* lt ISO-8859-2
* lv ISO-8859-2
* mk ISO-8859-5
* nl ISO-8859-1
* no ISO-8859-1
* pl ISO-8859-2
* pt ISO-8859-1
* ro ISO-8859-2
* ru ISO-8859-5
* sh ISO-8859-5
* sk ISO-8859-2
* sl ISO-8859-2
* sq ISO-8859-2
* sr ISO-8859-5
* sv ISO-8859-1
* tr ISO-8859-9
* uk ISO-8859-5
* zh GB2312
* zh_TW Big5
*
*
* @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 new Configuration()
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 (unless the new and the old values are the same).
* By providing your own {@link TemplateLoader} implementation, you can load templates from whatever kind of
* storages, like from relational databases, NoSQL-storages, etc.
*
* 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)}.
*
*
You can chain several {@link TemplateLoader}-s together with {@link MultiTemplateLoader}.
*
*
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(getDefaultTemplateLoader());
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 the {@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.
*
*
* 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.
*
*
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 freemarker.cache.NullCacheStorage} (you can't use {@code null}).
*
*
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.
*
*
* Note that FreeMarker can load templates from non-file-system sources too. See
* {@link #setTemplateLoader(TemplateLoader)} from more details.
*
*
* 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 freemarker.cache.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("freemarker.cache.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.
*
*
* Historical note: Despite what the API documentation said earlier, this method is not 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" than the cached one. Defaults to 5000 ms.
*
*
* 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();
}
/**
* Sets whether directives such as {@code if}, {@code else}, etc must be written as {@code #if}, {@code #else}, etc.
* Defaults to {@code true}.
*
*
When this is {@code true},
* any tag not starting with <# or </# or <@ or </@ is considered as plain text
* and will go to the output as is. Tag starting with <# or </# must
* be valid FTL tag, or else the template is invalid (i.e. <#noSuchDirective>
* is an error).
*
* @deprecated Only {@code true} (the default) value will be supported sometimes in the future.
*/
@Deprecated
public void setStrictSyntaxMode(boolean b) {
strictSyntax = b;
}
@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 setLocale(Locale locale) {
super.setLocale(locale);
localeExplicitlySet = true;
}
/**
* Resets the setting to its default, as if it was never set.
*
* @since 2.3.26
*/
public void unsetLocale() {
if (localeExplicitlySet) {
setLocale(getDefaultLocale());
localeExplicitlySet = false;
}
}
/**
* 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;
}
static Locale getDefaultLocale() {
return Locale.getDefault();
}
@Override
public void setTimeZone(TimeZone timeZone) {
super.setTimeZone(timeZone);
timeZoneExplicitlySet = true;
}
/**
* Resets the setting to its default, as if it was never set.
*
* @since 2.3.26
*/
public void unsetTimeZone() {
if (timeZoneExplicitlySet) {
setTimeZone(getDefaultTimeZone());
timeZoneExplicitlySet = false;
}
}
/**
* 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;
}
static TimeZone getDefaultTimeZone() {
return TimeZone.getDefault();
}
@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;
}
@Override
public void setAttemptExceptionReporter(AttemptExceptionReporter attemptExceptionReporter) {
super.setAttemptExceptionReporter(attemptExceptionReporter);
attemptExceptionReporterExplicitlySet = 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 #isAttemptExceptionReporterExplicitlySet()} will return {@code false}.
*
* @since 2.3.27
*/
public void unsetAttemptExceptionReporter() {
if (attemptExceptionReporterExplicitlySet) {
setAttemptExceptionReporter(getDefaultAttemptExceptionReporter());
attemptExceptionReporterExplicitlySet = false;
}
}
/**
* Tells if {@link #setAttemptExceptionReporter(AttemptExceptionReporter)} (or equivalent) was already called on
* this instance.
*
* @since 2.3.27
*/
public boolean isAttemptExceptionReporterExplicitlySet() {
return attemptExceptionReporterExplicitlySet;
}
/**
* {@inheritDoc}
*
* @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;
}
/**
* {@inheritDoc}
*
* @since 2.3.27
*/
@Override
public void setWrapUncheckedExceptions(boolean value) {
super.setWrapUncheckedExceptions(value);
wrapUncheckedExceptionsExplicitlySet = true;
}
/**
* @since 2.3.27
*/
public void unsetWrapUncheckedExceptions() {
if (wrapUncheckedExceptionsExplicitlySet) {
setWrapUncheckedExceptions(getDefaultWrapUncheckedExceptions());
wrapUncheckedExceptionsExplicitlySet = false;
}
}
/**
* Tells if {@link #setWrapUncheckedExceptions} (or equivalent) was already called on this instance.
*
* @since 2.3.27
*/
public boolean isWrapUncheckedExceptionsExplicitlySet() {
return wrapUncheckedExceptionsExplicitlySet;
}
/**
* The getter pair of {@link #setStrictSyntaxMode}.
*/
@Override
public boolean getStrictSyntaxMode() {
return strictSyntax;
}
/**
* Use {@link #Configuration(Version)} instead if possible; see the meaning of the parameter there.
*
*
Do NOT ever use {@link #getVersion()} to set the "incompatible improvements". Always use a fixed value, like
* {@link #VERSION_2_3_30}. Otherwise your application can break as you upgrade FreeMarker. (As of 2.3.30, doing
* this will be logged as an error. As of 2.4.0, it will be probably disallowed, by throwing exception.)
*
*
If the default value of a setting depends on the {@code incompatibleImprovements} and the value of that setting
* was never set in this {@link Configuration} object through the public API, its value will be set to the default
* value appropriate for the new {@code incompatibleImprovements}. (This adjustment of a setting value doesn't
* count as setting that setting, so setting {@code incompatibleImprovements} for multiple times also works as
* expected.) Note that if the {@code template_loader} have to be changed because of this, the template cache will
* be emptied.
*
* @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.20
*/
public void setIncompatibleImprovements(Version incompatibleImprovements) {
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
if (!this.incompatibleImprovements.equals(incompatibleImprovements)) {
checkCurrentVersionNotRecycled(incompatibleImprovements);
this.incompatibleImprovements = incompatibleImprovements;
if (!templateLoaderExplicitlySet) {
templateLoaderExplicitlySet = true;
unsetTemplateLoader();
}
if (!templateLookupStrategyExplicitlySet) {
templateLookupStrategyExplicitlySet = true;
unsetTemplateLookupStrategy();
}
if (!templateNameFormatExplicitlySet) {
templateNameFormatExplicitlySet = true;
unsetTemplateNameFormat();
}
if (!cacheStorageExplicitlySet) {
cacheStorageExplicitlySet = true;
unsetCacheStorage();
}
if (!templateExceptionHandlerExplicitlySet) {
templateExceptionHandlerExplicitlySet = true;
unsetTemplateExceptionHandler();
}
if (!attemptExceptionReporterExplicitlySet) {
attemptExceptionReporterExplicitlySet = true;
unsetAttemptExceptionReporter();
}
if (!logTemplateExceptionsExplicitlySet) {
logTemplateExceptionsExplicitlySet = true;
unsetLogTemplateExceptions();
}
if (!wrapUncheckedExceptionsExplicitlySet) {
wrapUncheckedExceptionsExplicitlySet = true;
unsetWrapUncheckedExceptions();
}
if (!objectWrapperExplicitlySet) {
objectWrapperExplicitlySet = true;
unsetObjectWrapper();
}
recreateTemplateCache();
}
}
private static void checkCurrentVersionNotRecycled(Version incompatibleImprovements) {
_TemplateAPI.checkCurrentVersionNotRecycled(
incompatibleImprovements,
"freemarker.configuration", "Configuration");
}
/**
* @see #setIncompatibleImprovements(Version)
* @return Never {@code null}.
* @since 2.3.20
*/
@Override
public Version getIncompatibleImprovements() {
return incompatibleImprovements;
}
/**
* @deprecated Use {@link #Configuration(Version)}, or
* as last chance, {@link #setIncompatibleImprovements(Version)} instead.
*/
@Deprecated
public void setIncompatibleEnhancements(String version) {
setIncompatibleImprovements(new Version(version));
}
/**
* @deprecated Use {@link #getIncompatibleImprovements()} instead.
*/
@Deprecated
public String getIncompatibleEnhancements() {
return incompatibleImprovements.toString();
}
/**
* @deprecated Use {@link #getIncompatibleImprovements()} instead.
*/
@Deprecated
public int getParsedIncompatibleEnhancements() {
return getIncompatibleImprovements().intValue();
}
/**
* Sets whether the FTL parser will try to remove
* superfluous white-space around certain FTL tags.
*/
public void setWhitespaceStripping(boolean b) {
whitespaceStripping = b;
}
/**
* Gets whether the FTL parser will try to remove
* superfluous white-space around certain FTL tags.
*
* @see #setWhitespaceStripping
*/
@Override
public boolean getWhitespaceStripping() {
return whitespaceStripping;
}
/**
* Sets when auto-escaping should be enabled depending on the current {@linkplain OutputFormat output format};
* default is {@link #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=...>}, {@code <#autoesc>...#autoesc>}, and {@code <#noautoesc>...#noautoesc>}, which
* are ignoring the auto-escaping policy.
*
*
About auto-escaping
*
*
* Auto-escaping has significance when a value is printed with ${...}
(or #{...}
). 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 #setOutputFormat(OutputFormat)}
* and {@link TemplateConfiguration#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.
*
*
Further notes on auto-escaping:
*
* - When printing numbers, dates, and other kind of non-string values with
${...}
, they will be
* first converted to string (according the formatting settings and locale), then they are escaped just like
* string values.
* - When printing {@link TemplateMarkupOutputModel}-s, they aren't escaped again (they are already escaped).
*
- 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}.
*
- 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
<#assign s = "foo${bar}">
, {@code bar} will always get into {@code s}
* without escaping, but with <#assign s>foo${bar}<#assign>
it may will be escaped.
*
*
* Note that what you set here is just a default, which can be overridden for individual templates via
* {@link #setTemplateConfigurations(TemplateConfigurationFactory)}. This setting is also overridden by the standard file
* extensions; see them at {@link #setRecognizeStandardFileExtensions(boolean)}.
*
* @param autoEscapingPolicy
* One of the {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
* {@link #ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}, and {@link #DISABLE_AUTO_ESCAPING_POLICY} constants.
*
* @see TemplateConfiguration#setAutoEscapingPolicy(int)
* @see Configuration#setOutputFormat(OutputFormat)
* @see TemplateConfiguration#setOutputFormat(OutputFormat)
*
* @since 2.3.24
*/
public void setAutoEscapingPolicy(int autoEscapingPolicy) {
_TemplateAPI.validateAutoEscapingPolicyValue(autoEscapingPolicy);
int prevAutoEscaping = getAutoEscapingPolicy();
this.autoEscapingPolicy = autoEscapingPolicy;
if (prevAutoEscaping != autoEscapingPolicy) {
clearTemplateCache();
}
}
/**
* Getter pair of {@link #setAutoEscapingPolicy(int)}
*
* @since 2.3.24
*/
@Override
public int getAutoEscapingPolicy() {
return autoEscapingPolicy;
}
/**
* Sets the default output format. Usually, you should leave this on its default, which is
* {@link UndefinedOutputFormat#INSTANCE}, and then use standard file extensions like "ftlh" (for HTML) or "ftlx"
* (for XML) and ensure that {@link #setRecognizeStandardFileExtensions(boolean)} is {@code true} (see more there).
* Where you can't use the standard extensions, templates still can be associated to output formats with
* patterns matching their name (their path) using {@link #setTemplateConfigurations(TemplateConfigurationFactory)}.
* But if all templates will have the same output format, you may use {@link #setOutputFormat(OutputFormat)} after
* all, to a value like {@link HTMLOutputFormat#INSTANCE}, {@link XMLOutputFormat#INSTANCE}, etc. Also note
* that templates can specify their own output format like {@code
* <#ftl output_format="HTML">}, which overrides any configuration settings.
*
*
* The output format is mostly important because of auto-escaping (see {@link #setAutoEscapingPolicy(int)}), but
* maybe also used by the embedding application to set the HTTP response MIME type, etc.
*
* @param outputFormat
* Not {@code null}; use {@link UndefinedOutputFormat#INSTANCE} instead.
*
* @see #setRegisteredCustomOutputFormats(Collection)
* @see #setTemplateConfigurations(TemplateConfigurationFactory)
* @see #setRecognizeStandardFileExtensions(boolean)
* @see #setAutoEscapingPolicy(int)
*
* @since 2.3.24
*/
public void setOutputFormat(OutputFormat outputFormat) {
if (outputFormat == null) {
throw new NullArgumentException(
"outputFormat",
"You may meant: " + UndefinedOutputFormat.class.getSimpleName() + ".INSTANCE");
}
OutputFormat prevOutputFormat = getOutputFormat();
this.outputFormat = outputFormat;
outputFormatExplicitlySet = true;
if (prevOutputFormat != outputFormat) {
clearTemplateCache();
}
}
/**
* Getter pair of {@link #setOutputFormat(OutputFormat)}
*
* @since 2.3.24
*/
@Override
public OutputFormat getOutputFormat() {
return outputFormat;
}
/**
* Tells if {@link #setOutputFormat(OutputFormat)} (or equivalent) was already called on this instance.
*
* @since 2.3.24
*/
public boolean isOutputFormatExplicitlySet() {
return outputFormatExplicitlySet;
}
/**
* 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 #isOutputFormatExplicitlySet()} will return {@code false}.
*
* @since 2.3.24
*/
public void unsetOutputFormat() {
outputFormat = UndefinedOutputFormat.INSTANCE;
outputFormatExplicitlySet = false;
}
/**
* Returns the output format for a name.
*
* @param name
* Either the name of the output format as it was registered with
* {@link Configuration#setRegisteredCustomOutputFormats(Collection)}, 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 HTML{RTF}
, where "HTML" and "RTF" refer to the
* existing formats. This logic can be used recursively, so for example XML{HTML{RTF}}
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 {
and }
in the name is syntactically wrong, or if not all
* {@link OutputFormat}-s are {@link MarkupOutputFormat}-s in the ...{...}
expression.
*
* @since 2.3.24
*/
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 = registeredCustomOutputFormats.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 registeredNames = new TreeSet<>();
registeredNames.addAll(STANDARD_OUTPUT_FORMATS.keySet());
registeredNames.addAll(registeredCustomOutputFormats.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;
}
}
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.");
}
MarkupOutputFormat outerOF = (MarkupOutputFormat) of;
return outerOF;
}
/**
* Sets 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 #setRecognizeStandardFileExtensions(boolean)}.
*
*
* 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}.
*
*
* The default value is an empty collection.
*
* @param registeredCustomOutputFormats
* The collection of the {@link OutputFormat}-s, each must be different and has a unique name (
* {@link OutputFormat#getName()}) within this 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 '{'
or '}'
.
* If an {@link OutputFormat} name equals to {@link UndefinedOutputFormat#getName()} or
* {@link PlainTextOutputFormat#getName()}.
*
* @since 2.3.24
*/
public void setRegisteredCustomOutputFormats(Collection extends OutputFormat> registeredCustomOutputFormats) {
NullArgumentException.check(registeredCustomOutputFormats);
Map m = new LinkedHashMap<>(
registeredCustomOutputFormats.size() * 4 / 3, 1f);
for (OutputFormat outputFormat : registeredCustomOutputFormats) {
String name = outputFormat.getName();
if (name.equals(UndefinedOutputFormat.INSTANCE.getName())) {
throw new IllegalArgumentException(
"The \"" + name + "\" output format can't be redefined");
}
if (name.equals(PlainTextOutputFormat.INSTANCE.getName())) {
throw new IllegalArgumentException(
"The \"" + name + "\" output format can't be redefined");
}
if (name.length() == 0) {
throw new IllegalArgumentException("The output format name can't be 0 long");
}
if (!Character.isLetterOrDigit(name.charAt(0))) {
throw new IllegalArgumentException("The output format name must start with letter or digit: "
+ name);
}
if (name.indexOf('+') != -1) {
throw new IllegalArgumentException("The output format name can't contain \"+\" character: "
+ name);
}
if (name.indexOf('{') != -1) {
throw new IllegalArgumentException("The output format name can't contain \"{\" character: "
+ name);
}
if (name.indexOf('}') != -1) {
throw new IllegalArgumentException("The output format name can't contain \"}\" character: "
+ name);
}
OutputFormat replaced = m.put(outputFormat.getName(), outputFormat);
if (replaced != null) {
if (replaced == outputFormat) {
throw new IllegalArgumentException(
"Duplicate output format in the collection: " + outputFormat);
}
throw new IllegalArgumentException(
"Clashing output format names between " + replaced + " and " + outputFormat + ".");
}
}
this.registeredCustomOutputFormats = Collections.unmodifiableMap(m);
clearTemplateCache();
}
/**
* Getter pair of {@link #setRegisteredCustomOutputFormats(Collection)}.
*
* @since 2.3.24
*/
public Collection extends OutputFormat> getRegisteredCustomOutputFormats() {
return registeredCustomOutputFormats.values();
}
/**
* Sets if the "file" extension part of the source name ({@link Template#getSourceName()}) will influence certain
* parsing settings. For backward compatibility, it defaults to {@code false} if
* {@link #getIncompatibleImprovements()} is less than 2.3.24. Starting from {@code incompatibleImprovements}
* 2.3.24, it defaults to {@code true}, so the following standard file extensions take their effect:
*
*
* - {@code ftlh}: Sets {@link TemplateConfiguration#setOutputFormat(OutputFormat) outputFormat} to
* {@code "HTML"} (i.e., {@link HTMLOutputFormat#INSTANCE}, unless the {@code "HTML"} name is overridden by
* {@link #setRegisteredCustomOutputFormats(Collection)}) and
* {@link TemplateConfiguration#setAutoEscapingPolicy(int) autoEscapingPolicy} to
* {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
*
- {@code ftlx}: Sets {@link TemplateConfiguration#setOutputFormat(OutputFormat) outputFormat} to
* {@code "XML"} (i.e., {@link XMLOutputFormat#INSTANCE}, unless the {@code "XML"} name is overridden by
* {@link #setRegisteredCustomOutputFormats(Collection)}) and
* {@link TemplateConfiguration#setAutoEscapingPolicy(int) autoEscapingPolicy} to
* {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
*
*
* These file extensions are not case sensitive. The file extension is the part after the last dot in the source
* name. If the source name contains no dot, then it has no file extension.
*
*
The settings activated by these file extensions override the setting values dictated by
* {@link #setTemplateConfigurations(TemplateConfigurationFactory)}.
*/
public void setRecognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
boolean prevEffectiveValue = getRecognizeStandardFileExtensions();
this.recognizeStandardFileExtensions = Boolean.valueOf(recognizeStandardFileExtensions);
if (prevEffectiveValue != recognizeStandardFileExtensions) {
clearTemplateCache();
}
}
/**
* 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 #isRecognizeStandardFileExtensionsExplicitlySet()} will return {@code false}.
*
* @since 2.3.24
*/
public void unsetRecognizeStandardFileExtensions() {
if (recognizeStandardFileExtensions != null) {
recognizeStandardFileExtensions = null;
}
}
/**
* Tells if {@link #setRecognizeStandardFileExtensions(boolean)} (or equivalent) was already called on this
* instance.
*
* @since 2.3.24
*/
public boolean isRecognizeStandardFileExtensionsExplicitlySet() {
return recognizeStandardFileExtensions != null;
}
/**
* Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
*
* @since 2.3.24
*/
@Override
public boolean getRecognizeStandardFileExtensions() {
return recognizeStandardFileExtensions == null
? incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_24
: recognizeStandardFileExtensions.booleanValue();
}
/**
* Determines the tag syntax (like {@code <#if x>} VS {@code [#if x]}) of the template files
* that has no {@code #ftl} header to decide that. Don't confuse this with the interpolation syntax
* ({@link #setInterpolationSyntax(int)}); they are independent.
*
*
The {@code tagSyntax} parameter must be one of:
*
* - {@link Configuration#AUTO_DETECT_TAG_SYNTAX}:
* Use the syntax of the first FreeMarker tag (can be anything, like #list,
* #include, user defined, etc.)
*
- {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}:
* Use the angle bracket tag syntax (the normal syntax), like {@code <#include ...>}
*
- {@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}:
* Use the square bracket tag syntax, like {@code [#include ...]}. Note that this does not change
*
${x}
to {@code [=...]}; that's interpolation syntax, so use
* {@link #setInterpolationSyntax(int)} for that.
*
*
* In FreeMarker 2.3.x {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX} is the
* default for better backward compatibility. Starting from 2.4.x {@link
* Configuration#AUTO_DETECT_TAG_SYNTAX} is the default, so it's recommended to use
* that even for 2.3.x.
*
*
This setting is ignored for the templates that have {@code ftl} directive in
* it. For those templates the syntax used for the {@code ftl} directive determines
* the syntax.
*
* @see #setInterpolationSyntax(int)
*/
public void setTagSyntax(int tagSyntax) {
_TemplateAPI.valideTagSyntaxValue(tagSyntax);
this.tagSyntax = tagSyntax;
}
/**
* The getter pair of {@link #setTagSyntax(int)}.
*/
@Override
public int getTagSyntax() {
return tagSyntax;
}
/**
* Determines the interpolation syntax (like ${x}
VS [=x]
) of the template files. Don't
* confuse this with the tag syntax ({@link #setTagSyntax(int)}); they are independent.
*
*
* The {@code interpolationSyntax} parameter must be one of {@link Configuration#LEGACY_INTERPOLATION_SYNTAX},
* {@link Configuration#DOLLAR_INTERPOLATION_SYNTAX}, and {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX}.
* Note that {@link Configuration#SQUARE_BRACKET_INTERPOLATION_SYNTAX} does not change {@code <#if x>} to
* {@code [#if x]}; that's tag syntax, so use {@link #setTagSyntax(int)} for that.
*
* @see #setTagSyntax(int)
*
* @since 2.3.28
*/
public void setInterpolationSyntax(int interpolationSyntax) {
_TemplateAPI.valideInterpolationSyntaxValue(interpolationSyntax);
this.interpolationSyntax = interpolationSyntax;
}
/**
* The getter pair of {@link #setInterpolationSyntax(int)}.
*
* @since 2.3.28
*/
@Override
public int getInterpolationSyntax() {
return interpolationSyntax;
}
/**
* Sets the naming convention used for the identifiers that are part of the template language. The available naming
* conventions are legacy (directive (tag) names are all-lower-case {@code likethis}, others are snake case
* {@code like_this}), and camel case ({@code likeThis}). The default is auto-detect, which detects the naming
* convention used and enforces that same naming convention for the whole template.
*
*
* This setting doesn't influence what naming convention is used for the setting names outside templates. Also, it
* won't ever convert the names of user-defined things, like of data-model members, or the names of user defined
* macros/functions. It only influences the names of the built-in directives ({@code #elseIf} VS {@code elseif}),
* built-ins ({@code ?upper_case} VS {@code ?upperCase} ), special variables ({@code .data_model} VS
* {@code .dataModel}).
*
*
* Which convention to use: FreeMarker prior to 2.3.23 has only supported
* {@link Configuration#LEGACY_NAMING_CONVENTION}, so that's how most templates and examples out there are written
* as of 2015. But as templates today are mostly written by programmers and often access Java API-s which already
* use camel case, {@link Configuration#CAMEL_CASE_NAMING_CONVENTION} is the recommended option for most projects.
* However, it's no necessary to make a application-wide decision; see auto-detection below.
*
*
* FreeMarker will decide the naming convention automatically for each template individually when this setting is
* set to {@link #AUTO_DETECT_NAMING_CONVENTION} (which is the default). The naming convention of a template is
* decided when the first core (non-user-defined) identifier is met during parsing (not during processing) where the
* naming convention is relevant (like for {@code s?upperCase} or {@code s?upper_case} it's relevant, but for
* {@code s?length} it isn't). At that point, the naming convention of the template is decided, and any later core
* identifier that uses a different convention will be a parsing error. As the naming convention is decided per
* template, it's not a problem if a template and the other template it {@code #include}-s/{@code #import} uses a
* different convention.
*
*
* FreeMarker always enforces the same naming convention to be used consistently within the same template "file".
* Additionally, when this setting is set to non-{@link #AUTO_DETECT_NAMING_CONVENTION}, the selected naming
* convention is enforced on all templates. Thus such a setup can be used to enforce an application-wide naming
* convention.
*
*
* Non-strict tags (a long deprecated syntax from FreeMarker 1, activated via {@link #setStrictSyntaxMode(boolean)})
* are only recognized as FTL tags when they are using the {@link Configuration#LEGACY_NAMING_CONVENTION} syntax,
* regardless of this setting. As they aren't exempt from the naming convention consistency enforcement, generally,
* you can't use strict {@link Configuration#CAMEL_CASE_NAMING_CONVENTION} tags mixed with non-strict tags.
*
* @param namingConvention
* One of the {@link #AUTO_DETECT_NAMING_CONVENTION} or {@link #LEGACY_NAMING_CONVENTION}
* {@link #CAMEL_CASE_NAMING_CONVENTION}.
*
* @throws IllegalArgumentException
* If the parameter isn't one of the valid constants.
*
* @since 2.3.23
*/
public void setNamingConvention(int namingConvention) {
_TemplateAPI.validateNamingConventionValue(namingConvention);
this.namingConvention = namingConvention;
}
/**
* The getter pair of {@link #setNamingConvention(int)}.
*
* @since 2.3.23
*/
@Override
public int getNamingConvention() {
return namingConvention;
}
/**
* Sets the assumed display width of the tab character (ASCII 9), which influences the column number shown in error
* messages (or the column number you get through other API-s). So for example if the users edit templates in an
* editor where the tab width is set to 4, you should set this to 4 so that the column numbers printed by FreeMarker
* will match the column number shown in the editor. This setting doesn't affect the output of templates, as a tab
* in the template will remain a tab in the output too. If you set this setting to 1, then tab characters will be
* kept in the return value of {@link Template#getSource(int, int, int, int)}, otherwise they will be replaced with
* the appropriate number of spaces.
*
* @param tabSize
* At least 1, at most 256.
*
* @since 2.3.25
*/
public void setTabSize(int tabSize) {
if (tabSize < 1) {
throw new IllegalArgumentException("\"tabSize\" must be at least 1, but was " + tabSize);
}
// To avoid integer overflows:
if (tabSize > 256) {
throw new IllegalArgumentException("\"tabSize\" can't be more than 256, but was " + tabSize);
}
this.tabSize = tabSize;
}
/**
* The getter pair of {@link #setTabSize(int)}.
*
* @since 2.3.25
*/
@Override
public int getTabSize() {
return tabSize;
}
/**
* The getter pair of {@link #setFallbackOnNullLoopVariable(boolean)}.
*
* @since 2.3.29
*/
public boolean getFallbackOnNullLoopVariable() {
return fallbackOnNullLoopVariable;
}
/**
* Specifies the behavior when reading a loop variable (like {@code i} in {@code <#list items as i>}, or in
* {@code <@myMacro items; i>}) that's {@code null} (missing); if {@code true}, FreeMarker will look for a variable
* with the same name in higher variable scopes, or if {@code false} the variable will be simply {@code null}
* (missing). For backward compatibility the default is {@code true}. The recommended value for new projects is
* {@code false}, as otherwise adding new variables to higher scopes (typically to the data-model) can
* unintentionally change the behavior of templates. You have to be quite unlucky for that to happen though:
* The newly added variable has to have the same name as the loop variable, and there must be some null (missing)
* values in what you loop through.
*
*
This setting doesn't influence the behavior of lambdas, like {@code items?filter(i -> i?hasContent)}, as they
* never had this problem. Reading a lambda argument never falls back to higher scopes.
*
* @since 2.3.29
*/
public void setFallbackOnNullLoopVariable(boolean fallbackOnNullLoopVariable) {
this.fallbackOnNullLoopVariable = fallbackOnNullLoopVariable;
}
/**
* Getter pair of {@link #setPreventStrippings(boolean)}.
*
* @since 2.3.27
*/
boolean getPreventStrippings() {
return preventStrippings;
}
/**
* Used internally; added for the FreeMarker 2 to FreeMarker 3 converter, prevents the stripping/removal of AST
* nodes so that the source code can be fully reproduced from the AST.
*
* @since 2.3.27
*/
void setPreventStrippings(boolean preventStrippings) {
this.preventStrippings = preventStrippings;
}
/**
* Retrieves the template with the given name from the template cache, loading it into the cache first if it's
* missing/staled.
*
*
* This is a shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean)
* getTemplate(name, null, null, null, true, false)}; see more details there.
*
*
* See {@link Configuration} for an example of basic usage.
*/
public Template getTemplate(String name)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
return getTemplate(name, null, null, null, true, false);
}
/**
* Shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean)
* getTemplate(name, locale, null, null, true, false)}.
*/
public Template getTemplate(String name, Locale locale)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
return getTemplate(name, locale, null, null, true, false);
}
/**
* Shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean)
* getTemplate(name, null, null, encoding, true, false)}.
*/
public Template getTemplate(String name, String encoding)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
return getTemplate(name, null, null, encoding, true, false);
}
/**
* Shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean)
* getTemplate(name, locale, null, encoding, true, false)}.
*/
public Template getTemplate(String name, Locale locale, String encoding)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
return getTemplate(name, locale, null, encoding, true, false);
}
/**
* Shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean)
* getTemplate(name, locale, null, encoding, parseAsFTL, false)}.
*/
public Template getTemplate(String name, Locale locale, String encoding, boolean parseAsFTL)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
return getTemplate(name, locale, null, encoding, parseAsFTL, false);
}
/**
* Shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean)
* getTemplate(name, locale, null, encoding, parseAsFTL, ignoreMissing)}.
*
* @since 2.3.21
*/
public Template getTemplate(String name, Locale locale, String encoding, boolean parseAsFTL, boolean ignoreMissing)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
return getTemplate(name, locale, null, encoding, parseAsFTL, ignoreMissing);
}
/**
* Retrieves the template with the given name (and according the specified further parameters) from the template
* cache, loading it into the cache first if it's missing/staled.
*
*
* This method is thread-safe.
*
*
* See {@link Configuration} for an example of basic usage.
*
* @param name
* The name or path of the template, which is not a real path, but interpreted inside the current
* {@link TemplateLoader}. Can't be {@code null}. The exact syntax of the name depends on the underlying
* {@link TemplateLoader}, but the cache makes some assumptions. First, the name is expected to be a
* hierarchical path, with path components separated by a slash character (not with backslash!). The path
* (the name) given here must not begin with slash; it's always interpreted relative to the
* "template root directory". Then, the {@code ..} and {@code .} path meta-elements will be resolved. For
* example, if the name is {@code a/../b/./c.ftl}, then it will be simplified to {@code b/c.ftl}. The
* rules regarding this are the same as with conventional UN*X paths. The path must not reach outside the
* template root directory, that is, it can't be something like {@code "../templates/my.ftl"} (not even
* if this path happens to be equivalent with {@code "/my.ftl"}). Furthermore, the path is allowed to
* contain at most one path element whose name is {@code *} (asterisk). This path meta-element triggers
* the acquisition mechanism. If the template is not found in the location described by the
* concatenation of the path left to the asterisk (called base path) and the part to the right of the
* asterisk (called resource path), the cache will attempt to remove the rightmost path component from
* the base path ("go up one directory") and concatenate that with the resource path. The process is
* repeated until either a template is found, or the base path is completely exhausted.
*
* @param locale
* The requested locale of the template. This is what {@link Template#getLocale()} on the resulting
* {@link Template} will return (unless it's overridden via {@link #getTemplateConfigurations()}). This
* parameter can be {@code null} since 2.3.22, in which case it defaults to
* {@link Configuration#getLocale()} (note that {@link Template#getLocale()} will give the default value,
* not {@code null}). This parameter also drives localized template lookup. Assuming that you have
* specified {@code en_US} as the locale and {@code myTemplate.ftl} as the name of the template, and the
* default {@link TemplateLookupStrategy} is used and
* {@code #setLocalizedLookup(boolean) localized_lookup} is {@code true}, FreeMarker will first try to
* retrieve {@code myTemplate_en_US.html}, then {@code myTemplate.en.ftl}, and finally
* {@code myTemplate.ftl}. Note that that the template's locale will be {@code en_US} even if it only
* finds {@code myTemplate.ftl}. Note that when the {@code locale} setting is overridden with a
* {@link TemplateConfiguration} provided by {@link #getTemplateConfigurations()}, that overrides the
* value specified here, but only after the localized lookup, that is, it modifies the template
* found by the localized lookup.
*
* @param customLookupCondition
* This value can be used by a custom {@link TemplateLookupStrategy}; has no effect with the default one.
* Can be {@code null} (though it's up to the custom {@link TemplateLookupStrategy} if it allows that).
* This object will be used as part of the cache key, so it must to have a proper
* {@link Object#equals(Object)} and {@link Object#hashCode()} method. It also should have reasonable
* {@link Object#toString()}, as it's possibly quoted in error messages. The expected type is up to the
* custom {@link TemplateLookupStrategy}. See also:
* {@link TemplateLookupContext#getCustomLookupCondition()}.
*
* @param encoding
* Deprecated mechanism, {@code null} is the recommended; the charset used to interpret the template
* source code bytes (if it's read from a binary source). Can be {@code null} since 2.3.22, in which case
* it will default to {@link Configuration#getEncoding(Locale)} where {@code Locale} is the
* {@code locale} parameter (when {@code locale} was {@code null} too, the its default value is used
* instead). Why is this deprecated: It doesn't make sense to get the same template with
* different encodings, hence, it's error prone to specify the encoding where you get the template.
* Instead, if you have template "files" with different charsets, you should use
* {@link #setTemplateConfigurations(TemplateConfigurationFactory)}, where you can associate encodings to
* individual templates based on their names (like which "directory" are they in, what's their file
* extension, etc.). The encoding associated with the templates that way overrides the encoding that you
* specify here.
*
* @param parseAsFTL
* If {@code true}, the loaded template is parsed and interpreted normally, as a regular FreeMarker
* template. If {@code false}, the loaded template is treated as a static text, so ${...}
,
* {@code <#...>} etc. will not have special meaning in it.
*
* @param ignoreMissing
* If {@code true}, the method won't throw {@link TemplateNotFoundException} if the template doesn't
* exist, instead it returns {@code null}. Other kind of exceptions won't be suppressed.
*
* @return the requested template; maybe {@code null} when the {@code ignoreMissing} parameter is {@code true}.
*
* @throws TemplateNotFoundException
* If the template could not be found. Note that this exception extends {@link IOException}.
* @throws MalformedTemplateNameException
* If the template name given was in violation with the {@link TemplateNameFormat} in use. Note that
* this exception extends {@link IOException}.
* @throws ParseException
* (extends IOException
) if the template is syntactically bad. Note that this exception
* extends {@link IOException}.
* @throws IOException
* If there was some other problem with reading the template "file". Note that the other exceptions
* extend {@link IOException}, so this should be catched the last.
*
* @since 2.3.22
*/
public Template getTemplate(String name, Locale locale, Object customLookupCondition,
String encoding, boolean parseAsFTL, boolean ignoreMissing)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException {
if (locale == null) {
locale = getLocale();
}
if (encoding == null) {
encoding = getEncoding(locale);
}
final MaybeMissingTemplate maybeTemp = cache.getTemplate(name, locale, customLookupCondition, encoding, parseAsFTL);
final Template temp = maybeTemp.getTemplate();
if (temp == null) {
if (ignoreMissing) {
return null;
}
TemplateLoader tl = getTemplateLoader();
String msg;
if (tl == null) {
msg = "Don't know where to load template " + StringUtil.jQuote(name)
+ " from because the \"template_loader\" FreeMarker "
+ "setting wasn't set (Configuration.setTemplateLoader), so it's null.";
} else {
final String missingTempNormName = maybeTemp.getMissingTemplateNormalizedName();
final String missingTempReason = maybeTemp.getMissingTemplateReason();
final TemplateLookupStrategy templateLookupStrategy = getTemplateLookupStrategy();
msg = "Template not found for name " + StringUtil.jQuote(name)
+ (missingTempNormName != null && name != null
&& !removeInitialSlash(name).equals(missingTempNormName)
? " (normalized: " + StringUtil.jQuote(missingTempNormName) + ")"
: "")
+ (customLookupCondition != null ? " and custom lookup condition "
+ StringUtil.jQuote(customLookupCondition) : "")
+ "."
+ (missingTempReason != null
? "\nReason given: " + ensureSentenceIsClosed(missingTempReason)
: "")
+ "\nThe name was interpreted by this TemplateLoader: "
+ StringUtil.tryToString(tl) + "."
+ (!isKnownNonConfusingLookupStrategy(templateLookupStrategy)
? "\n(Before that, the name was possibly changed by this lookup strategy: "
+ StringUtil.tryToString(templateLookupStrategy) + ".)"
: "")
// Suspected reasons or warning:
+ (!templateLoaderExplicitlySet
? "\nWarning: The \"template_loader\" FreeMarker setting "
+ "wasn't set (Configuration.setTemplateLoader), and using the default value "
+ "is most certainly not intended and dangerous, and can be the cause of this error."
: "")
+ (missingTempReason == null && name.indexOf('\\') != -1
? "\nWarning: The name contains backslash (\"\\\") instead of slash (\"/\"); "
+ "template names should use slash only."
: "");
}
String normName = maybeTemp.getMissingTemplateNormalizedName();
throw new TemplateNotFoundException(
normName != null ? normName : name,
customLookupCondition,
msg);
}
return temp;
}
private boolean isKnownNonConfusingLookupStrategy(TemplateLookupStrategy templateLookupStrategy) {
return templateLookupStrategy == TemplateLookupStrategy.DEFAULT_2_3_0;
}
private String removeInitialSlash(String name) {
return name.startsWith("/") ? name.substring(1) : name;
}
private String ensureSentenceIsClosed(String s) {
if (s == null || s.length() == 0) {
return s;
}
final char lastChar = s.charAt(s.length() - 1);
return lastChar == '.' || lastChar == '!' || lastChar == '?' ? s : s + ".";
}
/**
* Sets the charset used for decoding byte sequences to character sequences when
* reading template files in a locale for which no explicit encoding
* was specified via {@link #setEncoding(Locale, String)}. Note that by default there is no locale specified for
* any locale, so the default encoding is always in effect.
*
*
Defaults to the default system encoding, which can change from one server to
* another, so you should always set this setting. If you don't know what charset your should chose,
* {@code "UTF-8"} is usually a good choice.
*
*
Note that individual templates may specify their own charset by starting with
* <#ftl encoding="...">
*
* @param encoding The name of the charset, such as {@code "UTF-8"} or {@code "ISO-8859-1"}
*/
public void setDefaultEncoding(String encoding) {
defaultEncoding = encoding;
defaultEncodingExplicitlySet = true;
}
/**
* Gets the default encoding for converting bytes to characters when
* reading template files in a locale for which no explicit encoding
* was specified. Defaults to the default system encoding.
*/
public String getDefaultEncoding() {
return defaultEncoding;
}
/**
* Resets the setting to its default, as if it was never set.
*
* @since 2.3.26
*/
public void unsetDefaultEncoding() {
if (defaultEncodingExplicitlySet) {
setDefaultEncoding(getDefaultDefaultEncoding());
defaultEncodingExplicitlySet = false;
}
}
/**
* Tells if {@link #setDefaultEncoding(String)} (or equivalent) was already called on this instance, or it just holds the
* default value.
*
* @since 2.3.26
*/
public boolean isDefaultEncodingExplicitlySet() {
return defaultEncodingExplicitlySet;
}
static private String getDefaultDefaultEncoding() {
return getJVMDefaultEncoding();
}
static private String getJVMDefaultEncoding() {
return SecurityUtilities.getSystemProperty("file.encoding", "utf-8");
}
/**
* Gets the preferred character encoding for the given locale, or the
* default encoding if no encoding is set explicitly for the specified
* locale. You can associate encodings with locales using
* {@link #setEncoding(Locale, String)} or {@link #loadBuiltInEncodingMap()}.
*
* @param locale Shouldn't be {@code null}, though for backward compatibility it's accepted when the locale to
* encoding {@link Map} (see earlier) is empty.
*/
public String getEncoding(Locale locale) {
if (localeToCharsetMap.isEmpty()) {
return defaultEncoding;
} else {
// Try for a full name match (may include country and variant)
NullArgumentException.check("locale", locale);
String charset = (String) localeToCharsetMap.get(locale.toString());
if (charset == null) {
if (locale.getVariant().length() > 0) {
Locale l = new Locale(locale.getLanguage(), locale.getCountry());
charset = (String) localeToCharsetMap.get(l.toString());
if (charset != null) {
localeToCharsetMap.put(locale.toString(), charset);
}
}
charset = (String) localeToCharsetMap.get(locale.getLanguage());
if (charset != null) {
localeToCharsetMap.put(locale.toString(), charset);
}
}
return charset != null ? charset : defaultEncoding;
}
}
/**
* Sets the character set encoding to use for templates of
* a given locale. If there is no explicit encoding set for some
* locale, then the default encoding will be used, what you can
* set with {@link #setDefaultEncoding}.
*
* @see #clearEncodingMap
* @see #loadBuiltInEncodingMap
*/
public void setEncoding(Locale locale, String encoding) {
localeToCharsetMap.put(locale.toString(), encoding);
}
/**
* Adds a shared variable to the configuration.
* Shared sharedVariables are sharedVariables that are visible
* as top-level sharedVariables for all templates which use this
* configuration, if the data model does not contain a
* variable with the same name.
*
*
Never use TemplateModel implementation that is not thread-safe for shared sharedVariables,
* if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
*
*
This method is not thread safe; use it with the same restrictions as those that modify setting values.
*
* @param name the name used to access the data object from your template.
* If a shared variable with this name already exists, it will replace
* that.
*
* @see #setAllSharedVariables
* @see #setSharedVariable(String,Object)
*/
public void setSharedVariable(String name, TemplateModel tm) {
Object replaced = sharedVariables.put(name, tm);
if (replaced != null && rewrappableSharedVariables != null) {
rewrappableSharedVariables.remove(name);
}
}
/**
* Returns the set containing the names of all defined shared sharedVariables.
* The method returns a new Set object on each call that is completely
* disconnected from the Configuration. That is, modifying the set will have
* no effect on the Configuration object.
*/
public Set getSharedVariableNames() {
return new HashSet(sharedVariables.keySet());
}
/**
* Adds shared variable to the configuration; It uses {@link Configurable#getObjectWrapper()} to wrap the
* {@code value}, so it's important that the object wrapper is set before this.
*
*
This method is not thread safe; use it with the same restrictions as those that modify setting values.
*
*
The added value should be thread safe, if you are running templates from multiple threads with this
* configuration.
*
* @throws TemplateModelException If some of the variables couldn't be wrapped via {@link #getObjectWrapper()}.
*
* @see #setSharedVaribles(Map)
* @see #setSharedVariable(String,TemplateModel)
* @see #setAllSharedVariables(TemplateHashModelEx)
*/
public void setSharedVariable(String name, Object value) throws TemplateModelException {
setSharedVariable(name, getObjectWrapper().wrap(value));
}
/**
* Replaces all shared variables (removes all previously added ones).
*
*
The values in the map can be {@link TemplateModel}-s or plain Java objects which will be immediately converted
* to {@link TemplateModel} with the {@link ObjectWrapper} returned by {@link #getObjectWrapper()}. If
* {@link #setObjectWrapper(ObjectWrapper)} is called later, this conversion will be re-applied. Thus, ignoring some
* extra resource usage, it doesn't mater if in what order are {@link #setObjectWrapper(ObjectWrapper)} and
* {@link #setSharedVaribles(Map)} called. This is essential when you don't have control over the order in which
* the setters are called.
*
*
The values in the map must be thread safe, if you are running templates from multiple threads with
* this configuration. This means that both the plain Java object and the {@link TemplateModel}-s created from them
* by the {@link ObjectWrapper} must be thread safe. (The standard {@link ObjectWrapper}-s of FreeMarker create
* thread safe {@link TemplateModel}-s.) The {@link Map} itself need not be thread-safe.
*
*
This setter method has no getter pair because of the tricky relation ship with
* {@link #setSharedVariable(String, Object)}.
*
* @throws TemplateModelException If some of the variables couldn't be wrapped via {@link #getObjectWrapper()}.
*
* @since 2.3.29
*/
public void setSharedVariables(Map map) throws TemplateModelException {
rewrappableSharedVariables = new HashMap(map);
sharedVariables.clear();
setSharedVariablesFromRewrappableSharedVariables();
}
/**
* Same as {@link #setSharedVariables(Map)}, but with typo in the name.
* @since 2.3.21
* @deprecated Use {@link #setSharedVariables(Map)} instead.
*/
public void setSharedVaribles(Map/**/ map) throws TemplateModelException {
setSharedVariables(map);
}
private void setSharedVariablesFromRewrappableSharedVariables() throws TemplateModelException {
if (rewrappableSharedVariables == null) return;
for (Iterator it = rewrappableSharedVariables.entrySet().iterator(); it.hasNext(); ) {
Map.Entry/**/ ent = (Entry) it.next();
String name = (String) ent.getKey();
Object value = ent.getValue();
TemplateModel valueAsTM;
if (value instanceof TemplateModel) {
valueAsTM = (TemplateModel) value;
} else {
valueAsTM = getObjectWrapper().wrap(value);
}
sharedVariables.put(name, valueAsTM);
}
}
/**
* Adds all object in the hash as shared variable to the configuration; it's like doing several
* {@link #setSharedVariable(String, Object)} calls, one for each hash entry. It doesn't remove the already added
* shared variable before doing this.
*
* Never use TemplateModel implementation that is not thread-safe for shared shared variable values,
* if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
*
*
This method is not thread safe; use it with the same restrictions as those that modify setting values.
*
* @param hash a hash model whose objects will be copied to the
* configuration with same names as they are given in the hash.
* If a shared variable with these names already exist, it will be replaced
* with those from the map.
*
* @see #setSharedVaribles(Map)
* @see #setSharedVariable(String,Object)
* @see #setSharedVariable(String,TemplateModel)
*/
public void setAllSharedVariables(TemplateHashModelEx hash) throws TemplateModelException {
TemplateModelIterator keys = hash.keys().iterator();
TemplateModelIterator values = hash.values().iterator();
while (keys.hasNext()) {
setSharedVariable(((TemplateScalarModel) keys.next()).getAsString(), values.next());
}
}
/**
* Gets a shared variable. Shared shared variables are variables that are
* available to all templates. When a template is processed, and an identifier
* is undefined in the data model, a shared variable object with the same identifier
* is then looked up in the configuration. There are several predefined shared variables
* that are always available through this method; see the FreeMarker manual
* for a comprehensive list of them.
*
* @see #setSharedVariable(String,Object)
* @see #setSharedVariable(String,TemplateModel)
* @see #setAllSharedVariables
*/
public TemplateModel getSharedVariable(String name) {
return (TemplateModel) sharedVariables.get(name);
}
/**
* Removes all shared variables, except the predefined ones (compress, html_escape, etc.).
*/
public void clearSharedVariables() {
sharedVariables.clear();
loadBuiltInSharedVariables();
}
/**
* Removes all entries from the template cache, thus forcing reloading of templates
* on subsequent getTemplate
calls.
*
*
This method is thread-safe and can be called while the engine processes templates.
*/
public void clearTemplateCache() {
cache.clear();
}
/**
* Equivalent to {@link
* #removeTemplateFromCache(String, Locale, Object, String, boolean)
* removeTemplateFromCache(name, thisCfg.getLocale(), null, thisCfg.getEncoding(thisCfg.getLocale()), true)}.
* @since 2.3.19
*/
public void removeTemplateFromCache(String name) throws IOException {
Locale loc = getLocale();
removeTemplateFromCache(name, loc, null, getEncoding(loc), true);
}
/**
* Equivalent to {@link
* #removeTemplateFromCache(String, Locale, Object, String, boolean)
* removeTemplateFromCache(name, locale, null, thisCfg.getEncoding(locale), true)}.
* @since 2.3.19
*/
public void removeTemplateFromCache(String name, Locale locale) throws IOException {
removeTemplateFromCache(name, locale, null, getEncoding(locale), true);
}
/**
* Equivalent to {@link
* #removeTemplateFromCache(String, Locale, Object, String, boolean)
* removeTemplateFromCache(name, thisCfg.getLocale(), null, encoding, true)}.
* @since 2.3.19
*/
public void removeTemplateFromCache(String name, String encoding) throws IOException {
removeTemplateFromCache(name, getLocale(), null, encoding, true);
}
/**
* Equivalent to {@link
* #removeTemplateFromCache(String, Locale, Object, String, boolean)
* removeTemplateFromCache(name, locale, null, encoding, true)}.
* @since 2.3.19
*/
public void removeTemplateFromCache(String name, Locale locale, String encoding) throws IOException {
removeTemplateFromCache(name, locale, null, encoding, true);
}
/**
* Equivalent to {@link
* #removeTemplateFromCache(String, Locale, Object, String, boolean)
* removeTemplateFromCache(name, locale, null, encoding, parse)}.
* @since 2.3.19
*/
public void removeTemplateFromCache(
String name, Locale locale, String encoding, boolean parse)
throws IOException {
removeTemplateFromCache(name, locale, null, encoding, parse);
}
/**
* Removes a template from the template cache, hence forcing the re-loading
* of it when it's next time requested. This is to give the application
* finer control over cache updating than {@link #setTemplateUpdateDelay(int)}
* alone does.
*
*
For the meaning of the parameters, see
* {@link #getTemplate(String, Locale, Object, String, boolean, boolean)}.
*
*
This method is thread-safe and can be called while the engine processes templates.
*
* @since 2.3.28
*/
public void removeTemplateFromCache(
String name, Locale locale, Object customLookupCondition, String encoding, boolean parse)
throws IOException {
cache.removeTemplate(name, locale, customLookupCondition, encoding, parse);
}
/**
* The getter pair of {@link #setLocalizedLookup(boolean)}.
*
*
This method is thread-safe and can be called while the engine works.
*/
public boolean getLocalizedLookup() {
return cache.getLocalizedLookup();
}
/**
* Enables/disables localized template lookup. Enabled by default.
*
*
* With the default {@link TemplateLookupStrategy}, localized lookup works like this: Let's say your locale setting
* is {@code Locale("en", "AU")}, and you call {@link Configuration#getTemplate(String) cfg.getTemplate("foo.ftl")}.
* Then FreeMarker will look for the template under these names, stopping at the first that exists:
* {@code "foo_en_AU.ftl"}, {@code "foo_en.ftl"}, {@code "foo.ftl"}. See the description of the default value at
* {@link #setTemplateLookupStrategy(TemplateLookupStrategy)} for a more details. If you need to generate different
* template names, use {@link #setTemplateLookupStrategy(TemplateLookupStrategy)} with your custom
* {@link TemplateLookupStrategy}.
*
*
Note that changing the value of this setting causes the template cache to be emptied so that old lookup
* results won't be reused (since 2.3.22).
*
*
* Historical note: Despite what the API documentation said earlier, this method is not thread-safe. While
* setting it can't cause any serious problems, and in fact it works well on most hardware, it's not guaranteed that
* FreeMarker will see the update in all threads.
*/
public void setLocalizedLookup(boolean localizedLookup) {
this.localizedLookup = localizedLookup;
cache.setLocalizedLookup(localizedLookup);
}
@Override
public void setSetting(String name, String value) throws TemplateException {
boolean unknown = false;
try {
if ("TemplateUpdateInterval".equalsIgnoreCase(name)) {
name = TEMPLATE_UPDATE_DELAY_KEY;
} else if ("DefaultEncoding".equalsIgnoreCase(name)) {
name = DEFAULT_ENCODING_KEY;
}
if (DEFAULT_ENCODING_KEY_SNAKE_CASE.equals(name) || DEFAULT_ENCODING_KEY_CAMEL_CASE.equals(name)) {
if (JVM_DEFAULT.equalsIgnoreCase(value)) {
setDefaultEncoding(getJVMDefaultEncoding());
} else {
setDefaultEncoding(value);
}
} else if (LOCALIZED_LOOKUP_KEY_SNAKE_CASE.equals(name) || LOCALIZED_LOOKUP_KEY_CAMEL_CASE.equals(name)) {
setLocalizedLookup(StringUtil.getYesNo(value));
} else if (STRICT_SYNTAX_KEY_SNAKE_CASE.equals(name) || STRICT_SYNTAX_KEY_CAMEL_CASE.equals(name)) {
setStrictSyntaxMode(StringUtil.getYesNo(value));
} else if (WHITESPACE_STRIPPING_KEY_SNAKE_CASE.equals(name)
|| WHITESPACE_STRIPPING_KEY_CAMEL_CASE.equals(name)) {
setWhitespaceStripping(StringUtil.getYesNo(value));
} else if (AUTO_ESCAPING_POLICY_KEY_SNAKE_CASE.equals(name) || AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE.equals(name)) {
if ("enable_if_default".equals(value) || "enableIfDefault".equals(value)) {
setAutoEscapingPolicy(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
} else if ("enable_if_supported".equals(value) || "enableIfSupported".equals(value)) {
setAutoEscapingPolicy(ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY);
} else if ("disable".equals(value)) {
setAutoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY);
} else {
throw invalidSettingValueException(name, value);
}
} else if (OUTPUT_FORMAT_KEY_SNAKE_CASE.equals(name) || OUTPUT_FORMAT_KEY_CAMEL_CASE.equals(name)) {
if (value.equalsIgnoreCase(DEFAULT)) {
unsetOutputFormat();
} else {
OutputFormat stdOF = STANDARD_OUTPUT_FORMATS.get(value);
setOutputFormat(
stdOF != null ? stdOF
: (OutputFormat) _ObjectBuilderSettingEvaluator.eval(
value, OutputFormat.class, true, _SettingEvaluationEnvironment.getCurrent()));
}
} else if (REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE.equals(name)
|| REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE.equals(name)) {
List list = (List) _ObjectBuilderSettingEvaluator.eval(
value, List.class, true, _SettingEvaluationEnvironment.getCurrent());
for (Object item : list) {
if (!(item instanceof OutputFormat)) {
throw new _MiscTemplateException(getEnvironment(),
"Invalid value for setting ", new _DelayedJQuote(name), ": List items must be "
+ OutputFormat.class.getName() + " instances, in: ", value);
}
}
setRegisteredCustomOutputFormats(list);
} else if (RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE.equals(name)
|| RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE.equals(name)) {
if (value.equalsIgnoreCase(DEFAULT)) {
unsetRecognizeStandardFileExtensions();
} else {
setRecognizeStandardFileExtensions(StringUtil.getYesNo(value));
}
} else if (CACHE_STORAGE_KEY_SNAKE_CASE.equals(name) || CACHE_STORAGE_KEY_CAMEL_CASE.equals(name)) {
if (value.equalsIgnoreCase(DEFAULT)) {
unsetCacheStorage();
} if (value.indexOf('.') == -1) {
int strongSize = 0;
int softSize = 0;
Map map = StringUtil.parseNameValuePairList(
value, String.valueOf(Integer.MAX_VALUE));
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry ent = (Map.Entry) it.next();
String pname = (String) ent.getKey();
int pvalue;
try {
pvalue = Integer.parseInt((String) ent.getValue());
} catch (NumberFormatException e) {
throw invalidSettingValueException(name, value);
}
if ("soft".equalsIgnoreCase(pname)) {
softSize = pvalue;
} else if ("strong".equalsIgnoreCase(pname)) {
strongSize = pvalue;
} else {
throw invalidSettingValueException(name, value);
}
}
if (softSize == 0 && strongSize == 0) {
throw invalidSettingValueException(name, value);
}
setCacheStorage(new MruCacheStorage(strongSize, softSize));
} else {
setCacheStorage((CacheStorage) _ObjectBuilderSettingEvaluator.eval(
value, CacheStorage.class, false, _SettingEvaluationEnvironment.getCurrent()));
}
} else if (TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_UPDATE_DELAY_KEY_CAMEL_CASE.equals(name)) {
long multiplier;
String valueWithoutUnit;
if (value.endsWith("ms")) {
multiplier = 1;
valueWithoutUnit = rightTrim(value.substring(0, value.length() - 2));
} else if (value.endsWith("s")) {
multiplier = 1000;
valueWithoutUnit = rightTrim(value.substring(0, value.length() - 1));
} else if (value.endsWith("m")) {
multiplier = 1000 * 60;
valueWithoutUnit = rightTrim(value.substring(0, value.length() - 1));
} else if (value.endsWith("h")) {
multiplier = 1000 * 60 * 60;
valueWithoutUnit = rightTrim(value.substring(0, value.length() - 1));
} else {
multiplier = 1000; // Default is seconds for backward compatibility
valueWithoutUnit = value;
}
setTemplateUpdateDelayMilliseconds(Integer.parseInt(valueWithoutUnit) * multiplier);
} else if (TAG_SYNTAX_KEY_SNAKE_CASE.equals(name) || TAG_SYNTAX_KEY_CAMEL_CASE.equals(name)) {
if ("auto_detect".equals(value) || "autoDetect".equals(value)) {
setTagSyntax(AUTO_DETECT_TAG_SYNTAX);
} else if ("angle_bracket".equals(value) || "angleBracket".equals(value)) {
setTagSyntax(ANGLE_BRACKET_TAG_SYNTAX);
} else if ("square_bracket".equals(value) || "squareBracket".equals(value)) {
setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX);
} else {
throw invalidSettingValueException(name, value);
}
} else if (INTERPOLATION_SYNTAX_KEY_SNAKE_CASE.equals(name)
|| INTERPOLATION_SYNTAX_KEY_CAMEL_CASE.equals(name)) {
if ("legacy".equals(value)) {
setInterpolationSyntax(LEGACY_INTERPOLATION_SYNTAX);
} else if ("dollar".equals(value)) {
setInterpolationSyntax(DOLLAR_INTERPOLATION_SYNTAX);
} else if ("square_bracket".equals(value) || "squareBracket".equals(value)) {
setInterpolationSyntax(SQUARE_BRACKET_INTERPOLATION_SYNTAX);
} else {
throw invalidSettingValueException(name, value);
}
} else if (NAMING_CONVENTION_KEY_SNAKE_CASE.equals(name) || NAMING_CONVENTION_KEY_CAMEL_CASE.equals(name)) {
if ("auto_detect".equals(value) || "autoDetect".equals(value)) {
setNamingConvention(AUTO_DETECT_NAMING_CONVENTION);
} else if ("legacy".equals(value)) {
setNamingConvention(LEGACY_NAMING_CONVENTION);
} else if ("camel_case".equals(value) || "camelCase".equals(value)) {
setNamingConvention(CAMEL_CASE_NAMING_CONVENTION);
} else {
throw invalidSettingValueException(name, value);
}
} else if (TAB_SIZE_KEY_SNAKE_CASE.equals(name) || TAB_SIZE_KEY_CAMEL_CASE.equals(name)) {
setTabSize(Integer.parseInt(value));
} else if (INCOMPATIBLE_IMPROVEMENTS_KEY_SNAKE_CASE.equals(name)
|| INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE.equals(name)) {
setIncompatibleImprovements(new Version(value));
} else if (INCOMPATIBLE_ENHANCEMENTS.equals(name)) {
setIncompatibleEnhancements(value);
} else if (TEMPLATE_LOADER_KEY_SNAKE_CASE.equals(name) || TEMPLATE_LOADER_KEY_CAMEL_CASE.equals(name)) {
if (value.equalsIgnoreCase(DEFAULT)) {
unsetTemplateLoader();
} else {
setTemplateLoader((TemplateLoader) _ObjectBuilderSettingEvaluator.eval(
value, TemplateLoader.class, true, _SettingEvaluationEnvironment.getCurrent()));
}
} else if (TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE.equals(name)) {
if (value.equalsIgnoreCase(DEFAULT)) {
unsetTemplateLookupStrategy();
} else {
setTemplateLookupStrategy((TemplateLookupStrategy) _ObjectBuilderSettingEvaluator.eval(
value, TemplateLookupStrategy.class, false, _SettingEvaluationEnvironment.getCurrent()));
}
} else if (TEMPLATE_NAME_FORMAT_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_NAME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
if (value.equalsIgnoreCase(DEFAULT)) {
unsetTemplateNameFormat();
} else if (value.equalsIgnoreCase("default_2_3_0")) {
setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_3_0);
} else if (value.equalsIgnoreCase("default_2_4_0")) {
setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_4_0);
} else {
throw invalidSettingValueException(name, value);
}
} else if (TEMPLATE_CONFIGURATIONS_KEY_SNAKE_CASE.equals(name)
|| TEMPLATE_CONFIGURATIONS_KEY_CAMEL_CASE.equals(name)) {
if (value.equals(NULL)) {
setTemplateConfigurations(null);
} else {
setTemplateConfigurations((TemplateConfigurationFactory) _ObjectBuilderSettingEvaluator.eval(
value, TemplateConfigurationFactory.class, false, _SettingEvaluationEnvironment.getCurrent()));
}
} else if (FALLBACK_ON_NULL_LOOP_VARIABLE_KEY_SNAKE_CASE.equals(name)
|| FALLBACK_ON_NULL_LOOP_VARIABLE_KEY_CAMEL_CASE.equals(name)) {
setFallbackOnNullLoopVariable(StringUtil.getYesNo(value));
} else {
unknown = true;
}
} catch (Exception e) {
throw settingValueAssignmentException(name, value, e);
}
if (unknown) {
super.setSetting(name, value);
}
}
private String rightTrim(String s) {
int ln = s.length();
while (ln > 0 && Character.isWhitespace(s.charAt(ln - 1))) {
ln--;
}
return s.substring(0, ln);
}
/**
* Returns the valid {@link Configuration} setting names. Naturally, this includes the {@link Configurable} setting
* names too.
*
* @param camelCase
* If we want the setting names with camel case naming convention, or with snake case (legacy) naming
* convention.
*
* @see Configurable#getSettingNames(boolean)
*
* @since 2.3.24
*/
@Override
public Set getSettingNames(boolean camelCase) {
return new _UnmodifiableCompositeSet<>(
super.getSettingNames(camelCase),
new _SortedArraySet<>(camelCase ? SETTING_NAMES_CAMEL_CASE : SETTING_NAMES_SNAKE_CASE));
}
@Override
protected String getCorrectedNameForUnknownSetting(String name) {
if ("encoding".equals(name) || "charset".equals(name) || "default_charset".equals(name)) {
// [2.4] Default might changes to camel-case
return DEFAULT_ENCODING_KEY;
}
if ("defaultCharset".equals(name)) {
return DEFAULT_ENCODING_KEY_CAMEL_CASE;
}
return super.getCorrectedNameForUnknownSetting(name);
}
@Override
protected void doAutoImportsAndIncludes(Environment env) throws TemplateException, IOException {
Template t = env.getMainTemplate();
doAutoImports(env, t);
doAutoIncludes(env, t);
}
private void doAutoImports(Environment env, Template t) throws IOException, TemplateException {
Map envAutoImports = env.getAutoImportsWithoutFallback();
Map tAutoImports = t.getAutoImportsWithoutFallback();
boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports().booleanValue()
: env.getLazyImports();
for (Map.Entry autoImport : getAutoImportsWithoutFallback().entrySet()) {
String nsVarName = autoImport.getKey();
if ((tAutoImports == null || !tAutoImports.containsKey(nsVarName))
&& (envAutoImports == null || !envAutoImports.containsKey(nsVarName))) {
env.importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
}
}
if (tAutoImports != null) {
for (Map.Entry autoImport : tAutoImports.entrySet()) {
String nsVarName = autoImport.getKey();
if (envAutoImports == null || !envAutoImports.containsKey(nsVarName)) {
env.importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
}
}
}
if (envAutoImports != null) {
for (Map.Entry autoImport : envAutoImports.entrySet()) {
String nsVarName = autoImport.getKey();
env.importLib(autoImport.getValue(), nsVarName, lazyAutoImports);
}
}
}
private void doAutoIncludes(Environment env, Template t) throws TemplateException, IOException,
TemplateNotFoundException, MalformedTemplateNameException, ParseException {
// We can't store autoIncludes in LinkedHashSet-s because setAutoIncludes(List) allows duplicates,
// unfortunately. Yet we have to prevent duplicates among Configuration levels, with the lowest levels having
// priority. So we build some Set-s to do that, but we avoid the most common cases where they aren't needed.
List tAutoIncludes = t.getAutoIncludesWithoutFallback();
List envAutoIncludes = env.getAutoIncludesWithoutFallback();
for (String templateName : getAutoIncludesWithoutFallback()) {
if ((tAutoIncludes == null || !tAutoIncludes.contains(templateName))
&& (envAutoIncludes == null || !envAutoIncludes.contains(templateName))) {
env.include(getTemplate(templateName, env.getLocale()));
}
}
if (tAutoIncludes != null) {
for (String templateName : tAutoIncludes) {
if (envAutoIncludes == null || !envAutoIncludes.contains(templateName)) {
env.include(getTemplate(templateName, env.getLocale()));
}
}
}
if (envAutoIncludes != null) {
for (String templateName : envAutoIncludes) {
env.include(getTemplate(templateName, env.getLocale()));
}
}
}
/**
* Returns FreeMarker version number string.
*
* @deprecated Use {@link #getVersion()} instead.
*/
@Deprecated
public static String getVersionNumber() {
return VERSION.toString();
}
/**
* Returns FreeMarker version information, most importantly the major.minor.micro version numbers;
* do NOT use this as the value of the {@code incompatible_improvements} setting (as the parameter to
* {@link Configuration#Configuration(Version)}), as then your application can break when you upgrade FreeMarker!
* Use a constant value, like {@link #VERSION_2_3_28}, to protect your application from fixes/changes that aren't
* entirely backward compatible. Fixes and features that are backward compatible are always enabled.
*
* On FreeMarker version numbering rules:
*
* - For final/stable releases the version number is like major.minor.micro, like 2.3.19. (Historically,
* when micro was 0 the version strings was like major.minor instead of the proper major.minor.0, but that's
* not like that anymore.)
*
- When only the micro version is increased, compatibility with previous versions with the same
* major.minor is kept. Thus freemarker.jar can be replaced in an existing application without
* breaking it.
* - For non-final/unstable versions (that almost nobody uses), the format is:
*
* - Starting from 2.3.20: major.minor.micro-extraInfo, like
* 2.3.20-nightly_20130506T123456Z, 2.4.0-RC01. The major.minor.micro
* always indicates the target we move towards, so 2.3.20-nightly or 2.3.20-M01 is
* after 2.3.19 and will eventually become to 2.3.20. "PRE", "M" and "RC" (uppercase!) means
* "preview", "milestone" and "release candidate" respectively, and is always followed by a 2 digit
* 0-padded counter, like M03 is the 3rd milestone release of a given major.minor.micro.
* - Before 2.3.20: The extraInfo wasn't preceded by a "-".
* Instead of "nightly" there was "mod", where the major.minor.micro part has indicated where
* are we coming from, so 2.3.19mod (read as: 2.3.19 modified) was after 2.3.19 but before 2.3.20.
* Also, "pre" and "rc" was lowercase, and was followd by a number without 0-padding.
*
*
*
* @since 2.3.20
*/
public static Version getVersion() {
return VERSION;
}
/**
* Returns the default object wrapper for a given "incompatible_improvements" version.
*
* @see #setIncompatibleImprovements(Version)
*
* @since 2.3.21
*/
public static ObjectWrapper getDefaultObjectWrapper(Version incompatibleImprovements) {
if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
return ObjectWrapper.DEFAULT_WRAPPER;
} else {
return new DefaultObjectWrapperBuilder(incompatibleImprovements).build();
}
}
/**
* Same as {@link #getSupportedBuiltInNames(int)} with argument {@link #getNamingConvention()}.
*
* @since 2.3.20
*/
public Set getSupportedBuiltInNames() {
return getSupportedBuiltInNames(getNamingConvention());
}
/**
* Returns the names of the supported "built-ins". These are the ({@code expr?builtin_name}-like things). As of this
* writing, this information doesn't depend on the configuration options, so it could be a static method, but
* to be future-proof, it's an instance method.
*
* @param namingConvention
* One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
* {@link #CAMEL_CASE_NAMING_CONVENTION}. If it's {@link #AUTO_DETECT_NAMING_CONVENTION} then the union
* of the names in all the naming conventions is returned.
*
* @since 2.3.24
*/
public Set getSupportedBuiltInNames(int namingConvention) {
return _CoreAPI.getSupportedBuiltInNames(namingConvention);
}
/**
* Same as {@link #getSupportedBuiltInDirectiveNames(int)} with argument {@link #getNamingConvention()}.
*
* @since 2.3.21
*/
public Set getSupportedBuiltInDirectiveNames() {
return getSupportedBuiltInDirectiveNames(getNamingConvention());
}
/**
* Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
* <#directiveName ...>.
*
* @param namingConvention
* One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
* {@link #CAMEL_CASE_NAMING_CONVENTION}. If it's {@link #AUTO_DETECT_NAMING_CONVENTION} then the union
* of the names in all the naming conventions is returned.
*
* @since 2.3.24
*/
public Set getSupportedBuiltInDirectiveNames(int namingConvention) {
if (namingConvention == AUTO_DETECT_NAMING_CONVENTION) {
return _CoreAPI.ALL_BUILT_IN_DIRECTIVE_NAMES;
} else if (namingConvention == LEGACY_NAMING_CONVENTION) {
return _CoreAPI.LEGACY_BUILT_IN_DIRECTIVE_NAMES;
} else if (namingConvention == CAMEL_CASE_NAMING_CONVENTION) {
return _CoreAPI.CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES;
} else {
throw new IllegalArgumentException("Unsupported naming convention constant: " + namingConvention);
}
}
private static String getRequiredVersionProperty(Properties vp, String properyName) {
String s = vp.getProperty(properyName);
if (s == null) {
throw new RuntimeException(
"Version file is corrupt: \"" + properyName + "\" property is missing.");
}
return s;
}
}