All Downloads are FREE. Search and download functionalities are using the official Maven repository.

freemarker.template.Configuration Maven / Gradle / Ivy

Go to download

Google App Engine compliant variation of FreeMarker. FreeMarker is a "template engine"; a generic tool to generate text output based on templates.

There is a newer version: 2.3.34
Show newest version
/*
 * Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
 * 
 * Licensed 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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.SortedMap;
import java.util.TreeMap;

import javax.servlet.ServletContext;

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.TemplateLoader;
import freemarker.cache.URLTemplateLoader;
import freemarker.cache.WebappTemplateLoader;
import freemarker.cache._CacheAPI;
import freemarker.core.BugException;
import freemarker.core.Configurable;
import freemarker.core.Environment;
import freemarker.core.ParseException;
import freemarker.core._ConcurrentMapFactory;
import freemarker.core._CoreAPI;
import freemarker.core._ObjectBuilderSettingEvaluator;
import freemarker.core._SettingEvaluationEnvironment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.utility.CaptureOutput;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.Constants;
import freemarker.template.utility.HtmlEscape;
import freemarker.template.utility.NormalizeNewlines;
import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.SecurityUtilities;
import freemarker.template.utility.StandardCompress;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.XmlEscape;

/**
 * The main entry point into the FreeMarker API; encapsulates the configuration settings of FreeMarker,
 * also serves as a central template-loading and caching service.
 *
 * 

This class is meant to be used in a singleton pattern. That is, you create an instance of this at the beginning of * the application life-cycle, set its {@link #setSetting(String, String) configuration settings} there (either with the * setter methods like {@link #setTemplateLoader(TemplateLoader)} or by loading a {@code .properties} file), and then * use that single instance everywhere in your application. Frequently re-creating {@link Configuration} is a typical * and grave mistake from performance standpoint, as the {@link Configuration} holds the template cache, and often also * the class introspection cache, which then will be lost. (Note that, naturally, having multiple long-lived instances, * like one per component that internally uses FreeMarker is fine.) * *

The basic usage pattern is like: * *

 *  // Where the application is initialized; in general you do this ONLY ONCE in the application life-cycle!
 *  Configuration cfg = new Configuration(VERSION_X_Y_Z));
 *  // Where X, Y, Z enables the not-100%-backward-compatible fixes introduced in
 *  // FreeMarker version X.Y.Z  and earlier (see {@link #Configuration(Version)}).
 *  cfg.setSomeSetting(...);
 *  cfg.setOtherSetting(...);
 *  ...
 *  
 *  // Later, whenever the application needs a template (so you may do this a lot, and from multiple threads):
 *  {@link Template Template} myTemplate = cfg.{@link #getTemplate(String) getTemplate}("myTemplate.html");
 *  myTemplate.{@link Template#process(Object, java.io.Writer) process}(dataModel, out);
* *

A couple of settings that you should not leave on its default value are: *

    *
  • {@link #setTemplateLoader(TemplateLoader) template_loader}: The default value is deprecated and in fact quite * useless. (For the most common cases you can use the convenience methods, * {@link #setDirectoryForTemplateLoading(File)} and {@link #setClassForTemplateLoading(Class, String)} too.) *
  • {@link #setDefaultEncoding(String) default_encoding}: The default value is system dependent, which makes it * fragile on servers, so it should be set explicitly, like to "UTF-8" nowadays. *
  • {@link #setTemplateExceptionHandler(TemplateExceptionHandler) template_exception_handler}: For developing * HTML pages, the most convenient value is {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}. For production, * {@link TemplateExceptionHandler#RETHROW_HANDLER} is safer to use. * *
* *

A {@link Configuration} object is thread-safe only after you have stopped modifying the configuration settings, * and you have safely published it (see JSR 133 and related literature) to other threads. Generally, you set * everything directly after you have instantiated the {@link Configuration} object, then you don't change the settings * anymore, so then it's safe to make it accessible (again, via a "safe publication" technique) from multiple threads. * The methods that aren't for modifying settings, like {@link #getTemplate(String)}, are thread-safe. */ public class Configuration extends Configurable implements Cloneable { private static final String VERSION_PROPERTIES_PATH = "freemarker/version.properties"; public static final String DEFAULT_ENCODING_KEY = "default_encoding"; public static final String LOCALIZED_LOOKUP_KEY = "localized_lookup"; public static final String STRICT_SYNTAX_KEY = "strict_syntax"; public static final String WHITESPACE_STRIPPING_KEY = "whitespace_stripping"; public static final String CACHE_STORAGE_KEY = "cache_storage"; public static final String TEMPLATE_UPDATE_DELAY_KEY = "template_update_delay"; public static final String AUTO_IMPORT_KEY = "auto_import"; public static final String AUTO_INCLUDE_KEY = "auto_include"; public static final String TAG_SYNTAX_KEY = "tag_syntax"; public static final String TEMPLATE_LOADER_KEY = "template_loader"; public static final String INCOMPATIBLE_IMPROVEMENTS = "incompatible_improvements"; /** @deprecated Use {@link #INCOMPATIBLE_IMPROVEMENTS} instead. */ public static final String INCOMPATIBLE_ENHANCEMENTS = "incompatible_enhancements"; public static final int AUTO_DETECT_TAG_SYNTAX = 0; public static final int ANGLE_BRACKET_TAG_SYNTAX = 1; public static final int SQUARE_BRACKET_TAG_SYNTAX = 2; /** 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); /** The default of {@link #getIncompatibleImprovements()}, currently {@code new Version(2, 3, 0)}. */ public static final Version DEFAULT_INCOMPATIBLE_IMPROVEMENTS = Configuration.VERSION_2_3_0; /** @deprecated Use {@link #DEFAULT_INCOMPATIBLE_IMPROVEMENTS} instead. */ public static final String DEFAULT_INCOMPATIBLE_ENHANCEMENTS = DEFAULT_INCOMPATIBLE_IMPROVEMENTS.toString(); /** @deprecated Use {@link #DEFAULT_INCOMPATIBLE_IMPROVEMENTS} instead. */ public static final int PARSED_DEFAULT_INCOMPATIBLE_ENHANCEMENTS = DEFAULT_INCOMPATIBLE_IMPROVEMENTS.intValue(); private static final Version version; static { try { Properties vp = new Properties(); InputStream ins = Configuration.class.getClassLoader() .getResourceAsStream(VERSION_PROPERTIES_PATH); if (ins == null) { throw new RuntimeException("Version file is missing."); } else { try { vp.load(ins); } finally { ins.close(); } String versionString = getRequiredVersionProperty(vp, "version"); Date buildDate; { String buildDateStr = getRequiredVersionProperty(vp, "buildTimestamp"); if (buildDateStr.endsWith("Z")) { buildDateStr = buildDateStr.substring(0, buildDateStr.length() - 1) + "+0000"; } try { buildDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US).parse(buildDateStr); } catch (java.text.ParseException e) { buildDate = null; } } final Boolean gaeCompliant = Boolean.valueOf(getRequiredVersionProperty(vp, "isGAECompliant")); version = new Version(versionString, gaeCompliant, buildDate); } } catch (IOException e) { throw new RuntimeException("Failed to load and parse " + VERSION_PROPERTIES_PATH, e); } } private final static Object defaultConfigLock = new Object(); private static Configuration defaultConfig; private boolean strictSyntax = true; private volatile boolean localizedLookup = true; private boolean whitespaceStripping = true; private Version incompatibleImprovements; private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX; private TemplateCache cache; private boolean templateLoaderWasSet; private boolean objectWrapperWasSet; private HashMap/**/ sharedVariables = new HashMap(); /** * Needed so that it doesn't mater in what order do you call {@link #setSharedVaribles(Map)} * and {@link #setObjectWrapper(ObjectWrapper)}. When the user configures FreeMarker from Spring XML, he has no * control over the order, so it has to work on both ways. */ private HashMap/**/ rewrappableSharedVariables = null; private String defaultEncoding = SecurityUtilities.getSystemProperty("file.encoding"); private Map localeToCharsetMap = _ConcurrentMapFactory.newThreadSafeMap(); private ArrayList autoImports = new ArrayList(), autoIncludes = new ArrayList(); private Map autoImportNsToTmpMap = new HashMap(); // TODO No need for this, instead use List below. /** * @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)}). */ public Configuration() { this(DEFAULT_INCOMPATIBLE_IMPROVEMENTS); } /** * Creates a new instance and sets which of the non-backward-compatible bugfixes/improvements should be enabled. * Note that the specified versions corresponds to the {@code incompatible_improvements} configuration setting, and * can be changed later, with {@link #setIncompatibleImprovements(Version)} for example. * *

About the "incompatible improvements" setting * *

The setting value is the FreeMarker version number where the bugfixes/improvements to enable were already * implemented (but possibly wasn't active by default, as that would break backward-compatibility). * *

The default value is 2.3.0 for maximum backward-compatibility when upgrading {@code freemkarer.jar} under an * existing application. For actively developed applications usually you should set this to the highest possible * value where the 1st or 2nd version is still the same as in the version where you have started development. * As far as the 1st and 2nd version number remains, these changes are always very low-risk, so usually they don't * break anything. Of course, you are still encouraged to read the list of changes below. * *

Note that at FreeMarker 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. * *

This setting doesn't affect important not-fully-backward compatible security fixes; they are always enabled, * regardless of what you set here. * *

Currently the effects of this setting are: *

    *
  • * 2.3.0: This is the lowest supported value, the version used in older projects. This is the default in the * FreeMarker 2.3.x series. *

  • *
  • * 2.3.19 (or higher): Bug fix: Wrong {@code #} tags were printed as static text instead of * causing parsing error when there was no correct {@code #} or {@code @} tag earlier in the * same template. *

  • *
  • * 2.3.20 (or higher): {@code ?html} will escape apostrophe-quotes just like {@code ?xhtml} does. Utilizing * this is highly recommended, because otherwise if interpolations are used inside attribute values that use * apostrophe-quotation (<foo bar='${val}'>) instead of plain quotation mark * (<foo bar="${val}">), they might produce HTML/XML that's not well-formed. Note that * {@code ?html} didn't do this because long ago there was no cross-browser way of doing this, but it's not a * concern anymore. *

  • *
  • * 2.3.21 (or higher): *

      *
    • * The default of the {@code object_wrapper} setting ({@link #getObjectWrapper()}) changes from * {@link ObjectWrapper#DEFAULT_WRAPPER} to another almost identical {@link DefaultObjectWrapper} singleton, * returned by {@link DefaultObjectWrapperBuilder#build()}. The new default object wrapper's * "incompatible improvements" version is set to the same as of the {@link Configuration}. * See {@link BeansWrapper#BeansWrapper(Version)} for further details. Furthermore, the new default * object wrapper doesn't allow changing its settings; setter methods throw {@link IllegalStateException}). * (If anything tries to call setters on the old default in your application, that's a dangerous bug that * won't remain hidden now. As the old default is a singleton too, potentially shared by independently * developed components, most of them expects the out-of-the-box behavior from it (and the others are * necessarily buggy). Also, then concurrency glitches can occur (and even pollute the class introspection * cache) because the singleton is modified after publishing to other threads.) * Furthermore the new default object wrapper shares class introspection cache with other * {@link BeansWrapper}-s created with {@link BeansWrapperBuilder}, which has an impact as * {@link BeansWrapper#clearClassIntrospecitonCache()} will be disallowed; see more about it there. *

    • *
    • * The {@code ?iso_...} built-ins won't show the time zone offset for {@link java.sql.Time} values anymore, * because most databases store time values that aren't in any time zone, but just store hour, minute, * second, and decimal second field values. If you still want to show the offset (like for PostgreSQL * "time with time zone" columns you should), you can force showing the time zone offset by using * {@code myTime?string.iso_fz} (and its other variants). *

    • *
    • {@code ?is_enumerable} correctly returns {@code false} for Java methods get from Java objects that * are wrapped with {@link BeansWrapper} and its subclasses, like {@link DefaultObjectWrapper}. Although * method values implement {@link TemplateSequenceModel} (because of a historical design quirk in * {@link BeansWrapper}), trying to {@code #list} them will cause error, hence they aren't enumerable. *

    • *
    • * {@code ?c} will return {@code "INF"}, {@code "-INF"} and {@code "NaN"} for positive/negative infinity * and IEEE floating point Not-a-Number, respectively. These are the XML Schema compatible representations * of these special values. Earlier it has returned what {@link DecimalFormat} did with US locale, none of * which was understood by any (common) computer language. *

    • *
    • * FTL hash literals that repeat keys now only have the key once with {@code ?keys}, and only has the last * value associated to that key with {@code ?values}. This is consistent with the behavior of * {@code hash[key]} and how maps work in Java. *

    • *
    • In most cases (where FreeMarker is able to do that), for {@link TemplateLoader}-s that use * {@link URLConnection}, {@code URLConnection#setUseCaches(boolean)} will called with {@code false}, * so that only FreeMarker will do caching, not the URL scheme's handler. * See {@link URLTemplateLoader#setURLConnectionUsesCaches(Boolean)} for more details. *

    • *
    • * The default of the {@code template_loader} setting ({@link Configuration#getTemplateLoader()}) changes * to {@code null}, which means that FreeMarker will not find any templates. Earlier * the default was a {@link FileTemplateLoader} that used the current directory as the root. This was * dangerous and fragile as you usually don't have good control over what the current directory will be. * Luckily, the old default almost never looked for the templates at the right place * anyway, so pretty much all applications had to set the {@code template_loader} setting, so it's unlikely * that changing the default breaks your application. *

    • *
    • * Right-unlimited ranges become readable (like listable), so {@code <#list 1.. as i>...} works. * Earlier they were only usable for slicing (like {@code hits[10..]}). *

    • *
    • * Empty ranges return {@link Constants#EMPTY_SEQUENCE} instead of an empty {@link SimpleSequence}. This * is in theory backward compatible, as the API only promises to give something that implements * {@link TemplateSequenceModel}. *

    • *
    • * Unclosed comments ({@code <#-- ...}) and {@code #noparse}-s won't be silently closed at the end of * template anymore, but cause a parsing error instead. *

    • *
    *
  • *
* * @throws IllegalArgumentException if {@code incompatibleImmprovements} is greater than the current FreeMarker * version, or less than 2.3.0. * * @since 2.3.21 */ public Configuration(Version incompatibleImprovements) { super(incompatibleImprovements); NullArgumentException.check("incompatibleImprovements", incompatibleImprovements); this.incompatibleImprovements = incompatibleImprovements; createTemplateCache(); loadBuiltInSharedVariables(); } private void createTemplateCache() { cache = new TemplateCache(getDefaultTemplateLoader(), this); cache.clear(); // for fully BC behavior cache.setDelay(5000); } private void recreateTemplateCacheWith(TemplateLoader loader, CacheStorage storage) { TemplateCache oldCache = cache; cache = new TemplateCache(loader, storage, this); cache.clear(); // for fully BC behavior cache.setDelay(oldCache.getDelay()); cache.setLocalizedLookup(localizedLookup); } private TemplateLoader getDefaultTemplateLoader() { return incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21 ? _CacheAPI.createLegacyDefaultTemplateLoader() : null; } public Object clone() { try { Configuration copy = (Configuration)super.clone(); copy.sharedVariables = new HashMap(sharedVariables); copy.localeToCharsetMap = new HashMap(localeToCharsetMap); copy.autoImportNsToTmpMap = new HashMap(autoImportNsToTmpMap); copy.autoImports = (ArrayList) autoImports.clone(); copy.autoIncludes = (ArrayList) autoIncludes.clone(); copy.recreateTemplateCacheWith(cache.getTemplateLoader(), cache.getCacheStorage()); return copy; } catch (CloneNotSupportedException e) { throw new BugException(e.getMessage()); // Java 5: use cause exc. } } 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: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
arISO-8859-6
beISO-8859-5
bgISO-8859-5
caISO-8859-1
csISO-8859-2
daISO-8859-1
deISO-8859-1
elISO-8859-7
enISO-8859-1
esISO-8859-1
etISO-8859-1
fiISO-8859-1
frISO-8859-1
hrISO-8859-2
huISO-8859-2
isISO-8859-1
itISO-8859-1
iwISO-8859-8
jaShift_JIS
koEUC-KR
ltISO-8859-2
lvISO-8859-2
mkISO-8859-5
nlISO-8859-1
noISO-8859-1
plISO-8859-2
ptISO-8859-1
roISO-8859-2
ruISO-8859-5
shISO-8859-5
skISO-8859-2
slISO-8859-2
sqISO-8859-2
srISO-8859-5
svISO-8859-1
trISO-8859-9
ukISO-8859-5
zhGB2312
zh_TWBig5
* * @see #clearEncodingMap() * @see #setEncoding(Locale, String) * @see #setDefaultEncoding(String) */ public void loadBuiltInEncodingMap() { localeToCharsetMap.clear(); localeToCharsetMap.put("ar", "ISO-8859-6"); localeToCharsetMap.put("be", "ISO-8859-5"); localeToCharsetMap.put("bg", "ISO-8859-5"); localeToCharsetMap.put("ca", "ISO-8859-1"); localeToCharsetMap.put("cs", "ISO-8859-2"); localeToCharsetMap.put("da", "ISO-8859-1"); localeToCharsetMap.put("de", "ISO-8859-1"); localeToCharsetMap.put("el", "ISO-8859-7"); localeToCharsetMap.put("en", "ISO-8859-1"); localeToCharsetMap.put("es", "ISO-8859-1"); localeToCharsetMap.put("et", "ISO-8859-1"); localeToCharsetMap.put("fi", "ISO-8859-1"); localeToCharsetMap.put("fr", "ISO-8859-1"); localeToCharsetMap.put("hr", "ISO-8859-2"); localeToCharsetMap.put("hu", "ISO-8859-2"); localeToCharsetMap.put("is", "ISO-8859-1"); localeToCharsetMap.put("it", "ISO-8859-1"); localeToCharsetMap.put("iw", "ISO-8859-8"); localeToCharsetMap.put("ja", "Shift_JIS"); localeToCharsetMap.put("ko", "EUC-KR"); localeToCharsetMap.put("lt", "ISO-8859-2"); localeToCharsetMap.put("lv", "ISO-8859-2"); localeToCharsetMap.put("mk", "ISO-8859-5"); localeToCharsetMap.put("nl", "ISO-8859-1"); localeToCharsetMap.put("no", "ISO-8859-1"); localeToCharsetMap.put("pl", "ISO-8859-2"); localeToCharsetMap.put("pt", "ISO-8859-1"); localeToCharsetMap.put("ro", "ISO-8859-2"); localeToCharsetMap.put("ru", "ISO-8859-5"); localeToCharsetMap.put("sh", "ISO-8859-5"); localeToCharsetMap.put("sk", "ISO-8859-2"); localeToCharsetMap.put("sl", "ISO-8859-2"); localeToCharsetMap.put("sq", "ISO-8859-2"); localeToCharsetMap.put("sr", "ISO-8859-5"); localeToCharsetMap.put("sv", "ISO-8859-1"); localeToCharsetMap.put("tr", "ISO-8859-9"); localeToCharsetMap.put("uk", "ISO-8859-5"); localeToCharsetMap.put("zh", "GB2312"); localeToCharsetMap.put("zh_TW", "Big5"); } /** * Clears language-to-encoding map. * @see #loadBuiltInEncodingMap * @see #setEncoding */ public void clearEncodingMap() { localeToCharsetMap.clear(); } /** * Returns the default (singleton) Configuration object. Note that you can * create as many separate configurations as you wish; this global instance * is provided for convenience, or when you have no reason to use a separate * instance. * * @deprecated The usage of the static singleton (the "default") * {@link Configuration} instance can easily cause erroneous, unpredictable * behavior. This is because multiple independent software components may use * FreeMarker internally inside the same application, so they will interfere * because of the common {@link Configuration} instance. Each such component * should use its own private {@link Configuration} object instead, that it * typically creates with new Configuration() when the component * is initialized. */ static public Configuration getDefaultConfiguration() { // Java 5: use volatile + double check synchronized (defaultConfigLock) { if (defaultConfig == null) { defaultConfig = new Configuration(); } 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...}. */ static public void setDefaultConfiguration(Configuration config) { synchronized (defaultConfigLock) { defaultConfig = config; } } /** * Sets a {@link TemplateLoader} that is used to look up and load templates; * as a side effect the template cache will be emptied. * By providing your own {@link TemplateLoader} implementation, you can load templates from whatever kind of * storages, like from relational databases, NoSQL-storages, etc. * *

Convenience methods exists to install commonly used loaders, instead of using this method: * {@link #setClassForTemplateLoading(Class, String)}, * {@link #setDirectoryForTemplateLoading(File)}, and * {@link #setServletContextForTemplateLoading(Object, String)}. * *

You can chain several {@link TemplateLoader}-s together with {@link MultiTemplateLoader}. * *

Default value: You should always set the template loader instead of relying on the default value. * (But if you still care what it is, before "incompatible improvements" 2.3.21 it's a {@link FileTemplateLoader} * that uses the current directory as its root; as it's hard tell what that directory will be, it's not very useful * and dangerous. Starting with "incompatible improvements" 2.3.21 the default is {@code null}.) * *

Note that setting the template loader will re-create the template cache, so * all its content will be lost. */ 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()); templateLoaderWasSet = true; } } } /** * The getter pair of {@link #setTemplateLoader(TemplateLoader)}. */ public TemplateLoader getTemplateLoader() { return cache.getTemplateLoader(); } /** * Sets the {@link CacheStorage} used for caching {@link Template}-s; * the earlier content of the template cache will be dropt. * * The default is a {@link SoftCacheStorage}. If the total size of the {@link Template} * objects is significant but most templates are used rarely, using a * {@link MruCacheStorage} instead might be advisable. If you don't want caching at * all, use {@link freemarker.cache.NullCacheStorage} (you can't use {@code null}). * *

Note that setting the cache storage will re-create the template cache, so * all its content will be lost. */ public void setCacheStorage(CacheStorage storage) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { recreateTemplateCacheWith(cache.getTemplateLoader(), storage); } } /** * 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) { return cache.getCacheStorage(); } } /** * Sets the file system directory from which to load templates. * This is equivalent to {@code setTemplateLoader(new FileTemplateLoader(dir))}, * so see {@link FileTemplateLoader#FileTemplateLoader(File)} for more details. * * Note that FreeMarker can load templates from non-file-system sources too. * See {@link #setTemplateLoader(TemplateLoader)} from more details. */ 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 {@link WebappTemplateLoader} for more details. * * @param servletContext the {@link 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 a class relative to which we do the Class.getResource() call to load templates. * This is equivalent to {@code setTemplateLoader(new ClassTemplateLoader(clazz, pathPrefix))}, * so see {@link ClassTemplateLoader#ClassTemplateLoader(Class, String)} for more details. * * @see #setTemplateLoader(TemplateLoader) */ public void setClassForTemplateLoading(Class clazz, String pathPrefix) { setTemplateLoader(new ClassTemplateLoader(clazz, pathPrefix)); } /** * Sets the time in seconds that must elapse before checking whether there is a newer version of a template file * than the cached one. * *

Historical note: Despite what the API documentation said earlier, this method is not thread-safe. * While it works well on most hardware, it's not guaranteed that FreeMarker will see the update in all threads, * and theoretically it's also possible that it will see a value that's a binary mixture of the new and the old one. */ public void setTemplateUpdateDelay(int seconds) { cache.setDelay(1000L * seconds); } /** * Sets whether directives such as {@code if}, {@code else}, etc must be written as {@code #if}, {@code #else}, etc. * Defaults to {@code true}. * *

When this is {@code true}, * any tag not starting with <# or </# or <@ or </@ is considered as plain text * and will go to the output as is. Tag starting with <# or </# must * be valid FTL tag, or else the template is invalid (i.e. <#noSuchDirective> * is an error). * * @deprecated Only {@code true} (the default) value will be supported sometimes in the future. */ public void setStrictSyntaxMode(boolean b) { strictSyntax = b; } public void setObjectWrapper(ObjectWrapper objectWrapper) { ObjectWrapper prevObjectWrapper = getObjectWrapper(); super.setObjectWrapper(objectWrapper); objectWrapperWasSet = 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); } } } /** * The getter pair of {@link #setStrictSyntaxMode}. */ public boolean getStrictSyntaxMode() { return strictSyntax; } /** * Use {@link #Configuration(Version)} instead if possible; see the meaning of the parameter there. * If the default value of a setting depends on the {@code incompatibleImprovements} and 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} is greater than the current FreeMarker * version, or less than 2.3.0, or {@code null}. * * @since 2.3.20 */ public void setIncompatibleImprovements(Version incompatibleImprovements) { _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements); boolean hadLegacyTLOWDefaults = this.incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21; this.incompatibleImprovements = incompatibleImprovements; if (hadLegacyTLOWDefaults != incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) { if (!templateLoaderWasSet) { recreateTemplateCacheWith(getDefaultTemplateLoader(), cache.getCacheStorage()); } if (!objectWrapperWasSet) { // We use `super.` so that `objectWrapperWasSet` will not be set to `true`. super.setObjectWrapper(getDefaultObjectWrapper(incompatibleImprovements)); } } } /** * @see #setIncompatibleImprovements(Version) * @return Never {@code null}. * @since 2.3.20 */ public Version getIncompatibleImprovements() { return incompatibleImprovements; } /** * @deprecated Use {@link #Configuration(Version)}, or * as last chance, {@link #setIncompatibleImprovements(Version)} instead. */ public void setIncompatibleEnhancements(String version) { setIncompatibleImprovements(new Version(version)); } /** * @deprecated Use {@link #getIncompatibleImprovements()} instead. */ public String getIncompatibleEnhancements() { return incompatibleImprovements.toString(); } /** * @deprecated Use {@link #getIncompatibleImprovements()} instead. */ 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 */ public boolean getWhitespaceStripping() { return whitespaceStripping; } /** * Determines the syntax of the template files (angle bracket VS square bracket) * that has no {@code #ftl} in it. The {@code tagSyntax} * parameter must be one of: *

    *
  • {@link Configuration#AUTO_DETECT_TAG_SYNTAX}: * use the syntax of the first FreeMarker tag (can be anything, like #list, * #include, user defined, etc.) *
  • {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}: * use the angle bracket syntax (the normal syntax) *
  • {@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}: * use the square bracket syntax *
* *

In FreeMarker 2.3.x {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX} is the * default for better backward compatibility. Starting from 2.4.x {@link * Configuration#AUTO_DETECT_TAG_SYNTAX} is the default, so it's recommended to use * that even for 2.3.x. * *

This setting is ignored for the templates that have {@code ftl} directive in * it. For those templates the syntax used for the {@code ftl} directive determines * the syntax. */ public void setTagSyntax(int tagSyntax) { if (tagSyntax != AUTO_DETECT_TAG_SYNTAX && tagSyntax != SQUARE_BRACKET_TAG_SYNTAX && tagSyntax != ANGLE_BRACKET_TAG_SYNTAX) { throw new IllegalArgumentException("\"tag_syntax\" can only be set to one of these: " + "Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, " + "or Configuration.SQAUARE_BRACKET_SYNTAX"); } this.tagSyntax = tagSyntax; } /** * The getter pair of {@link #setTagSyntax(int)}. */ public int getTagSyntax() { return tagSyntax; } /** * Retrieves the template with the given name from the template cache, loading it into the cache first if it's * missing/staled. * *

This is a shorthand for {@link #getTemplate(String, Locale, String, boolean, boolean) * getTemplate(name, getLocale(), getEncoding(getLocale()), true, false)}; see more details there. * *

See {@link Configuration} for an example of basic usage. */ public Template getTemplate(String name) throws IOException { Locale loc = getLocale(); return getTemplate(name, loc, getEncoding(loc), true); } /** * Shorthand for {@link #getTemplate(String, Locale, String, boolean, boolean) * getTemplate(name, locale, getEncoding(locale), true, false)}. */ public Template getTemplate(String name, Locale locale) throws IOException { return getTemplate(name, locale, getEncoding(locale), true); } /** * Shorthand for {@link #getTemplate(String, Locale, String, boolean, boolean) * getTemplate(name, getLocale(), encoding, true, false)}. */ public Template getTemplate(String name, String encoding) throws IOException { return getTemplate(name, getLocale(), encoding, true); } /** * Shorthand for {@link #getTemplate(String, Locale, String, boolean, boolean) * getTemplate(name, locale, encoding, true, false)}. */ public Template getTemplate(String name, Locale locale, String encoding) throws IOException { return getTemplate(name, locale, encoding, true); } /** * Shorthand for {@link #getTemplate(String, Locale, String, boolean, boolean) * getTemplate(name, locale, encoding, parseAsFTL, false)}. */ public Template getTemplate(String name, Locale locale, String encoding, boolean parseAsFTL) throws IOException { return getTemplate(name, locale, encoding, parseAsFTL, false); } /** * Retrieves the template with the given name (and according the specified further parameters) from the template * cache, loading it into the cache first if it's missing/staled. * *

This method is thread-safe. * *

See {@link Configuration} for an example of basic usage. * * @param name The name or path of the template, which is not a real path, * but interpreted inside the current {@link TemplateLoader}. * Can't be {@code null}. The exact syntax of the name * is interpreted by the underlying {@link TemplateLoader}, but the * cache makes some assumptions. First, the name is expected to be * a hierarchical path, with path components separated by a slash * character (not with backslash!). The path (the name) given here must not begin with slash; * it's always interpreted relative to the "template root directory". * Then, the {@code ..} and {@code .} path meta-elements will be resolved. * For example, if the name is {@code a/../b/./c.ftl}, then it will be * simplified to {@code b/c.ftl}. The rules regarding this are the same as with conventional * UN*X paths. The path must not reach outside the template root directory, that is, * it can't be something like {@code "../templates/my.ftl"} (not even if this path * happens to be equivalent with {@code "/my.ftl"}). * Further, the path is allowed to contain at most * one path element whose name is {@code *} (asterisk). This path meta-element triggers the * acquisition mechanism. If the template is not found in * the location described by the concatenation of the path left to the * asterisk (called base path) and the part to the right of the asterisk * (called resource path), the cache will attempt to remove the rightmost * path component from the base path ("go up one directory") and concatenate * that with the resource path. The process is repeated until either a * template is found, or the base path is completely exhausted. * * @param locale The requested locale of the template. Can't be {@code null}. * Assuming you have specified {@code en_US} as the locale and * {@code myTemplate.ftl} as the name of the template, the cache will * first try to retrieve {@code myTemplate_en_US.html}, then * {@code myTemplate.en.ftl}, and finally {@code myTemplate.ftl}. * * @param encoding The charset used to interpret the template source code bytes. Can't be {@code null}. * * @param parseAsFTL If {@code true}, the loaded template is parsed and interpreted normally, * as a regular FreeMarker template. If {@code false}, the loaded template is * treated as a static text, so ${...}, {@code <#...>} etc. will not have special meaning * in it. * * @param ignoreMissing If {@code true}, the method won't throw {@link FileNotFoundException} 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 FileNotFoundException if the template could not be found. * @throws IOException if there was a problem loading the template. * @throws ParseException (extends IOException) if the template is syntactically bad. * * @since 2.3.21 */ public Template getTemplate(String name, Locale locale, String encoding, boolean parseAsFTL, boolean ignoreMissing) throws IOException { Template result = cache.getTemplate(name, locale, encoding, parseAsFTL); if (result == 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."; } else { msg = "Template " + StringUtil.jQuote(name) + " not found. " + "The quoted name was interpreted by this template loader: "; String tlDesc; try { tlDesc = tl.toString(); } catch (Throwable e) { tlDesc = tl.getClass().getName() + " object (toString failed)"; } msg += tlDesc + "."; if (!templateLoaderWasSet) { msg += " Note that the \"template_loader\" FreeMarker setting wasn't set, so it's on its " + "default value, which is most certainly not intended and the cause of this problem."; } } throw new FileNotFoundException(msg); } return result; } /** * Sets the charset used for decoding byte sequences to character sequences when * reading template files in a locale for which no explicit encoding * was specified via {@link #setEncoding(Locale, String)}. Note that by default there is no locale specified for * any locale, so the default encoding is always in effect. * *

Defaults to the default system encoding, which can change from one server to * another, so you should always set this setting. If you don't know what charset your should chose, * {@code "UTF-8"} is usually a good choice. * *

Note that individual templates may specify their own charset by starting with * <#ftl encoding="..."> * * @param encoding The name of the charset, such as {@code "UTF-8"} or {@code "ISO-8859-1"} */ public void setDefaultEncoding(String encoding) { defaultEncoding = encoding; } /** * 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 default system encoding. */ public String getDefaultEncoding() { return defaultEncoding; } /** * 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()}. */ public String getEncoding(Locale locale) { if (localeToCharsetMap.isEmpty()) { return defaultEncoding; } else { // Try for a full name match (may include country and variant) String charset = (String) localeToCharsetMap.get(locale.toString()); if (charset == null) { if (locale.getVariant().length() > 0) { Locale l = new Locale(locale.getLanguage(), locale.getCountry()); charset = (String) localeToCharsetMap.get(l.toString()); if (charset != null) { localeToCharsetMap.put(locale.toString(), charset); } } charset = (String) localeToCharsetMap.get(locale.getLanguage()); if (charset != null) { localeToCharsetMap.put(locale.toString(), charset); } } return charset != null ? charset : defaultEncoding; } } /** * Sets the character set encoding to use for templates of * a given locale. If there is no explicit encoding set for some * locale, then the default encoding will be used, what you can * set with {@link #setDefaultEncoding}. * * @see #clearEncodingMap * @see #loadBuiltInEncodingMap */ public void setEncoding(Locale locale, String encoding) { localeToCharsetMap.put(locale.toString(), encoding); } /** * Adds a shared variable to the configuration. * Shared sharedVariables are sharedVariables that are visible * as top-level sharedVariables for all templates which use this * configuration, if the data model does not contain a * variable with the same name. * *

Never use TemplateModel implementation that is not thread-safe for shared sharedVariables, * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites. * *

This method is not thread safe; use it with the same restrictions as those that modify setting values. * * @param name the name used to access the data object from your template. * If a shared variable with this name already exists, it will replace * that. * * @see #setAllSharedVariables * @see #setSharedVariable(String,Object) */ public void setSharedVariable(String name, TemplateModel tm) { Object replaced = sharedVariables.put(name, tm); if (replaced != null && rewrappableSharedVariables != null) { rewrappableSharedVariables.remove(name); } } /** * Returns the set containing the names of all defined shared sharedVariables. * The method returns a new Set object on each call that is completely * disconnected from the Configuration. That is, modifying the set will have * no effect on the Configuration object. */ public Set getSharedVariableNames() { return new HashSet(sharedVariables.keySet()); } /** * Adds shared variable to the configuration; It uses {@link Configurable#getObjectWrapper()} to wrap the * {@code value}, so it's important that the object wrapper is set before this. * *

This method is not thread safe; use it with the same restrictions as those that modify setting values. * *

The added value should be thread safe, if you are running templates from multiple threads with this * configuration. * * @throws TemplateModelException If some of the variables couldn't be wrapped via {@link #getObjectWrapper()}. * * @see #setSharedVaribles(Map) * @see #setSharedVariable(String,TemplateModel) * @see #setAllSharedVariables(TemplateHashModelEx) */ public void setSharedVariable(String name, Object value) throws TemplateModelException { setSharedVariable(name, getObjectWrapper().wrap(value)); } /** * Replaces all shared variables (removes all previously added ones). * *

The values in the map can be {@link TemplateModel}-s or plain Java objects which will be immediately converted * to {@link TemplateModel} with the {@link ObjectWrapper} returned by {@link #getObjectWrapper()}. If * {@link #setObjectWrapper(ObjectWrapper)} is called later, this conversion will be re-applied. Thus, ignoring some * extra resource usage, it doesn't mater if in what order are {@link #setObjectWrapper(ObjectWrapper)} and * {@link #setSharedVaribles(Map)} called. This is essential when you don't have control over the order in which * the setters are called. * *

The values in the map must be thread safe, if you are running templates from multiple threads with * this configuration. This means that both the plain Java object and the {@link TemplateModel}-s created from them * by the {@link ObjectWrapper} must be thread safe. (The standard {@link ObjectWrapper}-s of FreeMarker create * thread safe {@link TemplateModel}-s.) The {@link Map} itself need not be thread-safe. * *

This setter method has no getter pair because of the tricky relation ship with * {@link #setSharedVariable(String, Object)}. * * @throws TemplateModelException If some of the variables couldn't be wrapped via {@link #getObjectWrapper()}. * * @since 2.3.21 */ public void setSharedVaribles(Map/**/ map) throws TemplateModelException { rewrappableSharedVariables = new HashMap(map); sharedVariables.clear(); setSharedVariablesFromRewrappableSharedVariables(); } private void setSharedVariablesFromRewrappableSharedVariables() throws TemplateModelException { if (rewrappableSharedVariables == null) return; for (Iterator it = rewrappableSharedVariables.entrySet().iterator(); it.hasNext();) { Map.Entry/**/ ent = (Entry) it.next(); String name = (String) ent.getKey(); Object value = ent.getValue(); TemplateModel valueAsTM; if (value instanceof TemplateModel) { valueAsTM = (TemplateModel) value; } else { valueAsTM = getObjectWrapper().wrap(value); } sharedVariables.put(name, valueAsTM); } } /** * Adds all object in the hash as shared variable to the configuration; it's like doing several * {@link #setSharedVariable(String, Object)} calls, one for each hash entry. It doesn't remove the already added * shared variable before doing this. * *

Never use TemplateModel implementation that is not thread-safe for shared shared variable values, * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites. * *

This method is not thread safe; use it with the same restrictions as those that modify setting values. * * @param hash a hash model whose objects will be copied to the * configuration with same names as they are given in the hash. * If a shared variable with these names already exist, it will be replaced * with those from the map. * * @see #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 sharedVariables are sharedVariables 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 sharedVariables * 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 sharedVariables, except the predefined ones (compress, html_escape, etc.). */ public void clearSharedVariables() { sharedVariables.clear(); loadBuiltInSharedVariables(); } /** * Removes all entries from the template cache, thus forcing reloading of templates * on subsequent getTemplate calls. * *

This method is thread-safe and can be called while the engine processes templates. */ public void clearTemplateCache() { cache.clear(); } /** * Equivalent to removeTemplateFromCache(name, thisCfg.getLocale(), thisCfg.getEncoding(thisCfg.getLocale()), true). * @since 2.3.19 */ public void removeTemplateFromCache(String name) throws IOException { Locale loc = getLocale(); removeTemplateFromCache(name, loc, getEncoding(loc), true); } /** * Equivalent to removeTemplateFromCache(name, locale, thisCfg.getEncoding(locale), true). * @since 2.3.19 */ public void removeTemplateFromCache(String name, Locale locale) throws IOException { removeTemplateFromCache(name, locale, getEncoding(locale), true); } /** * Equivalent to removeTemplateFromCache(name, thisCfg.getLocale(), encoding, true). * @since 2.3.19 */ public void removeTemplateFromCache(String name, String encoding) throws IOException { removeTemplateFromCache(name, getLocale(), encoding, true); } /** * Equivalent to removeTemplateFromCache(name, locale, encoding, true). * @since 2.3.19 */ public void removeTemplateFromCache(String name, Locale locale, String encoding) throws IOException { removeTemplateFromCache(name, locale, encoding, true); } /** * Removes a template from the template cache, hence forcing the re-loading * of it when it's next time requested. This is to give the application * finer control over cache updating than {@link #setTemplateUpdateDelay(int)} * alone does. * *

For the meaning of the parameters, see * {@link #getTemplate(String, Locale, String, boolean)}. * *

This method is thread-safe and can be called while the engine processes templates. * * @since 2.3.19 */ public void removeTemplateFromCache( String name, Locale locale, String encoding, boolean parse) throws IOException { cache.removeTemplate(name, locale, encoding, parse); } /** * The getter pair of {@link #setLocalizedLookup(boolean)}. * *

This method is thread-safe and can be called while the engine works. */ public boolean getLocalizedLookup() { return cache.getLocalizedLookup(); } /** * Enables/disables localized template lookup. Enabled by default. * *

Localized lookup works like this: Let's say your locale setting is "en_AU", and you call * {@link Configuration#getTemplate(String) cfg.getTemplate("foo.ftl")}. Then FreeMarker will look for the template * under names, stopping at the first that exists: {@code "foo_en_AU.ftl"}, {@code "foo_en.ftl"}, {@code "foo.ftl"}. * *

Historical note: Despite what the API documentation said earlier, this method is not thread-safe. * While setting it can't cause any serious problems, and in fact it works well on most hardware, it's not * guaranteed that FreeMarker will see the update in all threads. */ public void setLocalizedLookup(boolean localizedLookup) { this.localizedLookup = localizedLookup; cache.setLocalizedLookup(localizedLookup); } 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.equals(name)) { setDefaultEncoding(value); } else if (LOCALIZED_LOOKUP_KEY.equals(name)) { setLocalizedLookup(StringUtil.getYesNo(value)); } else if (STRICT_SYNTAX_KEY.equals(name)) { setStrictSyntaxMode(StringUtil.getYesNo(value)); } else if (WHITESPACE_STRIPPING_KEY.equals(name)) { setWhitespaceStripping(StringUtil.getYesNo(value)); } else if (CACHE_STORAGE_KEY.equals(name)) { 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, _SettingEvaluationEnvironment.getCurrent())); } } else if (TEMPLATE_UPDATE_DELAY_KEY.equals(name)) { setTemplateUpdateDelay(Integer.parseInt(value)); } else if (AUTO_INCLUDE_KEY.equals(name)) { setAutoIncludes(parseAsList(value)); } else if (AUTO_IMPORT_KEY.equals(name)) { setAutoImports(parseAsImportList(value)); } else if (TAG_SYNTAX_KEY.equals(name)) { if ("auto_detect".equals(value)) { setTagSyntax(AUTO_DETECT_TAG_SYNTAX); } else if ("angle_bracket".equals(value)) { setTagSyntax(ANGLE_BRACKET_TAG_SYNTAX); } else if ("square_bracket".equals(value)) { setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX); } else { throw invalidSettingValueException(name, value); } } else if (INCOMPATIBLE_IMPROVEMENTS.equals(name)) { setIncompatibleImprovements(new Version(value)); } else if (INCOMPATIBLE_ENHANCEMENTS.equals(name)) { setIncompatibleEnhancements(value); } else if (TEMPLATE_LOADER_KEY.equals(name)) { setTemplateLoader((TemplateLoader) _ObjectBuilderSettingEvaluator.eval( value, TemplateLoader.class, _SettingEvaluationEnvironment.getCurrent())); } else { unknown = true; } } catch(Exception e) { throw settingValueAssignmentException(name, value, e); } if (unknown) { super.setSetting(name, value); } } protected String getCorrectedNameForUnknownSetting(String name) { if ("encoding".equals(name) || "charset".equals(name) || "default_charset".equals(name)) { return DEFAULT_ENCODING_KEY; } return super.getCorrectedNameForUnknownSetting(name); } /** * Adds an invisible #import templateName as namespaceVarName at the beginning of all * templates. The order of the imports will be the same as the order in which they were added with this method. */ public void addAutoImport(String namespaceVarName, String templateName) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { autoImports.remove(namespaceVarName); autoImports.add(namespaceVarName); autoImportNsToTmpMap.put(namespaceVarName, templateName); } } /** * Removes an auto-import; see {@link #addAutoImport(String, String)}. Does nothing if the auto-import doesn't * exist. */ public void removeAutoImport(String namespaceVarName) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { autoImports.remove(namespaceVarName); autoImportNsToTmpMap.remove(namespaceVarName); } } /** * Removes all auto-imports, then calls {@link #addAutoImport(String, String)} for each {@link Map}-entry (the entry * key is the {@code namespaceVarName}). The order of the auto-imports will be the same as {@link Map#keySet()} * returns the keys, thus, it's not the best idea to use a {@link HashMap} (although the order of imports doesn't * mater for properly designed libraries). */ public void setAutoImports(Map map) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { autoImports = new ArrayList(map.keySet()); if (map instanceof HashMap) { autoImportNsToTmpMap = (Map) ((HashMap) map).clone(); } else if (map instanceof SortedMap) { autoImportNsToTmpMap = new TreeMap(map); } else { autoImportNsToTmpMap = new HashMap(map); } } } protected void doAutoImportsAndIncludes(Environment env) throws TemplateException, IOException { for (int i=0; i#include templateName as namespaceVarName at the beginning of all * templates. The order of the inclusions will be the same as the order in which they were added with this method. */ public void addAutoInclude(String templateName) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { autoIncludes.remove(templateName); autoIncludes.add(templateName); } } /** * Removes all auto-includes, then calls {@link #addAutoInclude(String)} for each {@link List} items. */ public void setAutoIncludes(List templateNames) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { autoIncludes.clear(); Iterator it = templateNames.iterator(); while (it.hasNext()) { Object o = it.next(); if (!(o instanceof String)) { throw new IllegalArgumentException("List items must be String-s."); } autoIncludes.add(o); } } } /** * Removes a template from the auto-include list; see {@link #addAutoInclude(String)}. Does nothing if the template * is not there. */ public void removeAutoInclude(String templateName) { // "synchronized" is removed from the API as it's not safe to set anything after publishing the Configuration synchronized (this) { autoIncludes.remove(templateName); } } /** * Returns FreeMarker version number string. * * @deprecated Use {@link #getVersion()} instead. */ public static String getVersionNumber() { return version.toString(); } /** * Returns the FreeMarker version information, most importantly the major.minor.micro version numbers. * * On FreeMarker version numbering rules: *

    *
  • For final/stable releases the version number is like major.minor.micro, like 2.3.19. (Historically, * when micro was 0 the version strings was like major.minor instead of the proper major.minor.0, but that's * not like that anymore.) *
  • When only the micro version is increased, compatibility with previous versions with the same * major.minor is kept. Thus freemarker.jar can be replaced in an existing application without * breaking it.
  • *
  • For non-final/unstable versions (that almost nobody uses), the format is: *
      *
    • Starting from 2.3.20: major.minor.micro-extraInfo, like * 2.3.20-nightly_20130506T123456Z, 2.4.0-RC01. The major.minor.micro * always indicates the target we move towards, so 2.3.20-nightly or 2.3.20-M01 is * after 2.3.19 and will eventually become to 2.3.20. "PRE", "M" and "RC" (uppercase!) means * "preview", "milestone" and "release candidate" respectively, and is always followed by a 2 digit * 0-padded counter, like M03 is the 3rd milestone release of a given major.minor.micro.
    • *
    • Before 2.3.20: The extraInfo wasn't preceded by a "-". * Instead of "nightly" there was "mod", where the major.minor.micro part has indicated where * are we coming from, so 2.3.19mod (read as: 2.3.19 modified) was after 2.3.19 but before 2.3.20. * Also, "pre" and "rc" was lowercase, and was followd by a number without 0-padding.
    • *
    *
* * @since 2.3.20 */ public static Version getVersion() { return version; } /** * Returns the default object wrapper for a given "incompatible_improvements" version. * * @see #setIncompatibleImprovements(Version) * * @since 2.3.21 */ public static ObjectWrapper getDefaultObjectWrapper(Version incompatibleImprovements) { if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) { return ObjectWrapper.DEFAULT_WRAPPER; } else { return new DefaultObjectWrapperBuilder(incompatibleImprovements).build(); } } /** * 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. * * @return {@link Set} of {@link String}-s. * * @since 2.3.20 */ public Set getSupportedBuiltInNames() { return _CoreAPI.getSupportedBuiltInNames(); } /** * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like * <#directiveName ...>. * * @return {@link Set} of {@link String}-s. * * @since 2.3.21 */ public Set getSupportedBuiltInDirectiveNames() { return _CoreAPI.BUILT_IN_DIRECTIVE_NAMES; } 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy