📄 templatecache.java
字号:
/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
package freemarker.cache;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import freemarker.core.Environment;
import freemarker.log.Logger;
import freemarker.template.Configuration;
import freemarker.template.Template;
/**
* A class that performs caching and on-demand loading of the templates.
* The actual loading is delegated to a {@link TemplateLoader}. Also,
* various constructors provide you with convenient caches with predefined
* behavior. Typically you don't use this class directly - in normal
* circumstances it is hidden behind a {@link Configuration}.
* @author Attila Szegedi, szegedia at freemail dot hu
* @version $Id: TemplateCache.java,v 1.62.2.1 2006/04/19 14:29:59 szegedia Exp $
*/
public class TemplateCache
{
private static final String ASTERISKSTR = "*";
private static final String LOCALE_SEPARATOR = "_";
private static final char ASTERISK = '*';
private static final String CURRENT_DIR_PATH_PREFIX = "./";
private static final String CURRENT_DIR_PATH = "/./";
private static final String PARENT_DIR_PATH_PREFIX = "../";
private static final String PARENT_DIR_PATH = "/../";
private static final char SLASH = '/';
private static final Logger logger = Logger.getLogger("freemarker.cache");
private final TemplateLoader mainLoader;
/** DD: [noFallback]
,
fallbackTemplateLoader = new ClassTemplateLoader(TemplateCache.class, "/");
*/
/** Here we keep our cached templates */
private final CacheStorage storage;
/** The default refresh delay in milliseconds. */
private long delay = 5000;
/** Specifies if localized template lookup is enabled or not */
private boolean localizedLookup = true;
private Configuration config;
/**
* Returns a template cache that will first try to load a template from
* the file system relative to the current user directory (i.e. the value
* of the system property <code>user.dir</code>), then from the classpath.
* This default template cache suits many applications.
*/
public TemplateCache()
{
this(createDefaultTemplateLoader());
}
private static TemplateLoader createDefaultTemplateLoader() {
try {
return new FileTemplateLoader();
} catch(Exception e) {
logger.warn("Could not create a file template loader for current directory", e);
return null;
}
}
/**
* Creates a new template cache with a custom template loader that is used
* to load the templates.
* @param loader the template loader to use.
*/
public TemplateCache(TemplateLoader loader)
{
this(loader, new MruCacheStorage(0, Integer.MAX_VALUE));
}
/**
* Creates a new template cache with a custom template loader that is used
* to load the templates.
* @param loader the template loader to use.
*/
public TemplateCache(TemplateLoader loader, CacheStorage storage)
{
this.mainLoader = loader;
this.storage = storage;
if(storage == null)
{
throw new IllegalArgumentException("storage == null");
}
}
/**
* Sets the configuration object to which this cache belongs. This
* method is called by the configuration itself to establish the
* relation, and should not be called by users.
*/
public void setConfiguration(Configuration config)
{
this.config = config;
clear();
}
public TemplateLoader getTemplateLoader()
{
return mainLoader;
}
public CacheStorage getCacheStorage()
{
return storage;
}
/**
* Loads a template with the given name, in the specified locale and
* using the specified character encoding.
*
* @param name the name of the template. Can't be 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) must <em>not</em> begin with slash;
* the path is always relative to the "template root directory".
* Then, the <tt>..</tt> and <tt>.</tt> path metaelements will be resolved.
* For example, if the name is <tt>a/../b/./c.ftl</tt>, then it will be
* simplified to <tt>b/c.ftl</tt>. The rules regarding this are 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 <tt>"../templates/my.ftl"</tt> (not even if the pervious path
* happens to be equivalent with <tt>"/my.ftl"</tt>).
* Further, the path is allowed to contain at most
* one path element whose name is <tt>*</tt> (asterisk). This path metaelement 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. Can't be null.
* Assuming you have specified <code>en_US</code> as the locale and
* <code>myTemplate.html</code> as the name of the template, the cache will
* first try to retrieve <code>myTemplate_en_US.html</code>, then
* <code>myTemplate.html_en.html</code>, and finally
* <code>myTemplate.html</code>.
*
* @param encoding the character encoding used to interpret the template
* source bytes. Can't be null.
*
* @param parse if true, the loaded template is parsed and interpreted
* as a regular FreeMarker template. If false, the loaded template is
* treated as an unparsed block of text.
*
* @return the loaded template, or null if the template is not found.
*/
public Template getTemplate(String name, Locale locale, String encoding, boolean parse)
throws IOException
{
if (name == null) {
throw new IllegalArgumentException("Argument \"name\" can't be null");
}
if (locale == null) {
throw new IllegalArgumentException("Argument \"locale\" can't be null");
}
if (encoding == null) {
throw new IllegalArgumentException("Argument \"encoding\" can't be null");
}
name = normalizeName(name);
if(name == null) {
return null;
}
Template result = null;
if (mainLoader != null) {
result = getTemplate(mainLoader, name, locale, encoding, parse);
}
/** DD: [noFallback]
if (result == null && name.toLowerCase().endsWith(".ftl")) {
result = getTemplate(fallbackTemplateLoader, name, locale, encoding, parse);
}
*/
return result;
}
private Template getTemplate(TemplateLoader loader, String name, Locale locale, String encoding, boolean parse)
throws IOException
{
boolean debug = logger.isDebugEnabled();
String debugName = debug ? name + "[" + locale + "," + encoding + (parse ? ",parsed] " : ",unparsed] ") : null;
TemplateKey tk = new TemplateKey(name, locale, encoding, parse);
synchronized (storage)
{
CachedTemplate cachedTemplate = (CachedTemplate)storage.get(tk);
long now = System.currentTimeMillis();
long lastModified = -1L;
Object newlyFoundSource = null;
try
{
if (cachedTemplate != null)
{
// If we're within the refresh delay, return the cached copy
if (now - cachedTemplate.lastChecked < delay)
{
if(debug)
{
logger.debug(debugName + "cached copy not yet stale; using cached.");
}
return cachedTemplate.template;
}
// Else, update the last-checked flag
cachedTemplate.lastChecked = now;
// Find the template source
newlyFoundSource = findTemplateSource(name, locale);
// Template source was removed
if (newlyFoundSource == null)
{
if(debug)
{
logger.debug(debugName + "no source found (removing from cache if it was cached).");
}
storage.remove(tk);
return null;
}
// If the source didn't change and its last modified date
// also didn't change, return the cached version.
lastModified = loader.getLastModified(newlyFoundSource);
boolean lastModifiedNotChanged = lastModified == cachedTemplate.lastModified;
boolean sourceEquals = newlyFoundSource.equals(cachedTemplate.source);
if(lastModifiedNotChanged && sourceEquals)
{
if(debug)
{
logger.debug(debugName + "using cached since " +
newlyFoundSource + " didn't change.");
}
cachedTemplate.lastChecked = now;
return cachedTemplate.template;
}
else
{
if(debug && !sourceEquals)
{
logger.debug("Updating source, info for cause: " +
"sourceEquals=" + sourceEquals +
", newlyFoundSource=" + newlyFoundSource +
", cachedTemplate.source=" + cachedTemplate.source);
}
if(debug && !lastModifiedNotChanged)
{
logger.debug("Updating source, info for cause: " +
"lastModifiedNotChanged=" + lastModifiedNotChanged +
", cache lastModified=" + cachedTemplate.lastModified +
" != file lastModified=" + lastModified);
}
// Update the source
cachedTemplate.source = newlyFoundSource;
}
}
else
{
if(debug)
{
logger.debug("Could not find template in cache, "
+ "creating new one; id=[" +
tk.name + "[" + tk.locale + "," + tk.encoding +
(tk.parse ? ",parsed] " : ",unparsed] ") + "]");
}
// Construct a new CachedTemplate entry. Note we set the
// cachedTemplate.lastModified to Long.MIN_VALUE. This is
// a flag that signs it has to be explicitly queried later on.
newlyFoundSource = findTemplateSource(name, locale);
if (newlyFoundSource == null)
{
return null;
}
cachedTemplate = new CachedTemplate();
cachedTemplate.source = newlyFoundSource;
cachedTemplate.lastChecked = now;
cachedTemplate.lastModified = lastModified = Long.MIN_VALUE;
storage.put(tk, cachedTemplate);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -