Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * 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.Reader;
 import java.util.List;
 
A class that performs caching and on-demand loading of the templates. The actual loading is delegated to a 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 freemarker.template.Configuration. However, the CacheStorage it uses can be set with freemarker.template.Configuration.setCacheStorage(freemarker.cache.CacheStorage)

Author(s):
Attila Szegedi
 
 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;
    private final boolean isStorageConcurrent;
    
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 user.dir), 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) {
            .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.

Parameters:
loader the template loader to use.
    public TemplateCache(TemplateLoader loader)
    {
        this(loadernew SoftCacheStorage());
    }
    
    
Creates a new template cache with a custom template loader that is used to load the templates.

Parameters:
loader the template loader to use.
    public TemplateCache(TemplateLoader loaderCacheStorage storage)
    {
        this. = loader;
        this. = storage;
        if(storage == null
        {
            throw new IllegalArgumentException("storage == null");
        }
         = storage instanceof ConcurrentCacheStorage &&
            ((ConcurrentCacheStorage)storage).isConcurrent();
    }

    
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;
        clear();
    }
    {
        return ;
    }
    
    public CacheStorage getCacheStorage()
    {
        return ;
    }
    
    
Loads a template with the given name, in the specified locale and using the specified character encoding.

Parameters:
name the name of the template. Can't be null. The exact syntax of the name is interpreted by the underlying 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 not begin with slash; the path is always relative to the "template root directory". Then, the .. and . path metaelements will be resolved. For example, if the name is a/../b/./c.ftl, then it will be simplified to b/c.ftl. 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 "../templates/my.ftl" (not even if the pervious path happens to be equivalent with "/my.ftl"). Further, the path is allowed to contain at most one path element whose name is * (asterisk). This path metaelement 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.
locale the requested locale of the template. Can't be null. Assuming you have specified en_US as the locale and myTemplate.html as the name of the template, the cache will first try to retrieve myTemplate_en_US.html, then myTemplate.html_en.html, and finally myTemplate.html.
encoding the character encoding used to interpret the template source bytes. Can't be null.
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.
Returns:
the loaded template, or null if the template is not found.
    public Template getTemplate(String nameLocale localeString encodingboolean 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 ( != null) {
            result = getTemplate(namelocaleencodingparse);
        }
        
DD: [noFallback] if (result == null && name.toLowerCase().endsWith(".ftl")) { result = getTemplate(fallbackTemplateLoader, name, locale, encoding, parse); }
        return result;
    }    
    
    private Template getTemplate(TemplateLoader loaderString nameLocale localeString encodingboolean parse)
    throws IOException
    {
        boolean debug = .isDebugEnabled();
        String debugName = debug
                ? StringUtil.jQuoteNoXSS(name) + "["
                        + StringUtil.jQuoteNoXSS(locale) + "," + encoding
                        + (parse ? ",parsed] " : ",unparsed] ")
                : null;
        TemplateKey tk = new TemplateKey(namelocaleencodingparse);
        
        CachedTemplate cachedTemplate;
        if() {
            cachedTemplate = (CachedTemplate).get(tk);
        }
        else {
            synchronized() {
                cachedTemplate = (CachedTemplate).get(tk);
            }
        }
        long now = System.currentTimeMillis();
        long lastModified = -1L;
        Object newlyFoundSource = null;
        boolean rethrown = false;
        try {
            if (cachedTemplate != null) {
                // If we're within the refresh delay, return the cached copy
                if (now - cachedTemplate.lastChecked < ) {
                    if(debug) {
                        .debug(debugName + "cached copy not yet stale; using cached.");
                    }
                    // Can be null, indicating a cached negative lookup
                    Object t = cachedTemplate.templateOrException;
                    if(t instanceof Template || t == null) {
                        return (Template)t;
                    }
                    else if(t instanceof RuntimeException) {
                        throwLoadFailedException((RuntimeException)t);
                    }
                    else if(t instanceof IOException) {
                        rethrown = true;
                        throwLoadFailedException((IOException)t);
                    }
                    throw new RuntimeException("t is " + t.getClass().getName());
                }
                // Clone as the instance bound to the map should be treated as
                // immutable to ensure proper concurrent semantics
                cachedTemplate = cachedTemplate.cloneCachedTemplate();
                // Update the last-checked flag
                cachedTemplate.lastChecked = now;
                // Find the template source
                newlyFoundSource = findTemplateSource(namelocale);
                // Template source was removed
                if (newlyFoundSource == null) {
                    if(debug) {
                        .debug(debugName + "no source found.");
                    } 
                    storeNegativeLookup(tkcachedTemplatenull);
                    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) {
                        .debug(debugName + "using cached since " + 
                                newlyFoundSource + " didn't change.");
                    }
                    storeCached(tkcachedTemplate);
                    return (Template)cachedTemplate.templateOrException;
                }
                else {
                    if(debug && !sourceEquals) {
                        .debug("Updating source, info for cause: " + 
                            "sourceEquals=" + sourceEquals + 
                            ", newlyFoundSource=" + StringUtil.jQuoteNoXSS(newlyFoundSource) + 
                            ", cachedTemplate.source=" + StringUtil.jQuoteNoXSS(cachedTemplate.source));
                    }
                    if(debug && !lastModifiedNotChanged) {
                        .debug("Updating source, info for cause: " + 
                            "lastModifiedNotChanged=" + lastModifiedNotChanged + 
                            ", cache lastModified=" + cachedTemplate.lastModified + 
                            " != file lastModified=" + lastModified);
                    }
                    // Update the source
                    cachedTemplate.source = newlyFoundSource;
                }
            }
            else {
                if(debug) {
                    .debug("Could not find template in cache, " +
                        "creating new one; id=[" +
                        StringUtil.jQuoteNoXSS(tk.name) + "[" +
                        StringUtil.jQuoteNoXSS(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.
                cachedTemplate = new CachedTemplate();
                cachedTemplate.lastChecked = now;
                newlyFoundSource = findTemplateSource(namelocale);
                if (newlyFoundSource == null) {
                    storeNegativeLookup(tkcachedTemplatenull);
                    return null;
                }
                cachedTemplate.source = newlyFoundSource;
                cachedTemplate.lastModified = lastModified = .;
            }
            if(debug) {
                .debug("Compiling FreeMarker template " + 
                    debugName + " from " + StringUtil.jQuoteNoXSS(newlyFoundSource));
            }
            // If we get here, then we need to (re)load the template
            Object source = cachedTemplate.source;
            Template t = loadTemplate(loadernamelocaleencodingparsesource);
            cachedTemplate.templateOrException = t;
            cachedTemplate.lastModified =
                lastModified == .
                    ? loader.getLastModified(source)
                    : lastModified;
            storeCached(tkcachedTemplate);
            return t;
        }
        catch(RuntimeException e) {
            storeNegativeLookup(tkcachedTemplatee);
            throw e;
        }
        catch(IOException e) {
            if(!rethrown) {
                storeNegativeLookup(tkcachedTemplatee);
            }
            throw e;
        }
        finally {
            if(newlyFoundSource != null) {
                loader.closeTemplateSource(newlyFoundSource);
            }
        }
    }
    private static final Method INIT_CAUSE = getInitCauseMethod();
    
    private static final Method getInitCauseMethod() {
        try {
            return Throwable.class.getMethod("initCause"new Class[] { Throwable.class });
        } catch(NoSuchMethodException e) {
            return null;
        }
    }
    
    private void throwLoadFailedException(Exception ethrows IOException {
        IOException ioe;
        if( != null) {
            ioe = new IOException("There was an error loading the " +
                "template on an earlier attempt; it is attached as a cause");
            try {
                .invoke(ioenew Object[] { e });
            } catch(RuntimeException ex) {
                throw ex;
            } catch(Exception ex) {
                throw new UndeclaredThrowableException(ex);
            }
        }
        else {
            ioe = new IOException("There was an error loading the " +
            "template on an earlier attempt: " + e.getClass().getName() + 
            ": " + e.getMessage());
        }
        throw ioe;
    }
    private void storeNegativeLookup(TemplateKey tk
            CachedTemplate cachedTemplateException e) {
        cachedTemplate.templateOrException = e;
        cachedTemplate.source = null;
        cachedTemplate.lastModified = 0L;
        storeCached(tkcachedTemplate);
    }
    private void storeCached(TemplateKey tkCachedTemplate cachedTemplate) {
        if() {
            .put(tkcachedTemplate);
        }
        else {
            synchronized() {
                .put(tkcachedTemplate);
            }
        }
    }
    private Template loadTemplate(TemplateLoader loaderString nameLocale localeString encoding,
                                   boolean parseObject source)
    throws IOException
    {
        Template template;
        Reader reader = loader.getReader(sourceencoding);
        try
        {
            if(parse)
            {
                try {
                    template = new Template(namereaderencoding);
                }
                catch (Template.WrongEncodingException wee) {
                    encoding = wee.specifiedEncoding;
                    reader = loader.getReader(sourceencoding);
                    template = new Template(namereaderencoding);
                }
                template.setLocale(locale);
            }
            else
            {
                // Read the contents into a StringWriter, then construct a single-textblock
                // template from it.
                StringWriter sw = new StringWriter();
                char[] buf = new char[4096];
                for(;;)
                {
                    int charsRead = reader.read(buf);
                    if (charsRead > 0)
                    {
                        sw.write(buf, 0, charsRead);
                    }
                    else if(charsRead == -1)
                    {
                        break;
                    }
                }
                template = Template.getPlainTextTemplate(namesw.toString(), );
                template.setLocale(locale);
            }
            template.setEncoding(encoding);
        }
        finally
        {
            reader.close();
        }
        return template;
    }

    
Gets the delay in milliseconds between checking for newer versions of a template source.

Returns:
the current value of the delay
    public synchronized long getDelay()
    {
        return ;
    }

    
Sets the delay in milliseconds between checking for newer versions of a template sources.

Parameters:
delay the new value of the delay
    public synchronized void setDelay(long delay)
    {
        this. = delay;
    }

    
Returns if localized template lookup is enabled or not.
    public synchronized boolean getLocalizedLookup()
    {
        return ;
    }

    
Setis if localized template lookup is enabled or not.
    public synchronized void setLocalizedLookup(boolean localizedLookup)
    {
        this. = localizedLookup;
    }

    
Removes all entries from the cache, forcing reloading of templates on subsequent getTemplate(java.lang.String,java.util.Locale,java.lang.String,boolean) calls. If the configured template loader is stateful, then its StatefulTemplateLoader.resetState() method is invoked as well.
    public void clear()
    {
        synchronized () {
            .clear();
            if( instanceof StatefulTemplateLoader) {
                ((StatefulTemplateLoader)).resetState();
            }
        }
    }
    public static String getFullTemplatePath(Environment envString parentTemplateDirString templateNameString)
    {
        if (!env.isClassicCompatible()) {
            if (templateNameString.indexOf("://") >0) {
                ;
            }
            else if (templateNameString.length() > 0 && templateNameString.charAt(0) == '/')  {
                int protIndex = parentTemplateDir.indexOf("://");
                if (protIndex >0) {
                    templateNameString = parentTemplateDir.substring(0, protIndex + 2) + templateNameString;
                } else {
                    templateNameString = templateNameString.substring(1);
                }
            }
            else {
                templateNameString = parentTemplateDir + templateNameString;
            }
        }
        return templateNameString;
    }
    private Object findTemplateSource(String nameLocale locale)
    throws
        IOException
    {
        if () {
            int lastDot = name.lastIndexOf('.');
            String prefix = lastDot == -1 ? name : name.substring(0, lastDot);
            String suffix = lastDot == -1 ? "" : name.substring(lastDot);
            String localeName =  + locale.toString();
            StringBuffer buf = new StringBuffer(name.length() + localeName.length());
            buf.append(prefix);
            for (;;)
            {
                buf.setLength(prefix.length());
                String path = buf.append(localeName).append(suffix).toString();
                Object templateSource = acquireTemplateSource(path);
                if (templateSource != null)
                {
                    return templateSource;
                }
                int lastUnderscore = localeName.lastIndexOf('_');
                if (lastUnderscore == -1)
                    break;
                localeName = localeName.substring(0, lastUnderscore);
            }
            return null;
        }
        else
        {
            return acquireTemplateSource(name);
        }
    }
    private Object acquireTemplateSource(String paththrows IOException
    {
        int asterisk = path.indexOf();
        // Shortcut in case there is no acquisition
        if(asterisk == -1)
        {
            return .findTemplateSource(path);
        }
        StringTokenizer tok = new StringTokenizer(path"/");
        int lastAsterisk = -1;
        List tokpath = new ArrayList();
        while(tok.hasMoreTokens())
        {
            String pathToken = tok.nextToken();
            if(pathToken.equals())
            {
                if(lastAsterisk != -1)
                {
                    tokpath.remove(lastAsterisk);
                }
                lastAsterisk = tokpath.size();
            }
            tokpath.add(pathToken);
        }
        String basePath = concatPath(tokpath, 0, lastAsterisk);
        String resourcePath = concatPath(tokpathlastAsterisk + 1, tokpath.size());
        if(resourcePath.endsWith("/"))
        {
            resourcePath = resourcePath.substring(0, resourcePath.length() - 1);
        }
        StringBuffer buf = new StringBuffer(path.length()).append(basePath);
        int l = basePath.length();
        boolean debug = .isDebugEnabled();
        for(;;)
        {
            String fullPath = buf.append(resourcePath).toString();
            if(debug)
            {
                .debug("Trying to find template source "
                        + StringUtil.jQuoteNoXSS(fullPath));
            }
            Object templateSource = .findTemplateSource(fullPath);
            if(templateSource != null)
            {
                return templateSource;
            }
            if(l == 0)
            {
                return null;
            }
            l = basePath.lastIndexOf(l - 2) + 1;
            buf.setLength(l);
        }
    }
    private String concatPath(List pathint fromint to)
    {
        StringBuffer buf = new StringBuffer((to - from) * 16);
        for(int i = fromi < to; ++i)
        {
            buf.append(path.get(i)).append('/');
        }
        return buf.toString();
    }
    
    private static String normalizeName(String name) {
        int cppIdx = name.indexOf("://"); 
        if (cppIdx > 0) {
            int zIdx = name.indexOf(0);
            if (zIdx != -1 && zIdx < cppIdxreturn null;
            return name;
        }
        for(;;) {
            int parentDirPathLoc = name.indexOf();
            if(parentDirPathLoc == 0) {
                // If it starts with /../, then it reaches outside the template
                // root.
                return null;
            }
            if(parentDirPathLoc == -1) {
                if(name.startsWith()) {
                    // Another attempt to reach out of template root.
                    return null;
                }
                break;
            }
            int previousSlashLoc = name.lastIndexOf(parentDirPathLoc - 1);
            name = name.substring(0, previousSlashLoc + 1) +
                   name.substring(parentDirPathLoc + .length());
        }
        for(;;) {
            int currentDirPathLoc = name.indexOf();
            if(currentDirPathLoc == -1) {
                if(name.startsWith()) {
                    name = name.substring(.length());
                }
                break;
            }
            name = name.substring(0, currentDirPathLoc) +
                   name.substring(currentDirPathLoc + .length() - 1);
        }
        // Editing can leave us with a leading slash; strip it.
        if(name.length() > 1 && name.charAt(0) == ) {
            name = name.substring(1);
        }
        return name;
    }

    
This class holds a (name, locale) pair and is used as the key in the cached templates map.
    private static final class TemplateKey
    {
        private final String name;
        private final Locale locale;
        private final String encoding;
        private final boolean parse;
        TemplateKey(String nameLocale localeString encodingboolean parse)
        {
            this. = name;
            this. = locale;
            this. = encoding;
            this. = parse;
        }
        public boolean equals(Object o)
        {
            if (o instanceof TemplateKey)
            {
                TemplateKey tk = (TemplateKey)o;
                return
                     == tk.parse &&
                    .equals(tk.name) &&
                    .equals(tk.locale) &&
                    .equals(tk.encoding);
            }
            return false;
        }
        public int hashCode()
        {
            return
                .hashCode() ^
                .hashCode() ^
                .hashCode() ^
                ( ? . : .).hashCode();
        }
    }

    
This class holds the cached template and associated information (the source object, and the last-checked and last-modified timestamps). It is used as the value in the cached templates map. Note: this class is Serializable to allow custom 3rd party CacheStorage implementations to serialize/replicate them (see tracker issue #1926150); FreeMarker code itself doesn't rely on its serializability.
    private static final class CachedTemplate implements CloneableSerializable
    {
        private static final long serialVersionUID = 1L;
        Object templateOrException;
        Object source;
        long lastChecked;
        long lastModified;
        
        public CachedTemplate cloneCachedTemplate() {
            try {
                return (CachedTemplate)super.clone();
            }
            catch(CloneNotSupportedException e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    }
New to GrepCode? Check out our FAQ X