| /* |
| * 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.Collator; |
| 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.CFormat; |
| 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.JavaScriptOrJSONCFormat; |
| import freemarker.core.JavaScriptOutputFormat; |
| import freemarker.core.LegacyCFormat; |
| 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.XSCFormat; |
| 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; |
| |
| /** |
| * <b>The main entry point into the FreeMarker API</b>; encapsulates the configuration settings of FreeMarker, |
| * also serves as a central template-loading and caching service. |
| * |
| * <p>This class is meant to be used in a singleton pattern. That is, you create an instance of this at the beginning of |
| * the application life-cycle, set its {@link #setSetting(String, String) configuration settings} there (either with the |
| * setter methods like {@link #setTemplateLoader(TemplateLoader)} or by loading a {@code .properties} file), and then |
| * use that single instance everywhere in your application. Frequently re-creating {@link Configuration} is a typical |
| * and grave mistake from performance standpoint, as the {@link Configuration} holds the template cache, and often also |
| * the class introspection cache, which then will be lost. (Note that, naturally, having multiple long-lived instances, |
| * like one per component that internally uses FreeMarker is fine.) |
| * |
| * <p>The basic usage pattern is like: |
| * |
| * <pre> |
| * // Where the application is initialized; in general you do this ONLY ONCE in the application life-cycle! |
| * Configuration cfg = new Configuration(VERSION_<i>X</i>_<i>Y</i>_<i>Z</i>)); |
| * // Where VERSION_<i>X</i>_<i>Y</i>_<i>Z</i> enables the not-100%-backward-compatible fixes introduced in |
| * // FreeMarker version X.Y.Z and earlier (see {@link #Configuration(Version)}). |
| * cfg.set<i>SomeSetting</i>(...); |
| * cfg.set<i>OtherSetting</i>(...); |
| * ... |
| * |
| * // Later, whenever the application needs a template (so you may do this a lot, and from multiple threads): |
| * {@link Template Template} myTemplate = cfg.{@link #getTemplate(String) getTemplate}("myTemplate.ftlh"); |
| * myTemplate.{@link Template#process(Object, java.io.Writer) process}(dataModel, out);</pre> |
| * |
| * <p>A couple of settings that you should not leave on its default value are: |
| * <ul> |
| * <li>{@link #setTemplateLoader(TemplateLoader) template_loader}: The default value is deprecated and in fact quite |
| * useless. (For the most common cases you can use the convenience methods, |
| * {@link #setDirectoryForTemplateLoading(File)} and {@link #setClassForTemplateLoading(Class, String)} and |
| * {@link #setClassLoaderForTemplateLoading(ClassLoader, String)} too.) |
| * <li>{@link #setDefaultEncoding(String) default_encoding}: The default value is system dependent, which makes it |
| * fragile on servers, so it should be set explicitly, like to "UTF-8" nowadays. |
| * <li>{@link #setTemplateExceptionHandler(TemplateExceptionHandler) template_exception_handler}: For developing |
| * HTML pages, the most convenient value is {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}. For production, |
| * {@link TemplateExceptionHandler#RETHROW_HANDLER} is safer to use. |
| * <!-- 2.4: recommend the new object wrapper here --> |
| * </ul> |
| * |
| * <p>A {@link Configuration} object is thread-safe only after you have stopped modifying the configuration settings, |
| * and you have <b>safely published</b> it (see JSR 133 and related literature) to other threads. Generally, you set |
| * everything directly after you have instantiated the {@link Configuration} object, then you don't change the settings |
| * anymore, so then it's safe to make it accessible (again, via a "safe publication" technique) from multiple threads. |
| * The methods that aren't for modifying settings, like {@link #getTemplate(String)}, are thread-safe. |
| */ |
| public class Configuration extends Configurable implements Cloneable, ParserConfiguration { |
| |
| private static final 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<String, OutputFormat> 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 <em>not</em> 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 <em>not</em> change <code>${x}</code> to {@code [=x]}; that's square bracket <em>interpolation</em> |
| * syntax ({@link #SQUARE_BRACKET_INTERPOLATION_SYNTAX}). |
| */ |
| public static final int SQUARE_BRACKET_TAG_SYNTAX = 2; |
| |
| /** <code>${expression}</code> and the deprecated <code>#{expression; numFormat}</code> @since 2.3.28 */ |
| public static final int LEGACY_INTERPOLATION_SYNTAX = 20; |
| |
| /** <code>${expression}</code> only (not <code>#{expression; numFormat}</code>) @since 2.3.28 */ |
| public static final int DOLLAR_INTERPOLATION_SYNTAX = 21; |
| |
| /** |
| * <code>[=expression]</code> instead of <code>${expression}</code>. |
| * It does <em>not</em> change {@code <#if x>} to {@code [#if x]}; that's square bracket <em>tag</em> 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; |
| /** |
| * This policy is to always require auto-escaping, to avoid accidents where because of misconfiguration, or a |
| * mistake of the template author it's disabled. With this policy, using output formats that don't support escaping |
| * will not be allowed. Using built-ins, and directives that disable auto-escaping (like {@code ?no_esc}) will also |
| * be errors (on parse-time). Note that if markup (like HTML) comers from the data model, then with this policy you |
| * will have to ensure that they come as {@link TemplateMarkupOutputModel}-s (which won't be auto-escaped even with |
| * this policy), not as {@link String}-s, because the template authors can't disable escaping for the value anymore. |
| */ |
| public static final int FORCE_AUTO_ESCAPING_POLICY = 23; |
| |
| /** 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); |
| |
| /** FreeMarker version 2.3.32 (an {@link #Configuration(Version) incompatible improvements break-point}) */ |
| public static final Version VERSION_2_3_32 = new Version(2, 3, 32); |
| |
| /** 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<String, ? extends OutputFormat> 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 boolean cFormatExplicitlySet; |
| |
| |
| private HashMap/*<String, TemplateModel>*/ sharedVariables = new HashMap(); |
| |
| /** |
| * Needed so that it doesn't mater in what order do you call {@link #setSharedVaribles(Map)} |
| * and {@link #setObjectWrapper(ObjectWrapper)}. When the user configures FreeMarker from Spring XML, he has no |
| * control over the order, so it has to work on both ways. |
| */ |
| private HashMap/*<String, Object>*/ rewrappableSharedVariables = null; |
| |
| private String defaultEncoding = 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. |
| * |
| * <p><b>About the "incompatible improvements" setting</b> |
| * |
| * <p>This setting value is the FreeMarker version number where the not 100% backward compatible bug fixes and |
| * improvements that you want to enable were already implemented. In new projects you should set this to the 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. |
| * |
| * <p>Bugfixes and improvements that are fully backward compatible, also those that are important security fixes, |
| * are enabled regardless of the incompatible improvements setting. |
| * |
| * <p>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.) |
| * |
| * <p>An important consequence of setting this setting is that now your application will check if the stated minimum |
| * FreeMarker version requirement is met. Like if you set this setting to 2.3.22, but accidentally the application |
| * is deployed with FreeMarker 2.3.21, then FreeMarker will fail, telling that a higher version is required. After |
| * all, the fixes/improvements you have requested aren't available on a lower version. |
| * |
| * <p>Note that as FreeMarker's minor (2nd) or major (1st) version number increments, it's possible that emulating |
| * some of the old bugs will become unsupported, that is, even if you set this setting to a low value, it silently |
| * wont bring back the old behavior anymore. Information about that will be present here. |
| * |
| * <p>Currently the effects of this setting are: |
| * <ul> |
| * <li><p> |
| * 2.3.0: This is the lowest supported value, the version used in 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. |
| * </li> |
| * <li><p> |
| * 2.3.19 (or higher): Bug fix: Wrong {@code #} tags were printed as static text instead of |
| * causing parsing error when there was no correct {@code #} or {@code @} tag earlier in the |
| * same template. |
| * </li> |
| * <li><p> |
| * 2.3.20 (or higher): {@code ?html} will escape apostrophe-quotes just like {@code ?xhtml} does. Utilizing |
| * this is highly recommended, because otherwise if interpolations are used inside attribute values that use |
| * apostrophe-quotation (<code><foo bar='${val}'></code>) instead of plain quotation mark |
| * (<code><foo bar="${val}"></code>), they might produce HTML/XML that's not well-formed. Note that |
| * {@code ?html} didn't do this because long ago there was no cross-browser way of doing this, but it's not a |
| * concern anymore. |
| * </li> |
| * <li><p> |
| * 2.3.21 (or higher): |
| * <ul> |
| * <li><p> |
| * The <em>default</em> of the {@code object_wrapper} setting ({@link #getObjectWrapper()}) changes from |
| * {@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. |
| * </li> |
| * <li><p> |
| * The {@code ?iso_...} built-ins won't show the time zone offset for {@link java.sql.Time} values anymore, |
| * because most databases store time values that aren't in any time zone, but just store hour, minute, |
| * second, and decimal second field values. If you still want to show the offset (like for PostgreSQL |
| * "time with time zone" columns you should), you can force showing the time zone offset by using |
| * {@code myTime?string.iso_fz} (and its other variants). |
| * </li> |
| * <li><p>{@code ?is_enumerable} correctly returns {@code false} for Java methods get from Java objects that |
| * are wrapped with {@link BeansWrapper} and its subclasses, like {@link DefaultObjectWrapper}. Although |
| * method values implement {@link TemplateSequenceModel} (because of a historical design quirk in |
| * {@link BeansWrapper}), trying to {@code #list} them will cause error, hence they aren't enumerable. |
| * </li> |
| * <li><p> |
| * {@code ?c} will return {@code "INF"}, {@code "-INF"} and {@code "NaN"} for positive/negative infinity |
| * and IEEE floating point Not-a-Number, respectively. These are the XML Schema compatible representations |
| * of these special values. Earlier it has returned what {@link DecimalFormat} did with US locale, none of |
| * which was understood by any (common) computer language. |
| * </li> |
| * <li><p> |
| * FTL hash literals that repeat keys now only have the key once with {@code ?keys}, and only has the last |
| * value associated to that key with {@code ?values}. This is consistent with the behavior of |
| * {@code hash[key]} and how maps work in Java. |
| * </li> |
| * <li><p>In most cases (where FreeMarker is able to do that), for {@link TemplateLoader}-s that use |
| * {@link URLConnection}, {@code URLConnection#setUseCaches(boolean)} will called with {@code false}, |
| * so that only FreeMarker will do caching, not the URL scheme's handler. |
| * See {@link URLTemplateLoader#setURLConnectionUsesCaches(Boolean)} for more details. |
| * </li> |
| * <li><p> |
| * The default of the {@code template_loader} setting ({@link Configuration#getTemplateLoader()}) changes |
| * to {@code null}, which means that FreeMarker will not find any templates. Earlier |
| * the default was a {@link FileTemplateLoader} that used the current directory as the root. This was |
| * dangerous and fragile as you usually don't have good control over what the current directory will be. |
| * Luckily, the old default almost never looked for the templates at the right place |
| * anyway, so pretty much all applications had to set the {@code template_loader} setting, so it's unlikely |
| * that changing the default breaks your application. |
| * </li> |
| * <li><p> |
| * Right-unlimited ranges become readable (like listable), so {@code <#list 1.. as i>...</#list>} works. |
| * Earlier they were only usable for slicing (like {@code hits[10..]}). |
| * </li> |
| * <li><p> |
| * Empty ranges return {@link Constants#EMPTY_SEQUENCE} instead of an empty {@link SimpleSequence}. This |
| * is in theory backward compatible, as the API only promises to give something that implements |
| * {@link TemplateSequenceModel}. |
| * </li> |
| * <li><p> |
| * Unclosed comments ({@code <#-- ...}) and {@code #noparse}-s won't be silently closed at the end of |
| * template anymore, but cause a parsing error instead. |
| * </li> |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.22 (or higher): |
| * <ul> |
| * <li><p> |
| * {@link DefaultObjectWrapper} has some substantial changes with {@code incompatibleImprovements} 2.3.22; |
| * check them out at {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. It's important to know |
| * that if you set the {@code object_wrapper} setting (to an other value than {@code "default"}), rather |
| * than leaving it on its default value, the {@code object_wrapper} won't inherit the |
| * {@code incompatibleImprovements} of the {@link Configuration}. In that case, if you want the 2.3.22 |
| * improvements of {@link DefaultObjectWrapper}, you have to set it in the {@link DefaultObjectWrapper} |
| * object itself too! (Note that it's OK to use a {@link DefaultObjectWrapper} with a different |
| * {@code incompatibleImprovements} version number than that of the {@link Configuration}, if that's |
| * really what you want.) |
| * </li> |
| * <li><p> |
| * In templates, {@code .template_name} will <em>always</em> return the main (top level) template's name. |
| * It won't be affected by {@code #include} and {@code #nested} anymore. This is unintended, a bug with |
| * {@code incompatible_improvement} 2.3.22 (a consequence of the lower level fixing described in the next |
| * point). The old behavior of {@code .template_name} is restored if you set |
| * {@code incompatible_improvement} to 2.3.23 (while {@link Configurable#getParent()} of |
| * {@link Environment} keeps the changed behavior shown in the next point). |
| * </li> |
| * <li><p> |
| * {@code #include} and {@code #nested} doesn't change the parent {@link Template} (see |
| * {@link Configurable#getParent()}) of the {@link Environment} anymore to the {@link Template} that's |
| * included or whose namespace {@code #nested} "returns" to. Thus, the parent of {@link Environment} will |
| * be now always the main {@link Template}. (The main {@link Template} is the {@link Template} whose |
| * {@code process} or {@code createProcessingEnvironment} method was called to initiate the output |
| * generation.) Note that apart from the effect on FTL's {@code .template_name} (see |
| * previous point), this should only matter if you have set settings directly on {@link Template} objects, |
| * and almost nobody does that. Also note that macro calls have never changed the {@link Environment} |
| * parent to the {@link Template} that contains the macro definition, so this mechanism was always broken. |
| * As now we consistently never change the parent, the behavior when calling macros didn't change. |
| * </li> |
| * <li><p> |
| * When using {@code freemarker.ext.servlet.FreemarkerServlet}: |
| * <ul> |
| * <li> |
| * <p>When using custom JSP tag libraries: Fixes bug where some kind of |
| * values, when put into the JSP <em>page</em> scope (via {@code #global} or via the JSP |
| * {@code PageContext} API) and later read back with the JSP {@code PageContext} API (typically in a |
| * custom JSP tag), might come back as FreeMarker {@link TemplateModel} objects instead of as objects |
| * with a standard Java type. Other Servlet scopes aren't affected. It's highly unlikely that |
| * something expects the presence of this bug. The affected values are of the FTL types listed below, |
| * and to trigger the bug, they either had to be created directly in the template (like as an FTL |
| * literal or with {@code ?date}/{@code time}/{@code datetime}), or you had to use |
| * {@link DefaultObjectWrapper} or {@link SimpleObjectWrapper} (or a subclass of them): |
| * |
| * <ul> |
| * <li>FTL date/time/date-time values may came back as {@link SimpleDate}-s, now they come back as |
| * {@link java.util.Date java.util.Date}-s instead.</li> |
| * |
| * <li>FTL sequence values may came back as {@link SimpleSequence}-s, now they come back as |
| * {@link java.util.List}-s as expected. This at least stands assuming that the |
| * {@link Configuration#setSetting(String, String) object_wrapper} configuration setting is a |
| * subclass of {@link BeansWrapper} (such as {@link DefaultObjectWrapper}, which is the default), |
| * but that's practically always the case in applications that use FreeMarker's JSP extension |
| * (otherwise it can still work, but it depends on the quality and capabilities of the |
| * {@link ObjectWrapper} implementation).</li> |
| * |
| * <li>FTL hash values may came back as {@link SimpleHash}-es, now they come back as |
| * {@link java.util.Map}-s as expected (again, assuming that the object wrapper is a subclass of |
| * {@link BeansWrapper}, like preferably {@link DefaultObjectWrapper}, which is also the default). |
| * </li> |
| * |
| * <li>FTL collection values may came back as {@link SimpleCollection}-s, now they come back as |
| * {@link java.util.Collection}-s as expected (again, assuming that the object wrapper is a subclass |
| * of {@link BeansWrapper}, like preferably {@link DefaultObjectWrapper}).</li> |
| * </ul> |
| * </li> |
| * <li><p> |
| * Initial {@code "["} in the {@code TemplatePath} init-param |
| * has special meaning; it's used for specifying multiple comma separated locations, like in |
| * {@code <param-value>[ WEB-INF/templates, classpath:com/example/myapp/templates ]</param-value>} |
| * </li> |
| * <li><p> |
| * Initial <code>"{"</code> in the {@code TemplatePath} init-param is reserved for future purposes, and |
| * thus will throw exception. |
| * </li> |
| * </ul> |
| * </li> |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.23 (or higher): |
| * <ul> |
| * <li><p> |
| * Fixed a loophole in the implementation of the long existing parse-time rule that says that |
| * {@code #break}, in the FTL source code itself, must occur nested inside a breakable directive, such as |
| * {@code #list} or {@code #switch}. This check could be circumvented with {@code #macro} or |
| * {@code #function}, like this: |
| * {@code <#list 1..1 as x><#macro callMeLater><#break></#macro></#list><@callMeLater />}. |
| * After activating this fix, this will be a parse time error. |
| * </li> |
| * <li><p> |
| * If you have used {@code incompatible_improvements} 2.3.22 earlier, know that there the behavior of the |
| * {@code .template_name} special variable used in templates was accidentally altered, but now it's |
| * restored to be backward compatible with 2.3.0. (Ironically, the restored legacy behavior itself is |
| * broken when it comes to macro invocations, we just keep it for backward compatibility. If you need fixed |
| * behavior, use {@code .current_template_name} or {@code .main_template_name} instead.) |
| * </li> |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.24 (or higher): |
| * <ul> |
| * <li><p> |
| * The default of the |
| * {@link #setRecognizeStandardFileExtensions(boolean) recognize_standard_file_extensions} |
| * setting changes to {@code true}, which means that templates whose name ends with {@code ".ftlh"} or |
| * {@code ".ftlx"} will automatically get {@link HTMLOutputFormat#INSTANCE} or |
| * {@link XMLOutputFormat#INSTANCE} output format respectively, in both cases with |
| * {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY} {@link #setAutoEscapingPolicy(int) auto_escaping_policy}. |
| * These "file" extensions aren't case sensitive. |
| * </li> |
| * <li><p> |
| * In number format and date format strings (like in the {@code number_format} setting, or in templates in |
| * {@code n?string("0.##")}), an initial {@code '@'} has special meaning; they refer to a custom format |
| * with the name given after the {@code @} (see: {@link #setCustomNumberFormats(Map)}, |
| * {@link #setCustomDateFormats(Map)}, {@link #setNumberFormat(String)}, and {@link #setDateTimeFormat}). |
| * If the custom format doesn't exist, that will be an error. To have a literal {@code @} as the first |
| * character in the output, it has to be written as {@code @@}. Again, all this only applies to the very |
| * first character of the format string, so {@code @} characters elsewhere must not be doubled. Also, if |
| * there are any custom formats defined, initial {@code '@'} will have the new meaning regardless of |
| * the value of the {@code incompatible_improvements} setting. So you don't need to set the |
| * {@code incompatible_improvements} only to use custom formats. |
| * </li> |
| * <li><p> |
| * Expressions inside interpolations that were inside <em>string literal expressions</em> |
| * (not <code>${...}</code>-s in general), like in <code><#assign s="Hello ${name}!"></code>, has |
| * always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect). Now it's fixed. |
| * </li> |
| * <li><p> |
| * {@link DefaultObjectWrapper} has some minor changes with {@code incompatibleImprovements} 2.3.24; |
| * check them out at {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. It's important to know |
| * that if you set the {@code object_wrapper} setting (to an other value than {@code "default"}), rather |
| * than leaving it on its default value, the {@code object_wrapper} won't inherit the |
| * {@code incompatibleImprovements} of the {@link Configuration}. In that case, if you want the 2.3.24 |
| * improvements of {@link DefaultObjectWrapper}, you have to set it in the {@link DefaultObjectWrapper} |
| * object itself too! (Note that it's OK to use a {@link DefaultObjectWrapper} with a different |
| * {@code incompatibleImprovements} version number than that of the {@link Configuration}, if that's |
| * really what you want.) |
| * </li> |
| * <li><p> |
| * Fixed bug: The {@code #import} directive meant to copy the library variable into a global variable if |
| * it's executed in the main namespace, but that haven't happened when the imported template was already |
| * imported earlier in another namespace. |
| * </li> |
| * <li><p> |
| * {@code ?is_sequence} doesn't return {@code true} for Java methods wrapped by {@link BeansWrapper} and |
| * its subclasses (most notably {@link DefaultObjectWrapper}) anymore, as they only implement the |
| * {@code [index]} operator, but not {@code ?size}, which causes {@code <#list ...>} to fail among others. |
| * (They shouldn't implement either, but this is historical heritage.) |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.25 (or higher): |
| * <ul> |
| * <li><p> |
| * When calling {@link Configurable#setAutoIncludes(List)} on a {@link Configuration}, it filters out |
| * duplicates from the list, similarly as repeatedly calling {@link Configurable#addAutoInclude(String)} |
| * would, hence avoiding repeated inclusions. Calling {@link Configurable#setAutoIncludes(List)} on other |
| * {@link Configurable}-s always do this filtering regardless of the incompatible improvements setting. |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.26 (or higher): |
| * <ul> |
| * <li><p> |
| * {@link BeansWrapper} and {@link DefaultObjectWrapper} now exposes Java 8 default methods (and the bean |
| * properties they define); see {@link BeansWrapper#BeansWrapper(Version)}. |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.27 (or higher): |
| * <ul> |
| * <li><p> |
| * {@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)}. |
| * <li><p> |
| * 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). |
| * <li><p> |
| * 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. |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.28 (or higher): |
| * <ul> |
| * <li><p>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. |
| * <li><p>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. |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.31 (or higher): |
| * <ul> |
| * <li><p>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. |
| * </ul> |
| * </li> |
| * <li><p> |
| * 2.3.32 (or higher): |
| * <ul> |
| * <li><p> |
| * The number formatting of {@code ?c}, {@code ?cn} (and thus also of the {@code "c"}, and |
| * {@code "computer"} {@link Configurable#setNumberFormat(String) number_format}) changes, if the |
| * {@link #setCFormat(CFormat) c_format} setting was left on its default. The default of |
| * {@link #setCFormat(CFormat) c_format} changes to {@link JavaScriptOrJSONCFormat#INSTANCE}, from |
| * {@link LegacyCFormat#INSTANCE}, and that's what contains the changes:</p> |
| * <ul> |
| * <li><p>Changes affecting non-whole numbers, and whole numbers with over 100 digits: |
| * Formatting is now lossless, so it potentially shows much more decimals. |
| * It now uses exponential format (like 1.2E-7 instead of 0.00000012) for numbers whose absolute value |
| * is less than 1E-6 (0.000001), and for whole numbers whose absolute value is at least 1E101 (so over |
| * 100 digits). It also uses exponential format for whole floating point |
| * ({@code double}/{@code Double}}, or {@code float}/{@code Float}) numbers, when their absolute value |
| * is too big for the floating point type to store them precisely (so if the intent was to store some |
| * ID-s, they are likely corrupted anyway, as the type skips some whole numbers).</p></li> |
| * <li><p>Changes affecting floating point infinity: Output changes from {@code INF} to {@code Infinity}, |
| * which is the JavaScript and JSON syntax. If you generate XML with XSD-style number syntax (which uses |
| * {@code INF}), but you want the other number formatting changes (recommended), then set |
| * {@link #setCFormat(CFormat) c_format} to {@link XSCFormat#INSTANCE}/{@code "XS"}.</p></li> |
| * </ul> |
| * </li> |
| * </ul> |
| * </li> |
| * <li> |
| * <p> |
| * 2.3.33 (or higher): |
| * <ul> |
| * <li><p>Comparing strings is now way faster. If your template does lot of string comparisons, this can |
| * mean very significant speedup. We now use a simpler way of comparing strings, and because templates |
| * were only ever allowed equality comparisons between strings (not less-than, or greater-than), it's very |
| * unlikely to change the behavior of your templates. (Technically, what changes is that instead of using |
| * Java's localized {@link Collator}-s, we switch to a simple binary comparison after UNICODE NFKC |
| * normalization. So, in theory it's possible that for some locales two different but similarly looking |
| * characters were treated as equal by the collator, but will count as different now. But it's very |
| * unlikely that anyone wanted to depend on such fragile logic anyway. Note again that we still do UNICODE |
| * normalization, so combining characters won't break your comparison.)</p></li> |
| * </ul> |
| * </li> |
| * </ul> |
| * |
| * @throws IllegalArgumentException |
| * If {@code incompatibleImmprovements} refers to a version that wasn't released yet when the currently |
| * used FreeMarker version was released, or is less than 2.3.0, or is {@code null}. |
| * |
| * @since 2.3.21 |
| */ |
| public Configuration(Version incompatibleImprovements) { |
| super(incompatibleImprovements); |
| |
| // We postpone this until here (rather that doing this in static initializer) for two reason: |
| // - Class initialization errors are often not reported very well |
| // - This way we avoid the error if FM isn't actually used |
| checkFreeMarkerVersionClash(); |
| |
| NullArgumentException.check("incompatibleImprovements", incompatibleImprovements); |
| 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() < _VersionInts.V_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: |
| * |
| * <table style="width: auto; border-collapse: collapse" border="1"> |
| * <caption style="display: none">Preset language to encoding mapping</caption> |
| * <tr><td>ar</td><td>ISO-8859-6</td></tr> |
| * <tr><td>be</td><td>ISO-8859-5</td></tr> |
| * <tr><td>bg</td><td>ISO-8859-5</td></tr> |
| * <tr><td>ca</td><td>ISO-8859-1</td></tr> |
| * <tr><td>cs</td><td>ISO-8859-2</td></tr> |
| * <tr><td>da</td><td>ISO-8859-1</td></tr> |
| * <tr><td>de</td><td>ISO-8859-1</td></tr> |
| * <tr><td>el</td><td>ISO-8859-7</td></tr> |
| * <tr><td>en</td><td>ISO-8859-1</td></tr> |
| * <tr><td>es</td><td>ISO-8859-1</td></tr> |
| * <tr><td>et</td><td>ISO-8859-1</td></tr> |
| * <tr><td>fi</td><td>ISO-8859-1</td></tr> |
| * <tr><td>fr</td><td>ISO-8859-1</td></tr> |
| * <tr><td>hr</td><td>ISO-8859-2</td></tr> |
| * <tr><td>hu</td><td>ISO-8859-2</td></tr> |
| * <tr><td>is</td><td>ISO-8859-1</td></tr> |
| * <tr><td>it</td><td>ISO-8859-1</td></tr> |
| * <tr><td>iw</td><td>ISO-8859-8</td></tr> |
| * <tr><td>ja</td><td>Shift_JIS</td></tr> |
| * <tr><td>ko</td><td>EUC-KR</td></tr> |
| * <tr><td>lt</td><td>ISO-8859-2</td></tr> |
| * <tr><td>lv</td><td>ISO-8859-2</td></tr> |
| * <tr><td>mk</td><td>ISO-8859-5</td></tr> |
| * <tr><td>nl</td><td>ISO-8859-1</td></tr> |
| * <tr><td>no</td><td>ISO-8859-1</td></tr> |
| * <tr><td>pl</td><td>ISO-8859-2</td></tr> |
| * <tr><td>pt</td><td>ISO-8859-1</td></tr> |
| * <tr><td>ro</td><td>ISO-8859-2</td></tr> |
| * <tr><td>ru</td><td>ISO-8859-5</td></tr> |
| * <tr><td>sh</td><td>ISO-8859-5</td></tr> |
| * <tr><td>sk</td><td>ISO-8859-2</td></tr> |
| * <tr><td>sl</td><td>ISO-8859-2</td></tr> |
| * <tr><td>sq</td><td>ISO-8859-2</td></tr> |
| * <tr><td>sr</td><td>ISO-8859-5</td></tr> |
| * <tr><td>sv</td><td>ISO-8859-1</td></tr> |
| * <tr><td>tr</td><td>ISO-8859-9</td></tr> |
| * <tr><td>uk</td><td>ISO-8859-5</td></tr> |
| * <tr><td>zh</td><td>GB2312</td></tr> |
| * <tr><td>zh_TW</td><td>Big5</td></tr> |
| * </table> |
| * |
| * @see #clearEncodingMap() |
| * @see #setEncoding(Locale, String) |
| * @see #setDefaultEncoding(String) |
| */ |
| public void loadBuiltInEncodingMap() { |
| localeToCharsetMap.clear(); |
| localeToCharsetMap.put("ar", "ISO-8859-6"); |
| localeToCharsetMap.put("be", "ISO-8859-5"); |
| localeToCharsetMap.put("bg", "ISO-8859-5"); |
| localeToCharsetMap.put("ca", "ISO-8859-1"); |
| localeToCharsetMap.put("cs", "ISO-8859-2"); |
| localeToCharsetMap.put("da", "ISO-8859-1"); |
| localeToCharsetMap.put("de", "ISO-8859-1"); |
| localeToCharsetMap.put("el", "ISO-8859-7"); |
| localeToCharsetMap.put("en", "ISO-8859-1"); |
| localeToCharsetMap.put("es", "ISO-8859-1"); |
| localeToCharsetMap.put("et", "ISO-8859-1"); |
| localeToCharsetMap.put("fi", "ISO-8859-1"); |
| localeToCharsetMap.put("fr", "ISO-8859-1"); |
| localeToCharsetMap.put("hr", "ISO-8859-2"); |
| localeToCharsetMap.put("hu", "ISO-8859-2"); |
| localeToCharsetMap.put("is", "ISO-8859-1"); |
| localeToCharsetMap.put("it", "ISO-8859-1"); |
| localeToCharsetMap.put("iw", "ISO-8859-8"); |
| localeToCharsetMap.put("ja", "Shift_JIS"); |
| localeToCharsetMap.put("ko", "EUC-KR"); |
| localeToCharsetMap.put("lt", "ISO-8859-2"); |
| localeToCharsetMap.put("lv", "ISO-8859-2"); |
| localeToCharsetMap.put("mk", "ISO-8859-5"); |
| localeToCharsetMap.put("nl", "ISO-8859-1"); |
| localeToCharsetMap.put("no", "ISO-8859-1"); |
| localeToCharsetMap.put("pl", "ISO-8859-2"); |
| localeToCharsetMap.put("pt", "ISO-8859-1"); |
| localeToCharsetMap.put("ro", "ISO-8859-2"); |
| localeToCharsetMap.put("ru", "ISO-8859-5"); |
| localeToCharsetMap.put("sh", "ISO-8859-5"); |
| localeToCharsetMap.put("sk", "ISO-8859-2"); |
| localeToCharsetMap.put("sl", "ISO-8859-2"); |
| localeToCharsetMap.put("sq", "ISO-8859-2"); |
| localeToCharsetMap.put("sr", "ISO-8859-5"); |
| localeToCharsetMap.put("sv", "ISO-8859-1"); |
| localeToCharsetMap.put("tr", "ISO-8859-9"); |
| localeToCharsetMap.put("uk", "ISO-8859-5"); |
| localeToCharsetMap.put("zh", "GB2312"); |
| localeToCharsetMap.put("zh_TW", "Big5"); |
| } |
| |
| /** |
| * Clears language-to-encoding map. |
| * @see #loadBuiltInEncodingMap |
| * @see #setEncoding |
| */ |
| public void clearEncodingMap() { |
| localeToCharsetMap.clear(); |
| } |
| |
| /** |
| * Returns the default (singleton) Configuration object. Note that you can |
| * create as many separate configurations as you wish; this global instance |
| * is provided for convenience, or when you have no reason to use a separate |
| * instance. |
| * |
| * @deprecated The usage of the static singleton (the "default") |
| * {@link Configuration} instance can easily cause erroneous, unpredictable |
| * behavior. This is because multiple independent software components may use |
| * FreeMarker internally inside the same application, so they will interfere |
| * because of the common {@link Configuration} instance. Each such component |
| * should use its own private {@link Configuration} object instead, that it |
| * typically creates with <code>new Configuration()</code> when the component |
| * is initialized. |
| */ |
| @Deprecated |
| static public Configuration getDefaultConfiguration() { |
| Configuration defaultConfig = Configuration.defaultConfig; |
| if (defaultConfig == null) { |
| synchronized (defaultConfigLock) { |
| defaultConfig = Configuration.defaultConfig; |
| if (defaultConfig == null) { |
| defaultConfig = new Configuration(); |
| Configuration.defaultConfig = defaultConfig; |
| } |
| } |
| } |
| return defaultConfig; |
| } |
| |
| /** |
| * Sets the Configuration object that will be retrieved from future calls |
| * to {@link #getDefaultConfiguration()}. |
| * |
| * @deprecated Using the "default" {@link Configuration} instance can |
| * easily lead to erroneous, unpredictable behaviour. |
| * See more {@link Configuration#getDefaultConfiguration() here...}. |
| */ |
| @Deprecated |
| static public void setDefaultConfiguration(Configuration config) { |
| synchronized (defaultConfigLock) { |
| defaultConfig = config; |
| } |
| } |
| |
| /** |
| * Sets a {@link TemplateLoader} that is used to look up and load templates; |
| * as a side effect the template cache will be emptied (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. |
| * |
| * <p>Convenience methods exists to install commonly used loaders, instead of using this method: |
| * {@link #setClassForTemplateLoading(Class, String)}, |
| * {@link #setClassLoaderForTemplateLoading(ClassLoader, String)}, |
| * {@link #setDirectoryForTemplateLoading(File)}, and |
| * {@link #setServletContextForTemplateLoading(Object, String)}. |
| * |
| * <p>You can chain several {@link TemplateLoader}-s together with {@link MultiTemplateLoader}. |
| * |
| * <p>Default value: You should always set the template loader instead of relying on the default value. |
| * (But if you still care what it is, before "incompatible improvements" 2.3.21 it's a {@link FileTemplateLoader} |
| * that uses the current directory as its root; as it's hard tell what that directory will be, it's not very useful |
| * and dangerous. Starting with "incompatible improvements" 2.3.21 the default is {@code null}.) |
| */ |
| public void setTemplateLoader(TemplateLoader templateLoader) { |
| // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration |
| synchronized (this) { |
| if (cache.getTemplateLoader() != templateLoader) { |
| recreateTemplateCacheWith(templateLoader, cache.getCacheStorage(), |
| cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), |
| cache.getTemplateConfigurations()); |
| } |
| templateLoaderExplicitlySet = true; |
| } |
| } |
| |
| /** |
| * Resets the setting to its default, as if it was never set. This means that when you change the |
| * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also |
| * {@link #isTemplateLoaderExplicitlySet()} will return {@code false}. |
| * |
| * @since 2.3.22 |
| */ |
| public void unsetTemplateLoader() { |
| if (templateLoaderExplicitlySet) { |
| setTemplateLoader(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. |
| * |
| * <p> |
| * Note that the settings suggested by standard file extensions are stronger than that you set here. See |
| * {@link #setRecognizeStandardFileExtensions(boolean)} for more information about standard file extensions. |
| * |
| * <p>See "Template configurations" in the FreeMarker Manual for examples. |
| * |
| * @since 2.3.24 |
| */ |
| public void setTemplateConfigurations(TemplateConfigurationFactory templateConfigurations) { |
| if (cache.getTemplateConfigurations() != templateConfigurations) { |
| if (templateConfigurations != null) { |
| templateConfigurations.setConfiguration(this); |
| } |
| recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage(), |
| cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), |
| templateConfigurations); |
| } |
| } |
| |
| /** |
| * The getter pair of {@link #setTemplateConfigurations(TemplateConfigurationFactory)}. |
| */ |
| public TemplateConfigurationFactory getTemplateConfigurations() { |
| if (cache == null) { |
| return null; |
| } |
| return cache.getTemplateConfigurations(); |
| } |
| |
| /** |
| * Sets the {@link CacheStorage} used for caching {@link Template}-s; |
| * the earlier content of the template cache will be dropt. |
| * |
| * The default is a {@link SoftCacheStorage}. If the total size of the {@link Template} |
| * objects is significant but most templates are used rarely, using a |
| * {@link MruCacheStorage} instead might be advisable. If you don't want caching at |
| * all, use {@link freemarker.cache.NullCacheStorage} (you can't use {@code null}). |
| * |
| * <p>Note that setting the cache storage will re-create the template cache, so |
| * all its content will be lost. |
| */ |
| public void setCacheStorage(CacheStorage cacheStorage) { |
| // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration |
| synchronized (this) { |
| if (getCacheStorage() != cacheStorage) { |
| recreateTemplateCacheWith(cache.getTemplateLoader(), cacheStorage, |
| cache.getTemplateLookupStrategy(), cache.getTemplateNameFormat(), |
| cache.getTemplateConfigurations()); |
| } |
| cacheStorageExplicitlySet = true; |
| } |
| } |
| |
| /** |
| * Resets the setting to its default, as if it was never set. This means that when you change the |
| * {@code incompatibe_improvements} setting later, the default will also change as appropriate. Also |
| * {@link #isCacheStorageExplicitlySet()} will return {@code false}. |
| * |
| * @since 2.3.22 |
| */ |
| public void unsetCacheStorage() { |
| if (cacheStorageExplicitlySet) { |
| setCacheStorage(getDefaultCacheStorage()); |
| cacheStorageExplicitlySet = false; |
| } |
| } |
| |
| /** |
| * Tells if {@link #setCacheStorage(CacheStorage)} (or equivalent) was already called on this instance. |
| * |
| * @since 2.3.22 |
| */ |
| public boolean isCacheStorageExplicitlySet() { |
| return cacheStorageExplicitlySet; |
| } |
| |
| /** |
| * The getter pair of {@link #setCacheStorage(CacheStorage)}. |
| * |
| * @since 2.3.20 |
| */ |
| public CacheStorage getCacheStorage() { |
| // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration |
| synchronized (this) { |
| if (cache == null) { |
| return null; |
| } |
| return cache.getCacheStorage(); |
| } |
| } |
| |
| /** |
| * Sets the file system directory from which to load templates. This is equivalent to |
| * {@code setTemplateLoader(new FileTemplateLoader(dir))}, so see |
| * {@link FileTemplateLoader#FileTemplateLoader(File)} for more details. |
| * |
| * <p> |
| * Note that FreeMarker can load templates from non-file-system sources too. See |
| * {@link #setTemplateLoader(TemplateLoader)} from more details. |
| * |
| * <p> |
| * Note that this shouldn't be used for loading templates that are coming from a WAR; use |
| * {@link #setServletContextForTemplateLoading(Object, String)} then. Servlet containers might not unpack the WAR |
| * file, in which case you clearly can't access the contained files via {@link File}. Even if the WAR is unpacked, |
| * the servlet container might not expose the location as a {@link File}. |
| * {@link #setServletContextForTemplateLoading(Object, String)} on the other hand will work in all these cases. |
| */ |
| public void setDirectoryForTemplateLoading(File dir) throws IOException { |
| TemplateLoader tl = getTemplateLoader(); |
| if (tl instanceof FileTemplateLoader) { |
| String path = ((FileTemplateLoader) tl).baseDir.getCanonicalPath(); |
| if (path.equals(dir.getCanonicalPath())) |
| return; |
| } |
| setTemplateLoader(new FileTemplateLoader(dir)); |
| } |
| |
| /** |
| * Sets the servlet context from which to load templates. |
| * This is equivalent to {@code setTemplateLoader(new WebappTemplateLoader(sctxt, path))} |
| * or {@code setTemplateLoader(new WebappTemplateLoader(sctxt))} if {@code path} was |
| * {@code null}, so see {@code 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. |
| * |
| * <p> |
| * Historical note: Despite what the API documentation said earlier, this method is <em>not</em> thread-safe. While |
| * it works well on most hardware, it's not guaranteed that FreeMarker will see the update in all threads, and |
| * theoretically it's also possible that it will see a value that's a binary mixture of the new and the old one. |
| * |
| * @deprecated Use {@link #setTemplateUpdateDelayMilliseconds(long)} instead, because the time granularity of this method |
| * is often misunderstood to be milliseconds. |
| */ |
| @Deprecated |
| public void setTemplateUpdateDelay(int seconds) { |
| cache.setDelay(1000L * seconds); |
| } |
| |
| /** |
| * Sets the time in milliseconds that must elapse before checking whether there is a newer version of a template |
| * "file" than the cached one. Defaults to 5000 ms. |
| * |
| * <p> |
| * When you get a template via {@link #getTemplate(String)} (or some of its overloads). FreeMarker will try to get |
| * the template from the template cache. If the template is found, and at least this amount of time was elapsed |
| * since the template last modification date was checked, FreeMarker will re-check the last modification date (this |
| * could mean I/O), possibly reloading the template and updating the cache as a consequence (can mean even more |
| * I/O). The {@link #getTemplate(String)} (or some of its overloads) call will only return after this all is |
| * done, so it will return the fresh template. |
| * |
| * @since 2.3.23 |
| */ |
| public void setTemplateUpdateDelayMilliseconds(long millis) { |
| cache.setDelay(millis); |
| } |
| |
| /** |
| * The getter pair of {@link #setTemplateUpdateDelayMilliseconds(long)}. |
| * |
| * @since 2.3.23 |
| */ |
| public long getTemplateUpdateDelayMilliseconds() { |
| return cache.getDelay(); |
| } |
| |
| /** |
| * Sets whether directives such as {@code if}, {@code else}, etc must be written as {@code #if}, {@code #else}, etc. |
| * Defaults to {@code true}. |
| * |
| * <p>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. |
| * |
| * <p>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.) |
| * |
| * <p>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 (!cFormatExplicitlySet) { |
| cFormatExplicitlySet = true; |
| unsetCFormat(); |
| } |
| |
| 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}. |
| * |
| * <p>Note that the default output format, {@link UndefinedOutputFormat}, is a non-escaping format, so there |
| * auto-escaping will be off. |
| * |
| * <p>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. |
| * |
| * <p><b>About auto-escaping</b></p> |
| * |
| * <p> |
| * Auto-escaping has significance when a value is printed with <code>${...}</code> (or <code>#{...}</code>). If |
| * auto-escaping is on, FreeMarker will assume that the value is plain text (as opposed to markup or some kind of |
| * rich text), so it will escape it according the current output format (see {@link #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. |
| * |
| * <p>Further notes on auto-escaping: |
| * <ul> |
| * <li>When printing numbers, dates, and other kind of non-string values with <code>${...}</code>, they will be |
| * first converted to string (according the formatting settings and locale), then they are escaped just like |
| * string values. |
| * <li>When printing {@link TemplateMarkupOutputModel}-s, they aren't escaped again (they are already escaped). |
| * <li>Auto-escaping doesn't do anything if the current output format isn't an {@link MarkupOutputFormat}. |
| * That's the case for the default output format, {@link UndefinedOutputFormat}, and also for |
| * {@link PlainTextOutputFormat}. |
| * <li>The output format inside a string literal expression is always {@link PlainTextOutputFormat} |
| * (regardless of the output format of the containing template), which is a non-escaping format. Thus for |
| * example, with <code><#assign s = "foo${bar}"></code>, {@code bar} will always get into {@code s} |
| * without escaping, but with <code><#assign s>foo${bar}<#assign></code> it may will be escaped. |
| * </ul> |
| * |
| * <p>Note that what you set here is just a default, which can be overridden for individual templates 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}, {@link #DISABLE_AUTO_ESCAPING_POLICY}, and |
| * {@link #FORCE_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. |
| * |
| * <p> |
| * 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 <code>HTML{RTF}</code>, where "HTML" and "RTF" refer to the |
| * existing formats. This logic can be used recursively, so for example <code>XML{HTML{RTF}}</code> is |
| * also valid. |
| * |
| * @return Not {@code null}. |
| * |
| * @throws UnregisteredOutputFormatException |
| * If there's no output format registered with the given name. |
| * @throws IllegalArgumentException |
| * If the usage of <code>{</code> and <code>}</code> in the name is syntactically wrong, or if not all |
| * {@link OutputFormat}-s are {@link MarkupOutputFormat}-s in the <code>...{...}</code> expression. |
| * |
| * @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<String> 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)}. |
| * |
| * <p> |
| * When there's a clash between a custom output format name and a standard output format name, the custom format |
| * will win, thus you can override the meaning of standard output format names. Except, it's not allowed to override |
| * {@link UndefinedOutputFormat} and {@link PlainTextOutputFormat}. |
| * |
| * <p> |
| * The default value is an empty collection. |
| * |
| * @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 <code>'{'</code> or <code>'}'</code>. |
| * 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<String, OutputFormat> 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: |
| * |
| * <ul> |
| * <li>{@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}. |
| * <li>{@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}. |
| * </ul> |
| * |
| * <p>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. |
| * |
| * <p>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() >= _VersionInts.V_2_3_24 |
| : recognizeStandardFileExtensions.booleanValue(); |
| } |
| |
| @Override |
| public void setCFormat(CFormat cFormat) { |
| super.setCFormat(cFormat); |
| cFormatExplicitlySet = 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 #isCFormatExplicitlySet()} will return {@code false}. |
| * |
| * @since 2.3.32 |
| */ |
| public void unsetCFormat() { |
| if (cFormatExplicitlySet) { |
| setCFormat(getDefaultCFormat(incompatibleImprovements)); |
| cFormatExplicitlySet = false; |
| } |
| } |
| |
| static CFormat getDefaultCFormat(Version incompatibleImprovements) { |
| return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_32 |
| ? JavaScriptOrJSONCFormat.INSTANCE |
| : LegacyCFormat.INSTANCE; |
| } |
| |
| /** |
| * Tells if {@link #setCFormat(CFormat)} (or equivalent) was already called on this instance. |
| * |
| * @since 2.3.32 |
| */ |
| public boolean isCFormatExplicitlySet() { |
| return cFormatExplicitlySet; |
| } |
| |
| /** |
| * 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. |
| * |
| * <p>The {@code tagSyntax} parameter must be one of: |
| * <ul> |
| * <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}: |
| * Use the syntax of the first FreeMarker tag (can be anything, like {@code #list}, |
| * {@code #include}, user defined, etc.) |
| * <li>{@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}: |
| * Use the angle bracket tag syntax (the normal syntax), like {@code <#include ...>} |
| * <li>{@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}: |
| * Use the square bracket tag syntax, like {@code [#include ...]}. Note that this does <em>not</em> change |
| * <code>${x}</code> to {@code [=...]}; that's <em>interpolation</em> syntax, so use |
| * {@link #setInterpolationSyntax(int)} for that. |
| * </ul> |
| * |
| * <p>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. |
| * |
| * <p>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 <code>${x}</code> VS <code>[=x]</code>) of the template files. Don't |
| * confuse this with the tag syntax ({@link #setTagSyntax(int)}); they are independent. |
| * |
| * <p> |
| * 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 <em>not</em> change {@code <#if x>} to |
| * {@code [#if x]}; that's <em>tag</em> 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. |
| * |
| * <p> |
| * 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}). |
| * |
| * <p> |
| * 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. |
| * |
| * <p> |
| * 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. |
| * |
| * <p> |
| * 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. |
| * |
| * <p> |
| * 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. |
| * |
| * <p>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. |
| * |
| * <p> |
| * This is a shorthand for {@link #getTemplate(String, Locale, Object, String, boolean, boolean) |
| * getTemplate(name, null, null, null, true, false)}; see more details there. |
| * |
| * <p> |
| * 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. |
| * |
| * <p> |
| * This method is thread-safe. |
| * |
| * <p> |
| * 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 <em>not</em> 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 <i>acquisition mechanism</i>. 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 <em>same</em> 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>${...}</code>, |
| * {@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 <code>IOException</code>) 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. |
| * |
| * <p>Defaults to the default system encoding, which can change from one server to |
| * another, so <b>you should always set this setting</b>. If you don't know what charset your should chose, |
| * {@code "UTF-8"} is usually a good choice. |
| * |
| * <p>Note that individual templates may specify their own charset by starting with |
| * <code><#ftl encoding="..."></code> |
| * |
| * @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. |
| * |
| * <p>Never use {@code 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. |
| * |
| * <p>This method is <b>not</b> 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. |
| * |
| * <p>This method is <b>not</b> thread safe; use it with the same restrictions as those that modify setting values. |
| * |
| * <p>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). |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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<String, ?> 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/*<String, Object>*/ map) throws TemplateModelException { |
| setSharedVariables(map); |
| } |
| |
| private void setSharedVariablesFromRewrappableSharedVariables() throws TemplateModelException { |
| if (rewrappableSharedVariables == null) return; |
| for (Iterator it = rewrappableSharedVariables.entrySet().iterator(); it.hasNext(); ) { |
| Map.Entry/*<String, Object>*/ 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. |
| * |
| * <p>Never use {@code 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. |
| * |
| * <p>This method is <b>not</b> 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 <code>getTemplate</code> calls. |
| * |
| * <p>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. |
| * |
| * <p>For the meaning of the parameters, see |
| * {@link #getTemplate(String, Locale, Object, String, boolean, boolean)}. |
| * |
| * <p>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)}. |
| * |
| * <p>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. |
| * |
| * <p> |
| * 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}. |
| * |
| * <p>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). |
| * |
| * <p> |
| * Historical note: Despite what the API documentation said earlier, this method is <em>not</em> 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 ("force".equals(value)) { |
| setAutoEscapingPolicy(FORCE_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<String> 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 change 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<String, String> envAutoImports = env.getAutoImportsWithoutFallback(); |
| Map<String, String> tAutoImports = t.getAutoImportsWithoutFallback(); |
| |
| boolean lazyAutoImports = env.getLazyAutoImports() != null ? env.getLazyAutoImports().booleanValue() |
| : env.getLazyImports(); |
| |
| for (Map.Entry<String, String> 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<String, String> 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<String, String> 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<String> tAutoIncludes = t.getAutoIncludesWithoutFallback(); |
| List<String> 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: |
| * <ul> |
| * <li>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.) |
| * <li>When only the micro version is increased, compatibility with previous versions with the same |
| * major.minor is kept. Thus {@code freemarker.jar} can be replaced in an existing application without |
| * breaking it.</li> |
| * <li>For non-final/unstable versions (that almost nobody uses), the format is: |
| * <ul> |
| * <li>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.</li> |
| * <li>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.</li> |
| * </ul> |
| * </ul> |
| * |
| * @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() < _VersionInts.V_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<String> 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 |
| * <code><#directiveName ...></code>. |
| * |
| * @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<String> 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; |
| } |
| |
| } |