http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateCache.java 
b/src/main/java/freemarker/cache/TemplateCache.java
index fa8df50..d545986 100644
--- a/src/main/java/freemarker/cache/TemplateCache.java
+++ b/src/main/java/freemarker/cache/TemplateCache.java
@@ -19,12 +19,13 @@
 
 package freemarker.cache;
 
+import java.io.BufferedInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.Serializable;
-import java.io.StringWriter;
 import java.lang.reflect.Method;
-import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -33,10 +34,11 @@ import java.util.StringTokenizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import freemarker.cache.MultiTemplateLoader.MultiSource;
 import freemarker.core.BugException;
 import freemarker.core.Environment;
+import freemarker.core.MarkReleaserTemplateSpecifiedEncodingHandler;
 import freemarker.core.TemplateConfiguration;
+import freemarker.core.TemplateSpecifiedEncodingHandler;
 import freemarker.template.Configuration;
 import freemarker.template.MalformedTemplateNameException;
 import freemarker.template.Template;
@@ -76,12 +78,12 @@ public class TemplateCache {
     private final TemplateLoader templateLoader;
     
     /** Here we keep our cached templates */
-    private final CacheStorage storage;
+    private final CacheStorage cacheStorage;
     private final TemplateLookupStrategy templateLookupStrategy;
     private final TemplateNameFormat templateNameFormat;
     private final TemplateConfigurationFactory templateConfigurations;
     
-    private final boolean isStorageConcurrent;
+    private final boolean isCacheStorageConcurrent;
     /** {@link Configuration#setTemplateUpdateDelayMilliseconds(long)} */
     private long updateDelay = DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS;
     /** {@link Configuration#setLocalizedLookup(boolean)} */
@@ -90,35 +92,6 @@ public class TemplateCache {
     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.
-     * 
-     * @deprecated Use {@link #TemplateCache(TemplateLoader)} instead. The 
default loader is useless in most
-     *     applications, also it can mean a security risk.
-     */
-    @Deprecated
-    public TemplateCache() {
-        
this(_TemplateAPI.createDefaultTemplateLoader(Configuration.VERSION_2_3_0));
-    }
-
-    /**
-     * @deprecated Use {@link #TemplateCache(TemplateLoader, CacheStorage, 
Configuration)} instead.
-     */
-    @Deprecated
-    public TemplateCache(TemplateLoader templateLoader) {
-        this(templateLoader, (Configuration) null);
-    }
-
-    /**
-     * @deprecated Use {@link #TemplateCache(TemplateLoader, CacheStorage, 
Configuration)} instead.
-     */
-    @Deprecated
-    public TemplateCache(TemplateLoader templateLoader, CacheStorage 
cacheStorage) {
-        this(templateLoader, cacheStorage, null);
-    }
-
-    /**
      * Same as {@link #TemplateCache(TemplateLoader, CacheStorage, 
Configuration)} with a new {@link SoftCacheStorage}
      * as the 2nd parameter.
      * 
@@ -157,7 +130,8 @@ public class TemplateCache {
 
     /**
      * @param templateLoader
-     *            The {@link TemplateLoader} to use. Can't be {@code null}.
+     *            The {@link TemplateLoader} to use. Can be {@code null}, 
though than you won't be able to load
+     *            anything.
      * @param cacheStorage
      *            The {@link CacheStorage} to use. Can't be {@code null}.
      * @param templateLookupStrategy
@@ -166,10 +140,10 @@ public class TemplateCache {
      *            The {@link TemplateNameFormat} to use. Can't be {@code null}.
      * @param templateConfigurations
      *            The {@link TemplateConfigurationFactory} to use. Can be 
{@code null} (then all templates will use the
-     *            settings coming from the {@link Configuration} as is).
+     *            settings coming from the {@link Configuration} as is, except 
in the very rare case where a
+     *            {@link TemplateLoader} itself specifies a {@link 
TemplateConfiguration}).
      * @param config
-     *            The {@link Configuration} this cache will be used for. Can 
be {@code null} for backward compatibility,
-     *            as it can be set with {@link 
#setConfiguration(Configuration)} later.
+     *            The {@link Configuration} this cache will be used for. Can't 
be {@code null}.
      * 
      * @since 2.3.24
      */
@@ -180,8 +154,8 @@ public class TemplateCache {
         this.templateLoader = templateLoader;
         
         NullArgumentException.check("cacheStorage", cacheStorage);
-        this.storage = cacheStorage;
-        isStorageConcurrent = cacheStorage instanceof ConcurrentCacheStorage &&
+        this.cacheStorage = cacheStorage;
+        isCacheStorageConcurrent = cacheStorage instanceof 
ConcurrentCacheStorage &&
                 ((ConcurrentCacheStorage) cacheStorage).isConcurrent();
         
         NullArgumentException.check("templateLookupStrategy", 
templateLookupStrategy);
@@ -193,28 +167,24 @@ public class TemplateCache {
         // Can be null
         this.templateConfigurations = templateConfigurations;
         
+        NullArgumentException.check("config", config);
         this.config = config;
     }
     
     /**
-     * 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.
-     * 
-     * @deprecated Use the {@link #TemplateCache(TemplateLoader, CacheStorage, 
Configuration)} constructor.
+     * Returns the configuration for internal usage.
      */
-    @Deprecated
-    public void setConfiguration(Configuration config) {
-        this.config = config;
-        clear();
+    // [FM3] After setConfiguration was removed, this can be too.
+    Configuration getConfiguration() {
+        return config;
     }
 
     public TemplateLoader getTemplateLoader() {
         return templateLoader;
     }
-    
+
     public CacheStorage getCacheStorage() {
-        return storage;
+        return cacheStorage;
     }
     
     /**
@@ -287,12 +257,12 @@ public class TemplateCache {
         }
         
         if (templateLoader == null) {
-            return new MaybeMissingTemplate(name, "The TemplateLoader was 
null.");
+            return new MaybeMissingTemplate(name, "The TemplateLoader (and 
TemplateLoader2) was null.");
         }
         
         Template template = getTemplateInternal(name, locale, 
customLookupCondition, encoding, parseAsFTL);
         return template != null ? new MaybeMissingTemplate(template) : new 
MaybeMissingTemplate(name, (String) null);
-    }    
+    }
 
     /**
      * Similar to {@link #getTemplate(String, Locale, Object, String, 
boolean)} with {@code null}
@@ -310,158 +280,236 @@ public class TemplateCache {
         return getTemplate(name, locale, null, encoding, 
parseAsFTL).getTemplate();
     }
     
-    /**
-     * Returns the deprecated default template loader of FreeMarker 2.3.0.
-     * 
-     * @deprecated The {@link TemplateLoader} should be always specified by 
the constructor caller.
-     */
-    @Deprecated
-    protected static TemplateLoader createLegacyDefaultTemplateLoader() {
-        return 
_TemplateAPI.createDefaultTemplateLoader(Configuration.VERSION_2_3_0);        
-    }
-    
     private Template getTemplateInternal(
             final String name, final Locale locale, final Object 
customLookupCondition,
             final String encoding, final boolean parseAsFTL)
     throws IOException {
         final boolean debug = LOG.isDebugEnabled();
-        final String debugName = debug
-                ? buildDebugName(name, locale, customLookupCondition, 
encoding, parseAsFTL)
+        final String debugPrefix = debug
+                ? getDebugPrefix("getTemplate", name, locale, 
customLookupCondition, encoding, parseAsFTL)
                 : null;
-        final TemplateKey tk = new TemplateKey(name, locale, 
customLookupCondition, encoding, parseAsFTL);
+        final CachedResultKey cacheKey = new CachedResultKey(name, locale, 
customLookupCondition, encoding, parseAsFTL);
         
-        CachedTemplate cachedTemplate;
-        if (isStorageConcurrent) {
-            cachedTemplate = (CachedTemplate) storage.get(tk);
+        CachedResult oldCachedResult;
+        if (isCacheStorageConcurrent) {
+            oldCachedResult = (CachedResult) cacheStorage.get(cacheKey);
         } else {
-            synchronized (storage) {
-                cachedTemplate = (CachedTemplate) storage.get(tk);
+            synchronized (cacheStorage) {
+                oldCachedResult = (CachedResult) cacheStorage.get(cacheKey);
             }
         }
         
         final long now = System.currentTimeMillis();
         
-        long lastModified = -1L;
-        boolean rethrown = false;
+        boolean rethrownCachedException = false;
+        boolean suppressFinallyException = false;
         TemplateLookupResult newLookupResult = null;
+        CachedResult newCachedResult = null;
+        TemplateLoaderSession session = null;
         try {
-            if (cachedTemplate != null) {
-                // If we're within the refresh delay, return the cached copy
-                if (now - cachedTemplate.lastChecked < updateDelay) {
+            if (oldCachedResult != null) {
+                // If we're within the refresh delay, return the cached result
+                if (now - oldCachedResult.lastChecked < updateDelay) {
                     if (debug) {
-                        LOG.debug(debugName + " cached copy not yet stale; 
using cached.");
+                        LOG.debug(debugPrefix + "Cached copy not yet stale; 
using cached.");
                     }
-                    // Can be null, indicating a cached negative lookup
-                    Object t = cachedTemplate.templateOrException;
+                    Object t = oldCachedResult.templateOrException;
+                    // t can be null, indicating a cached negative lookup
                     if (t instanceof Template || t == null) {
                         return (Template) t;
                     } else if (t instanceof RuntimeException) {
-                        throwLoadFailedException((RuntimeException) t);
+                        rethrowCachedException((RuntimeException) t);
                     } else if (t instanceof IOException) {
-                        rethrown = true;
-                        throwLoadFailedException((IOException) t);
+                        rethrownCachedException = true;
+                        rethrowCachedException((IOException) t);
                     }
-                    throw new BugException("t is " + t.getClass().getName());
+                    throw new BugException("Unhandled class for t: " + 
t.getClass().getName());
                 }
+                // The freshness of the cache result must be checked.
                 
-                // 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;
+                // Clone, as the instance in the cache store must not be 
modified to ensure proper concurrent behavior.
+                newCachedResult = oldCachedResult.clone();
+                newCachedResult.lastChecked = now;
 
-                // Find the template source
-                newLookupResult = lookupTemplate(name, locale, 
customLookupCondition);
+                session = templateLoader.createSession();
+                if (debug && session != null) {
+                    LOG.debug(debugPrefix + "Session created.");
+                }
+                
+                // Find the template source, load it if it doesn't correspond 
to the cached result.
+                newLookupResult = lookupAndLoadTemplateIfChanged(
+                        name, locale, customLookupCondition, 
oldCachedResult.source, oldCachedResult.version, session);
 
-                // Template source was removed
-                if (!newLookupResult.isPositive()) {
+                // Template source was removed 
(TemplateLoader2ResultStatus.NOT_FOUND, or no TemplateLoader2Result)
+                if (!newLookupResult.isPositive()) { 
                     if (debug) {
-                        LOG.debug(debugName + " no source found.");
+                        LOG.debug(debugPrefix + "No source found.");
                     } 
-                    storeNegativeLookup(tk, cachedTemplate, null);
+                    setToNegativeAndPutIntoCache(cacheKey, newCachedResult, 
null);
                     return null;
                 }
 
-                // If the source didn't change and its last modified date
-                // also didn't change, return the cached version.
-                final Object newLookupResultSource = 
newLookupResult.getTemplateSource();
-                lastModified = 
templateLoader.getLastModified(newLookupResultSource);
-                boolean lastModifiedNotChanged = lastModified == 
cachedTemplate.lastModified;
-                boolean sourceEquals = 
newLookupResultSource.equals(cachedTemplate.source);
-                if (lastModifiedNotChanged && sourceEquals) {
+                final TemplateLoadingResult newTemplateLoaderResult = 
newLookupResult.getTemplateLoaderResult();
+                if (newTemplateLoaderResult.getStatus() == 
TemplateLoadingResultStatus.NOT_MODIFIED) {
+                    // Return the cached version.
                     if (debug) {
-                        LOG.debug(debugName + ": using cached since " + 
newLookupResultSource + " hasn't changed.");
+                        LOG.debug(debugPrefix + ": Using cached template "
+                                + "(source: " + 
newTemplateLoaderResult.getSource() + ")"
+                                + " as it hasn't been changed on the backing 
store.");
                     }
-                    storeCached(tk, cachedTemplate);
-                    return (Template) cachedTemplate.templateOrException;
-                } else if (debug) {
-                    if (!sourceEquals) {
-                        LOG.debug("Updating source because: " + 
-                            "sourceEquals=" + sourceEquals + 
-                            ", newlyFoundSource=" + 
StringUtil.jQuoteNoXSS(newLookupResultSource) + 
-                            ", cached.source=" + 
StringUtil.jQuoteNoXSS(cachedTemplate.source));
-                    } else if (!lastModifiedNotChanged) {
-                        LOG.debug("Updating source because: " + 
-                            "lastModifiedNotChanged=" + lastModifiedNotChanged 
+ 
-                            ", cached.lastModified=" + 
cachedTemplate.lastModified + 
-                            " != source.lastModified=" + lastModified);
+                    putIntoCache(cacheKey, newCachedResult);
+                    return (Template) newCachedResult.templateOrException;
+                } else {
+                    if (newTemplateLoaderResult.getStatus() != 
TemplateLoadingResultStatus.OPENED) {
+                        // TemplateLoader2ResultStatus.NOT_FOUND was already 
handler earlier
+                        throw new BugException("Unxpected status: " + 
newTemplateLoaderResult.getStatus());
+                    }
+                    if (debug) {
+                        StringBuilder debugMsg = new StringBuilder();
+                        debugMsg.append(debugPrefix)
+                                .append("Reloading template instead of using 
the cached result because ");
+                        if (newCachedResult.templateOrException instanceof 
Throwable) {
+                            debugMsg.append("it's a cached error (retrying).");
+                        } else {
+                            Object newSource = 
newTemplateLoaderResult.getSource();
+                            if (!nullSafeEquals(newSource, 
oldCachedResult.source)) {
+                                debugMsg.append("the source has been changed: 
")
+                                        
.append("cached.source=").append(StringUtil.jQuoteNoXSS(oldCachedResult.source))
+                                        .append(", 
current.source=").append(StringUtil.jQuoteNoXSS(newSource));
+                            } else {
+                                Serializable newVersion = 
newTemplateLoaderResult.getVersion();
+                                if (!nullSafeEquals(oldCachedResult.version, 
newVersion)) {
+                                    debugMsg.append("the version has been 
changed: ")
+                                            
.append("cached.version=").append(oldCachedResult.version) 
+                                            .append(", 
current.version=").append(newVersion);
+                                } else {
+                                    debugMsg.append("??? (unknown reason)");
+                                }
+                            }
+                        }
+                        LOG.debug(debugMsg.toString());
                     }
                 }
-            } else {
+            } else { // if there was no cached result
                 if (debug) {
-                    LOG.debug("Couldn't find template in cache for " + 
debugName + "; will try to load it.");
+                    LOG.debug(debugPrefix + "No cached result was found; will 
try to load template.");
                 }
                 
-                // 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;
+                newCachedResult = new CachedResult();
+                newCachedResult.lastChecked = now;
+            
+                session = templateLoader.createSession();
+                if (debug && session != null) {
+                    LOG.debug(debugPrefix + "Session created.");
+                } 
                 
-                newLookupResult = lookupTemplate(name, locale, 
customLookupCondition);
+                newLookupResult = lookupAndLoadTemplateIfChanged(
+                        name, locale, customLookupCondition, null, null, 
session);
                 
                 if (!newLookupResult.isPositive()) {
-                    storeNegativeLookup(tk, cachedTemplate, null);
+                    setToNegativeAndPutIntoCache(cacheKey, newCachedResult, 
null);
                     return null;
                 }
-                
-                cachedTemplate.lastModified = lastModified = Long.MIN_VALUE;
             }
+            // We have newCachedResult and newLookupResult initialized at this 
point.
 
-            Object source = newLookupResult.getTemplateSource();
-            cachedTemplate.source = source;
+            TemplateLoadingResult templateLoaderResult = 
newLookupResult.getTemplateLoaderResult();
+            newCachedResult.source = templateLoaderResult.getSource();
             
             // If we get here, then we need to (re)load the template
             if (debug) {
-                LOG.debug("Loading template for " + debugName + " from " + 
StringUtil.jQuoteNoXSS(source));
+                LOG.debug(debugPrefix + "Reading template content (source: "
+                        + StringUtil.jQuoteNoXSS(newCachedResult.source) + 
")");
             }
             
-            lastModified = lastModified == Long.MIN_VALUE ? 
templateLoader.getLastModified(source) : lastModified;            
             Template template = loadTemplate(
-                    templateLoader, source,
+                    templateLoaderResult,
                     name, newLookupResult.getTemplateSourceName(), locale, 
customLookupCondition,
                     encoding, parseAsFTL);
-            cachedTemplate.templateOrException = template;
-            cachedTemplate.lastModified = lastModified;
-            storeCached(tk, cachedTemplate);
+            if (session != null) {
+                session.close();
+                if (debug) {
+                    LOG.debug(debugPrefix + "Session closed.");
+                } 
+            }
+            newCachedResult.templateOrException = template;
+            newCachedResult.version = templateLoaderResult.getVersion();
+            putIntoCache(cacheKey, newCachedResult);
             return template;
         } catch (RuntimeException e) {
-            if (cachedTemplate != null) {
-                storeNegativeLookup(tk, cachedTemplate, e);
+            if (newCachedResult != null) {
+                setToNegativeAndPutIntoCache(cacheKey, newCachedResult, e);
             }
+            suppressFinallyException = true;
             throw e;
         } catch (IOException e) {
-            if (!rethrown) {
-                storeNegativeLookup(tk, cachedTemplate, e);
+            // Rethrown cached exceptions are wrapped into IOException-s, so 
we only need this condition here.
+            if (!rethrownCachedException) {
+                setToNegativeAndPutIntoCache(cacheKey, newCachedResult, e);
             }
+            suppressFinallyException = true;
             throw e;
         } finally {
-            if (newLookupResult != null && newLookupResult.isPositive()) {
-                
templateLoader.closeTemplateSource(newLookupResult.getTemplateSource());
+            try {
+                // Close streams first:
+                
+                if (newLookupResult != null && newLookupResult.isPositive()) {
+                    TemplateLoadingResult templateLoaderResult = 
newLookupResult.getTemplateLoaderResult();
+                    Reader reader = templateLoaderResult.getReader();
+                    if (reader != null) {
+                        try {
+                            reader.close();
+                        } catch (IOException e) { // [FM3] Exception e
+                            if (suppressFinallyException) {
+                                if (LOG.isWarnEnabled()) { 
+                                    LOG.warn("Failed to close template content 
Reader for: " + name, e);
+                                }
+                            } else {
+                                suppressFinallyException = true;
+                                throw e;
+                            }
+                        }
+                    } else if (templateLoaderResult.getInputStream() != null) {
+                        try {
+                            templateLoaderResult.getInputStream().close();
+                        } catch (IOException e) { // [FM3] Exception e
+                            if (suppressFinallyException) {
+                                if (LOG.isWarnEnabled()) { 
+                                    LOG.warn("Failed to close template content 
InputStream for: " + name, e);
+                                }
+                            } else {
+                                suppressFinallyException = true;
+                                throw e;
+                            }
+                        }
+                    }
+                }
+            } finally {
+                // Then close streams:
+                
+                if (session != null && !session.isClosed()) {
+                    try {
+                        session.close();
+                        if (debug) {
+                            LOG.debug(debugPrefix + "Session closed.");
+                        } 
+                    } catch (IOException e) { // [FM3] Exception e
+                        if (suppressFinallyException) {
+                            if (LOG.isWarnEnabled()) { 
+                                LOG.warn("Failed to close template loader 
session for" + name, e);
+                            }
+                        } else {
+                            suppressFinallyException = true;
+                            throw e;
+                        }
+                    }
+                }
             }
         }
     }
 
+    
+    
     private static final Method INIT_CAUSE = getInitCauseMethod();
     
     private static final Method getInitCauseMethod() {
@@ -498,39 +546,59 @@ public class TemplateCache {
         return ioe;
     }
     
-    private void throwLoadFailedException(Throwable e) throws IOException {
+    private void rethrowCachedException(Throwable e) throws IOException {
         throw newIOException("There was an error loading the " +
                 "template on an earlier attempt; see cause exception.", e);
     }
 
-    private void storeNegativeLookup(TemplateKey tk, 
-            CachedTemplate cachedTemplate, Exception e) {
-        cachedTemplate.templateOrException = e;
-        cachedTemplate.source = null;
-        cachedTemplate.lastModified = 0L;
-        storeCached(tk, cachedTemplate);
+    private void setToNegativeAndPutIntoCache(CachedResultKey cacheKey, 
CachedResult cachedResult, Exception e) {
+        cachedResult.templateOrException = e;
+        cachedResult.source = null;
+        cachedResult.version = null;
+        putIntoCache(cacheKey, cachedResult);
     }
 
-    private void storeCached(TemplateKey tk, CachedTemplate cachedTemplate) {
-        if (isStorageConcurrent) {
-            storage.put(tk, cachedTemplate);
+    private void putIntoCache(CachedResultKey tk, CachedResult cachedTemplate) 
{
+        if (isCacheStorageConcurrent) {
+            cacheStorage.put(tk, cachedTemplate);
         } else {
-            synchronized (storage) {
-                storage.put(tk, cachedTemplate);
+            synchronized (cacheStorage) {
+                cacheStorage.put(tk, cachedTemplate);
             }
         }
     }
 
+    @SuppressWarnings("deprecation")
     private Template loadTemplate(
-            final TemplateLoader templateLoader, final Object source,
+            TemplateLoadingResult templateLoaderResult,
             final String name, final String sourceName, Locale locale, final 
Object customLookupCondition,
             String initialEncoding, final boolean parseAsFTL) throws 
IOException {
-        final TemplateConfiguration tc;
-        try {
-            tc = templateConfigurations != null ? 
templateConfigurations.get(sourceName, source) : null;
-        } catch (TemplateConfigurationFactoryException e) {
-            throw newIOException("Error while getting TemplateConfiguration; 
see cause exception.", e);
+        TemplateConfiguration tc;
+        {
+            TemplateConfiguration cfgTC;
+            try {
+                cfgTC = templateConfigurations != null
+                        ? templateConfigurations.get(sourceName, 
templateLoaderResult.getSource()) : null;
+            } catch (TemplateConfigurationFactoryException e) {
+                throw newIOException("Error while getting 
TemplateConfiguration; see cause exception.", e);
+            }
+            TemplateConfiguration resultTC = 
templateLoaderResult.getTemplateConfiguration();
+            if (resultTC != null) {
+                TemplateConfiguration mergedTC = new TemplateConfiguration();
+                if (cfgTC != null) {
+                    mergedTC.merge(cfgTC);
+                }
+                if (resultTC != null) {
+                    mergedTC.merge(resultTC);
+                }
+                mergedTC.setParentConfiguration(config);
+                
+                tc = mergedTC;
+            } else {
+                tc = cfgTC;
+            }
         }
+        
         if (tc != null) {
             // TC.{encoding,locale} is stronger than the cfg.getTemplate 
arguments by design.
             if (tc.isEncodingSet()) {
@@ -543,47 +611,70 @@ public class TemplateCache {
         
         Template template;
         {
-            if (parseAsFTL) {
-                try {
-                    final Reader reader = templateLoader.getReader(source, 
initialEncoding);
-                    try {
-                        template = new Template(name, sourceName, reader, 
config, tc, initialEncoding);
-                    } finally {
-                        reader.close();
-                    }
-                } catch (Template.WrongEncodingException wee) {
-                    String actualEncoding = wee.getTemplateSpecifiedEncoding();
-                    if (LOG.isDebugEnabled()) {
-                        LOG.debug("Initial encoding \"" + initialEncoding + 
"\" was incorrect, re-reading with \""
-                                + actualEncoding + "\". Template: " + 
sourceName);
-                    }
-                    
-                    final Reader reader = templateLoader.getReader(source, 
actualEncoding);
-                    try {
-                        template = new Template(name, sourceName, reader, 
config, tc, actualEncoding);
-                    } finally {
-                        reader.close();
+            Reader reader = templateLoaderResult.getReader();
+            InputStream inputStream = templateLoaderResult.getInputStream();
+            TemplateSpecifiedEncodingHandler templateSpecifiedEncodingHandler;
+            if (reader != null) {
+                if (inputStream != null) {
+                    throw new IllegalStateException("For a(n) " + 
templateLoaderResult.getClass().getName()
+                            + ", both getReader() and getInputStream() has 
returned non-null.");
+                }
+                initialEncoding = null;  // No charset decoding has happened
+                templateSpecifiedEncodingHandler = 
TemplateSpecifiedEncodingHandler.DEFAULT; 
+            } else if (inputStream != null) {
+                if (parseAsFTL) {
+                    // We need mark support, to restart if the charset 
suggested by <#ftl encoding=...> differs
+                    // from that we use initially.
+                    if (!inputStream.markSupported()) {
+                        inputStream = new BufferedInputStream(inputStream);
                     }
+                    inputStream.mark(Integer.MAX_VALUE); // [FM3] Mark should 
be released after the 1st FTL tag
+                    templateSpecifiedEncodingHandler = new 
MarkReleaserTemplateSpecifiedEncodingHandler(
+                            inputStream);
+                } else {
+                    templateSpecifiedEncodingHandler = null; 
                 }
+                reader = new InputStreamReader(inputStream, initialEncoding);
             } else {
-                // Read the contents into a StringWriter, then construct a 
single-text-block template from it.
-                final StringWriter sw = new StringWriter();
-                final char[] buf = new char[4096];
-                final Reader reader = templateLoader.getReader(source, 
initialEncoding);
-                try {
-                    fetchChars: while (true) {
-                        int charsRead = reader.read(buf);
-                        if (charsRead > 0) {
-                            sw.write(buf, 0, charsRead);
-                        } else if (charsRead < 0) {
-                            break fetchChars;
+                throw new IllegalStateException("For a(n) " + 
templateLoaderResult.getClass().getName()
+                        + ", both getReader() and getInputStream() has 
returned null.");
+            }
+            
+            try {
+                if (parseAsFTL) {
+                    try {
+                        template = new Template(name, sourceName, reader, 
config, tc,
+                                initialEncoding, 
templateSpecifiedEncodingHandler);
+                    } catch (Template.WrongEncodingException wee) {
+                        final String templateSpecifiedEncoding = 
wee.getTemplateSpecifiedEncoding();
+                        
+                        if (inputStream != null) {
+                            // We restart InputStream to re-decode it with the 
new charset.
+                            inputStream.reset();
+                            
+                            // Don't close `reader`; it's an InputStreamReader 
that would close the wrapped InputStream.
+                            reader = new InputStreamReader(inputStream, 
templateSpecifiedEncoding);
+                        } else {
+                            // Should be impossible to get here
+                            throw new BugException();
                         }
+                        
+                        template = new Template(name, sourceName, reader, 
config, tc,
+                                templateSpecifiedEncoding, 
templateSpecifiedEncodingHandler);
+                    }
+                } else {
+                    // Read the contents into a StringWriter, then construct a 
single-text-block template from it.
+                    final StringBuilder sb = new StringBuilder();
+                    final char[] buf = new char[4096];
+                    int charsRead;
+                    while ((charsRead = reader.read(buf)) > 0) {
+                        sb.append(buf, 0, charsRead);
                     }
-                } finally {
-                    reader.close();
+                    template = Template.getPlainTextTemplate(name, sourceName, 
sb.toString(), config);
+                    template.setEncoding(initialEncoding);
                 }
-                template = Template.getPlainTextTemplate(name, sourceName, 
sw.toString(), config);
-                template.setEncoding(initialEncoding);
+            } finally {
+                reader.close();
             }
         }
 
@@ -644,17 +735,29 @@ public class TemplateCache {
     }
 
     /**
-     * Removes all entries from the cache, forcing reloading of templates
-     * on subsequent {@link #getTemplate(String, Locale, String, boolean)}
-     * calls. If the configured template loader is 
-     * {@link StatefulTemplateLoader stateful}, then its 
-     * {@link StatefulTemplateLoader#resetState()} method is invoked as well.
+     * Removes all entries from the cache, forcing reloading of templates on 
subsequent
+     * {@link #getTemplate(String, Locale, String, boolean)} calls.
+     * 
+     * @param resetTemplateLoader
+     *            Whether to call {@link TemplateLoader#resetState()}. on the 
template loader.
+     */
+    public void clear(boolean resetTemplateLoader) {
+        synchronized (cacheStorage) {
+            cacheStorage.clear();
+            if (templateLoader != null && resetTemplateLoader) {
+                templateLoader.resetState();
+            }
+        }
+    }
+    
+    /**
+     * Same as {@link #clear(boolean)} with {@code true} {@code 
resetTemplateLoader} argument.
      */
     public void clear() {
-        synchronized (storage) {
-            storage.clear();
-            if (templateLoader instanceof StatefulTemplateLoader) {
-                ((StatefulTemplateLoader) templateLoader).resetState();
+        synchronized (cacheStorage) {
+            cacheStorage.clear();
+            if (templateLoader != null) {
+                templateLoader.resetState();
             }
         }
     }
@@ -691,31 +794,32 @@ public class TemplateCache {
         name = templateNameFormat.normalizeAbsoluteName(name);
         if (name != null && templateLoader != null) {
             boolean debug = LOG.isDebugEnabled();
-            String debugName = debug
-                    ? buildDebugName(name, locale, customLookupCondition, 
encoding, parse)
+            String debugPrefix = debug
+                    ? getDebugPrefix("removeTemplate", name, locale, 
customLookupCondition, encoding, parse)
                     : null;
-            TemplateKey tk = new TemplateKey(name, locale, 
customLookupCondition, encoding, parse);
+            CachedResultKey tk = new CachedResultKey(name, locale, 
customLookupCondition, encoding, parse);
             
-            if (isStorageConcurrent) {
-                storage.remove(tk);
+            if (isCacheStorageConcurrent) {
+                cacheStorage.remove(tk);
             } else {
-                synchronized (storage) {
-                    storage.remove(tk);
+                synchronized (cacheStorage) {
+                    cacheStorage.remove(tk);
                 }
             }
             if (debug) {
-                LOG.debug(debugName + " was removed from the cache, if it was 
there");
+                LOG.debug(debugPrefix + "Template was removed from the cache, 
if it was there");
             }
         }
     }
 
-    private String buildDebugName(String name, Locale locale, Object 
customLookupCondition, String encoding,
+    private String getDebugPrefix(String operation, String name, Locale 
locale, Object customLookupCondition, String encoding,
             boolean parse) {
-        return StringUtil.jQuoteNoXSS(name) + "("
+        return operation + " " + StringUtil.jQuoteNoXSS(name) + "("
                 + StringUtil.jQuoteNoXSS(locale)
                 + (customLookupCondition != null ? ", cond=" + 
StringUtil.jQuoteNoXSS(customLookupCondition) : "")
                 + ", " + encoding
-                + (parse ? ", parsed)" : ", unparsed]");
+                + (parse ? ", parsed)" : ", unparsed]")
+                + ": ";
     }    
 
     /**
@@ -735,110 +839,55 @@ public class TemplateCache {
         }
     }
 
-    private TemplateLookupResult lookupTemplate(String name, Locale locale, 
Object customLookupCondition)
-            throws IOException {
+    /**
+     * Looks up according the {@link TemplateLookupStrategy} and then starts 
reading the template, if it was changed
+     * compared to the cached result, or if there was no cached result yet.
+     */
+    private TemplateLookupResult lookupAndLoadTemplateIfChanged(
+            String name, Locale locale, Object customLookupCondition,
+            TemplateLoadingSource cachedResultSource, Serializable 
cachedResultVersion,
+            TemplateLoaderSession session) throws IOException {
         final TemplateLookupResult lookupResult = 
templateLookupStrategy.lookup(
-                new TemplateCacheTemplateLookupContext(name, locale, 
customLookupCondition));
+                new TemplateCacheTemplateLookupContext(
+                        name, locale, customLookupCondition,
+                        cachedResultSource, cachedResultVersion,
+                        session));
         if (lookupResult == null) {
             throw new NullPointerException("Lookup result shouldn't be null");
         }
         return lookupResult;
     }
 
-    private TemplateLookupResult lookupTemplateWithAcquisitionStrategy(String 
path) throws IOException {
-        int asterisk = path.indexOf(ASTERISK);
-        // Shortcut in case there is no acquisition
-        if (asterisk == -1) {
-            return TemplateLookupResult.from(path, findTemplateSource(path));
-        }
-        StringTokenizer tok = new StringTokenizer(path, "/");
-        int lastAsterisk = -1;
-        List tokpath = new ArrayList();
-        while (tok.hasMoreTokens()) {
-            String pathToken = tok.nextToken();
-            if (pathToken.equals(ASTERISKSTR)) {
-                if (lastAsterisk != -1) {
-                    tokpath.remove(lastAsterisk);
-                }
-                lastAsterisk = tokpath.size();
-            }
-            tokpath.add(pathToken);
-        }
-        if (lastAsterisk == -1) {  // if there was no real "*" step after all
-            return TemplateLookupResult.from(path, findTemplateSource(path));
-        }
-        String basePath = concatPath(tokpath, 0, lastAsterisk);
-        String resourcePath = concatPath(tokpath, lastAsterisk + 1, 
tokpath.size());
-        if (resourcePath.endsWith("/")) {
-            resourcePath = resourcePath.substring(0, resourcePath.length() - 
1);
-        }
-        StringBuilder buf = new StringBuilder(path.length()).append(basePath);
-        int l = basePath.length();
-        for (; ; ) {
-            String fullPath = buf.append(resourcePath).toString();
-            Object templateSource = findTemplateSource(fullPath);
-            if (templateSource != null) {
-                return TemplateLookupResult.from(fullPath, templateSource);
-            }
-            if (l == 0) {
-                return TemplateLookupResult.createNegativeResult();
-            }
-            l = basePath.lastIndexOf(SLASH, l - 2) + 1;
-            buf.setLength(l);
-        }
-    }
-
-    private Object findTemplateSource(String path) throws IOException {
-        final Object result = templateLoader.findTemplateSource(path);
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("TemplateLoader.findTemplateSource(" +  
StringUtil.jQuote(path) + "): "
-                    + (result == null ? "Not found" : "Found"));
-        }
-        return modifyForConfIcI(result);
-    }
-
-    /**
-     * If IcI >= 2.3.21, sets {@link URLTemplateSource#setUseCaches(boolean)} 
to {@code false} for sources that come
-     * from a {@link TemplateLoader} where {@link URLConnection} cache usage 
wasn't set explicitly.  
-     */
-    private Object modifyForConfIcI(Object templateSource) {
-        if (templateSource == null) return null;
-        
-        if (config.getIncompatibleImprovements().intValue() < 
_TemplateAPI.VERSION_INT_2_3_21) {
-            return templateSource;
-        }
-        
-        if (templateSource instanceof URLTemplateSource) {
-            URLTemplateSource urlTemplateSource = (URLTemplateSource) 
templateSource;
-            if (urlTemplateSource.getUseCaches() == null) {  // It was left 
unset
-                urlTemplateSource.setUseCaches(false);
-            }
-        } else if (templateSource instanceof MultiSource) {
-            modifyForConfIcI(((MultiSource) 
templateSource).getWrappedSource());
-        }
-        return templateSource;
-    }
-
-    private String concatPath(List path, int from, int to) {
+    private String concatPath(List<String> pathSteps, int from, int to) {
         StringBuilder buf = new StringBuilder((to - from) * 16);
         for (int i = from; i < to; ++i) {
-            buf.append(path.get(i)).append('/');
+            buf.append(pathSteps.get(i));
+            if (i < pathSteps.size() - 1) {
+                buf.append('/');
+            }
         }
         return buf.toString();
     }
     
+    // Replace with Objects.equals in Java 7
+    private static boolean nullSafeEquals(Object o1, Object o2) {
+        if (o1 == o2) return true;
+        if (o1 == null || o2 == null) return false;
+        return o1.equals(o2);
+    }
+    
     /**
-     * This class holds a (name, locale) pair and is used as the key in
-     * the cached templates map.
+     * Used as cache key to look up a {@link CachedResult}. 
      */
-    private static final class TemplateKey {
+    @SuppressWarnings("serial")
+    private static final class CachedResultKey implements Serializable {
         private final String name;
         private final Locale locale;
         private final Object customLookupCondition;
         private final String encoding;
         private final boolean parse;
 
-        TemplateKey(String name, Locale locale, Object customLookupCondition, 
String encoding, boolean parse) {
+        CachedResultKey(String name, Locale locale, Object 
customLookupCondition, String encoding, boolean parse) {
             this.name = name;
             this.locale = locale;
             this.customLookupCondition = customLookupCondition;
@@ -848,8 +897,8 @@ public class TemplateCache {
 
         @Override
         public boolean equals(Object o) {
-            if (o instanceof TemplateKey) {
-                TemplateKey tk = (TemplateKey) o;
+            if (o instanceof CachedResultKey) {
+                CachedResultKey tk = (CachedResultKey) o;
                 return
                     parse == tk.parse &&
                     name.equals(tk.name) &&
@@ -860,12 +909,6 @@ public class TemplateCache {
             return false;
         }
 
-        private boolean nullSafeEquals(Object o1, Object o2) {
-            return o1 != null
-                ? (o2 != null ? o1.equals(o2) : false)
-                : o2 == null;
-        }
-
         @Override
         public int hashCode() {
             return
@@ -878,24 +921,27 @@ public class TemplateCache {
     }
 
     /**
-     * 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.
+     * Hold the a cached {@link #getTemplate(String, Locale, Object, String, 
boolean)} result and the associated
+     * information needed to check if the cached value is up to date.
+     * 
+     * <p>
+     * Note: this class is Serializable to allow custom 3rd party CacheStorage 
implementations to serialize/replicate
+     * them; FreeMarker code itself doesn't rely on its serializability.
+     * 
+     * @see CachedResultKey
      */
-    private static final class CachedTemplate implements Cloneable, 
Serializable {
+    private static final class CachedResult implements Cloneable, Serializable 
{
         private static final long serialVersionUID = 1L;
 
         Object templateOrException;
-        Object source;
+        TemplateLoadingSource source;
+        Serializable version;
         long lastChecked;
-        long lastModified;
         
-        public CachedTemplate cloneCachedTemplate() {
+        @Override
+        public CachedResult clone() {
             try {
-                return (CachedTemplate) super.clone();
+                return (CachedResult) super.clone();
             } catch (CloneNotSupportedException e) {
                 throw new UndeclaredThrowableException(e);
             }
@@ -904,18 +950,65 @@ public class TemplateCache {
     
     private class TemplateCacheTemplateLookupContext extends 
TemplateLookupContext {
 
-        TemplateCacheTemplateLookupContext(String templateName, Locale 
templateLocale, Object customLookupCondition) {
-            super(templateName, localizedLookup ? templateLocale : null, 
customLookupCondition);
+        private final TemplateLoaderSession session; 
+        
+        TemplateCacheTemplateLookupContext(String templateName, Locale 
templateLocale, Object customLookupCondition,
+                TemplateLoadingSource cachedResultSource, Serializable 
cachedResultVersion,
+                TemplateLoaderSession session) {
+            super(templateName, localizedLookup ? templateLocale : null, 
customLookupCondition,
+                    cachedResultSource, cachedResultVersion);
+            this.session = session;
         }
 
         @Override
-        public TemplateLookupResult lookupWithAcquisitionStrategy(String name) 
throws IOException {
+        public TemplateLookupResult lookupWithAcquisitionStrategy(String path) 
throws IOException {
             // Only one of the possible ways of making a name non-normalized, 
but is the easiest mistake to do:
-            if (name.startsWith("/")) {
-                throw new IllegalArgumentException("Non-normalized name, 
starts with \"/\": " + name);
+            if (path.startsWith("/")) {
+                throw new IllegalArgumentException("Non-normalized name, 
starts with \"/\": " + path);
             }
             
-            return 
TemplateCache.this.lookupTemplateWithAcquisitionStrategy(name);
+            int asterisk = path.indexOf(ASTERISK);
+            // Shortcut in case there is no acquisition
+            if (asterisk == -1) {
+                return TemplateLookupResult.from(
+                        path,
+                        templateLoader.load(path, getCachedResultSource(), 
getCachedResultVersion(), session));
+            }
+            StringTokenizer pathTokenizer = new StringTokenizer(path, "/");
+            int lastAsterisk = -1;
+            List<String> pathSteps = new ArrayList<String>();
+            while (pathTokenizer.hasMoreTokens()) {
+                String pathStep = pathTokenizer.nextToken();
+                if (pathStep.equals(ASTERISKSTR)) {
+                    if (lastAsterisk != -1) {
+                        pathSteps.remove(lastAsterisk);
+                    }
+                    lastAsterisk = pathSteps.size();
+                }
+                pathSteps.add(pathStep);
+            }
+            if (lastAsterisk == -1) {  // if there was no real "*" step after 
all
+                return TemplateLookupResult.from(
+                        path,
+                        templateLoader.load(path, getCachedResultSource(), 
getCachedResultVersion(), session));
+            }
+            String basePath = concatPath(pathSteps, 0, lastAsterisk);
+            String postAsteriskPath = concatPath(pathSteps, lastAsterisk + 1, 
pathSteps.size());
+            StringBuilder buf = new 
StringBuilder(path.length()).append(basePath);
+            int basePathLen = basePath.length();
+            while (true) {
+                String fullPath = buf.append(postAsteriskPath).toString();
+                TemplateLoadingResult templateLoaderResult = 
templateLoader.load(
+                        fullPath, getCachedResultSource(), 
getCachedResultVersion(), session);
+                if (templateLoaderResult.getStatus() == 
TemplateLoadingResultStatus.OPENED) {
+                    return TemplateLookupResult.from(fullPath, 
templateLoaderResult);
+                }
+                if (basePathLen == 0) {
+                    return TemplateLookupResult.createNegativeResult();
+                }
+                basePathLen = basePath.lastIndexOf(SLASH, basePathLen - 2) + 1;
+                buf.setLength(basePathLen);
+            }
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateConfigurationFactory.java 
b/src/main/java/freemarker/cache/TemplateConfigurationFactory.java
index ec8d2ae..b2561e9 100644
--- a/src/main/java/freemarker/cache/TemplateConfigurationFactory.java
+++ b/src/main/java/freemarker/cache/TemplateConfigurationFactory.java
@@ -37,10 +37,10 @@ public abstract class TemplateConfigurationFactory {
      * Returns (maybe creates) the {@link TemplateConfiguration} for the given 
template source.
      * 
      * @param sourceName
-     *            The name (path) that was used for {@link 
TemplateLoader#findTemplateSource(String)}. See
+     *            The name (path) that was used for {@link 
TemplateLoader#load}. See
      *            {@link Template#getSourceName()} for details.
-     * @param templateSource
-     *            The object returned by {@link 
TemplateLoader#findTemplateSource(String)}.
+     * @param templateLoadingSource
+     *            The object returned by {@link 
TemplateLoadingResult#getSource()}.
      * 
      * @return The {@link TemplateConfiguration} to apply, or {@code null} if 
the there's no {@link TemplateConfiguration} for
      *         this template source.
@@ -51,7 +51,7 @@ public abstract class TemplateConfigurationFactory {
      * @throws TemplateConfigurationFactoryException
      *             If there's a problem that's specific to the factory logic.
      */
-    public abstract TemplateConfiguration get(String sourceName, Object 
templateSource)
+    public abstract TemplateConfiguration get(String sourceName, 
TemplateLoadingSource templateLoadingSource)
             throws IOException, TemplateConfigurationFactoryException;
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLoader.java 
b/src/main/java/freemarker/cache/TemplateLoader.java
index bab7cae..ae82c71 100644
--- a/src/main/java/freemarker/cache/TemplateLoader.java
+++ b/src/main/java/freemarker/cache/TemplateLoader.java
@@ -16,50 +16,56 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package freemarker.cache;
 
 import java.io.IOException;
-import java.io.Reader;
+import java.io.Serializable;
 
 import freemarker.template.Configuration;
 import freemarker.template.TemplateNotFoundException;
 
 /**
- * FreeMarker loads template "files" through objects that implement this 
interface,
- * thus the templates need not be real files, and can come from any kind of 
data source
- * (like classpath, servlet context, database, etc). While FreeMarker provides 
a few
- * {@link TemplateLoader} implementations out-of-the-box, it's normal for 
embedding
- * frameworks to use their own implementations.
+ * FreeMarker loads template "files" through objects that implement this 
interface, thus the templates need not be real
+ * files, and can come from any kind of data source (like classpath, servlet 
context, database, etc). While FreeMarker
+ * provides a few template loader implementations out-of-the-box, it's normal 
for embedding frameworks to use their own
+ * implementations.
  * 
- * <p>To set the {@link TemplateLoader} used by FreeMaker, use
- * {@link Configuration#setTemplateLoader(TemplateLoader)}.
+ * <p>
+ * To set the {@link TemplateLoader} used by FreeMaker, use {@link 
Configuration#setTemplateLoader(TemplateLoader)}.
  * 
- * <p>Implementations of this interface should be thread-safe.
+ * <p>
+ * Implementations of this interface should be thread-safe.
  * 
- * <p>Implementations should override {@link Object#toString()} to show 
information about from where the
+ * <p>
+ * Implementations should override {@link Object#toString()} to show 
information about from where the
  * {@link TemplateLoader} loads the templates. For example, for a template 
loader that loads template from database
  * table {@code toString} could return something like
  * {@code "MyDatabaseTemplateLoader(user=\"cms\", table=\"mail_templates\")"}. 
This string will be shown in
  * {@link TemplateNotFoundException} exception messages, next to the template 
name.
  * 
- * <p>For those who has to dig deeper, note that the {@link TemplateLoader} is 
actually stored inside
- * the {@link TemplateCache} of the {@link Configuration}, and is normally 
only accessed directly
- * by the {@link TemplateCache}, and templates are get via the {@link 
TemplateCache} API-s.
+ * <p>
+ * For those who has to dig deeper, note that the {@link TemplateLoader} is 
actually stored inside the
+ * {@link TemplateCache} of the {@link Configuration}, and is normally only 
accessed directly by the
+ * {@link TemplateCache}, and templates are get via the {@link TemplateCache} 
API-s.
  */
 public interface TemplateLoader {
-       
+
+    /**
+     * Creates a new session, or returns {@code null} if the template loader 
implementation doesn't support sessions.
+     * See {@link TemplateLoaderSession} for more information about sessions.
+     */
+    TemplateLoaderSession createSession();
+    
     /**
-     * Finds the template in the backing storage and returns an object that 
identifies the storage location where the
-     * template can be loaded from. See the return value for more information.
-     *
+     * Loads the template content together with meta-data such as the version 
(usually the last modification time).
+     * 
      * @param name
-     *            The name of the template, already localized and normalized 
by the
-     *            {@link freemarker.cache.TemplateCache cache}. It is 
completely up to the loader implementation to
+     *            The name (template root directory relative path) of the 
template, already localized and normalized by
+     *            the {@link freemarker.cache.TemplateCache cache}. It is 
completely up to the loader implementation to
      *            interpret the name, however it should expect to receive 
hierarchical paths where path components are
      *            separated by a slash (not backslash). Backslashes (or any 
other OS specific separator character) are
      *            not considered as separators by FreeMarker, and thus they 
will not be replaced with slash before
-     *            passing to this method, so it's up to the template loader to 
handle them (say, be throwing and
+     *            passing to this method, so it's up to the template loader to 
handle them (say, by throwing an
      *            exception that tells the user that the path (s)he has 
entered is invalid, as (s)he must use slash --
      *            typical mistake of Windows users). The passed names are 
always considered relative to some
      *            loader-defined root location (often referred as the 
"template root directory"), and will never start
@@ -70,81 +76,27 @@ public interface TemplateLoader {
      *            uses backslash as path separator instead of slash as (the 
template loader should not accept that), the
      *            normalization will not properly happen, as FreeMarker (the 
cache) recognizes only the slashes as
      *            separators.
-     *
-     * @return An object representing the template source, which can be 
supplied in subsequent calls to
-     *         {@link #getLastModified(Object)} and {@link #getReader(Object, 
String)}, when those are called on the
-     *         same {@link TemplateLoader}. {@code null} must be returned if 
the source for the template doesn't exist;
-     *         don't throw exception then! The exact type of this object is up 
to the {@link TemplateLoader}
-     *         implementation. As this object is possibly used as hash key in 
caches, and is surly compared with another
-     *         template source for equality, <b>it must have a proper {@link 
Object#equals(Object)} and
-     *         {@link Object#hashCode()}) implementation</b>. Especially, 
template sources that refer to the same
-     *         physical source must be equivalent, otherwise template caching 
can become inefficient. This is only
-     *         expected from {@link Object#equals(Object)} when the compared 
template sources came from the same
-     *         {@link TemplateLoader} instance. Also, it must not influence 
the equality if the source is open or
-     *         closed ({@link #closeTemplateSource(Object)}). 
-     * 
-     * @throws IOException
-     *             When an error occurs that makes it impossible to find out 
if the template exists, or to access the
-     *             existing template. Don't throw exception if the template 
doesn't exist, instead return with
-     *             {@code null} then!
-     */
-    public Object findTemplateSource(String name)
-    throws IOException;
-        
-    /**
-     * Returns the time of last modification of the specified template source. 
This method is called after
-     * <code>findTemplateSource()</code>.
+     * @param ifSourceDiffersFrom
+     *            If we only want to load the template if its source differs 
from this. {@code null} if you want the
+     *            template to be loaded unconditionally. If this is {@code 
null} then the
+     *            {@code ifVersionDiffersFrom} parameter must be {@code null} 
too. See
+     *            {@link TemplateLoadingResult#getSource()} for more about 
sources.
+     * @param ifVersionDiffersFrom
+     *            If we only want to load the template if its version (which 
is usually the last modification time)
+     *            differs from this. {@code null} if {@code 
ifSourceDiffersFrom} is {@code null}, or if the backing
+     *            storage from which the {@code ifSourceDiffersFrom} template 
source comes from doesn't store a version.
+     *            See {@link TemplateLoadingResult#getVersion()} for more 
about versions.
      * 
-     * @param templateSource
-     *            an object representing a template source, obtained through a 
prior call to
-     *            {@link #findTemplateSource(String)}. This must be an object 
on which
-     *            {@link TemplateLoader#closeTemplateSource(Object)} wasn't 
applied yet.
-     * @return the time of last modification of the specified template source, 
or -1 if the time is not known.
+     * @return Not {@code null}.
      */
-    public long getLastModified(Object templateSource);
+    TemplateLoadingResult load(String name, TemplateLoadingSource 
ifSourceDiffersFrom, Serializable ifVersionDiffersFrom,
+            TemplateLoaderSession session) throws IOException;
     
     /**
-     * Returns the character stream of a template represented by the specified 
template source. This method is possibly
-     * called for multiple times for the same template source object, and it 
must always return a {@link Reader} that
-     * reads the template from its beginning. Before this method is called for 
the second time (or later), its caller
-     * must close the previously returned {@link Reader}, and it must not use 
it anymore. That is, this method is not
-     * required to support multiple concurrent readers for the same source 
{@code templateSource} object.
-     * 
-     * <p>
-     * Typically, this method is called if the template is missing from the 
cache, or if after calling
-     * {@link #findTemplateSource(String)} and {@link 
#getLastModified(Object)} it was determined that the cached copy
-     * of the template is stale. Then, if it turns out that the {@code 
encoding} parameter used doesn't match the actual
-     * template content (based on the {@code #ftl encoding=...} header), this 
method will be called for a second time
-     * with the correct {@code encoding} parameter value.
-     * 
-     * @param templateSource
-     *            an object representing a template source, obtained through a 
prior call to
-     *            {@link #findTemplateSource(String)}. This must be an object 
on which
-     *            {@link TemplateLoader#closeTemplateSource(Object)} wasn't 
applied yet.
-     * @param encoding
-     *            the character encoding used to translate source bytes to 
characters. Some loaders may not have access
-     *            to the byte representation of the template stream, and 
instead directly obtain a character stream.
-     *            These loaders should ignore the encoding parameter.
-     * 
-     * @return A {@link Reader} representing the template character stream. 
It's the responsibility of the caller (which
-     *         is {@link TemplateCache} usually) to {@code close()} it. The 
{@link Reader} is not required to work after
-     *         the {@code templateSource} was closed ({@link 
#closeTemplateSource(Object)}).
-     * 
-     * @throws IOException
-     *             if an I/O error occurs while accessing the stream.
+     * Invoked by {@link Configuration#clearTemplateCache()} to instruct this 
template loader to throw away its current
+     * state (some kind of cache usually) and start afresh. For most {@link 
TemplateLoader} implementations this does
+     * nothing.
      */
-    public Reader getReader(Object templateSource, String encoding)
-    throws IOException;
-    
-    /**
-     * Closes the template source, releasing any resources held that are only 
required for reading the template and/or
-     * its metadata. This is the last method that is called by the {@link 
TemplateCache} for a template source, except
-     * that {@link Object#equals(Object)} is might called later too. {@link 
TemplateCache} ensures that this method will
-     * be called on every object that is returned from {@link 
#findTemplateSource(String)}.
-     * 
-     * @param templateSource
-     *            the template source that should be closed.
-     */
-    public void closeTemplateSource(Object templateSource)
-    throws IOException;
+    void resetState();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLoaderSession.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLoaderSession.java 
b/src/main/java/freemarker/cache/TemplateLoaderSession.java
new file mode 100644
index 0000000..318fe11
--- /dev/null
+++ b/src/main/java/freemarker/cache/TemplateLoaderSession.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Stores shared state between {@link TemplateLoader} operations that are 
executed close to each other in the same
+ * thread. For example, a {@link TemplateLoader} that reads from a database 
might wants to store the database
+ * connection in it for reuse. The goal of sessions is mostly to increase 
performance. However, because a
+ * {@link TemplateCache#getTemplate(String, java.util.Locale, Object, String, 
boolean)} call is executed inside a single
+ * session, sessions can be also be utilized to ensure that the template 
lookup (see {@link TemplateLookupStrategy})
+ * happens on a consistent view (a snapshot) of the backing storage, if the 
backing storage mechanism supports such
+ * thing.
+ * 
+ * <p>
+ * The {@link TemplateLoaderSession} implementation is (usually) specific to 
the {@link TemplateLoader}
+ * implementation. If your {@link TemplateLoader} implementation can't take 
advantage of sessions, you don't have to
+ * implement this interface, just return {@code null} for {@link 
TemplateLoader#createSession()}.
+ * 
+ * <p>
+ * {@link TemplateLoaderSession}-s should be lazy, that is, creating an 
instance should be very fast and should not
+ * cause I/O. Only when (and if ever) the shared resource stored in the 
session is needed for the first time should the
+ * shared resource be initialized.
+ *
+ * <p>
+ * {@link TemplateLoaderSession}-s need not be thread safe.
+ */
+public interface TemplateLoaderSession {
+
+    /**
+     * Closes this session, freeing any resources it holds. Further operations 
involving this session should fail, with
+     * the exception of {@link #close()} itself, which should be silently 
ignored.
+     * 
+     * <p>
+     * The {@link Reader} or {@link InputStream} contained in the {@link 
TemplateLoadingResult} must be closed before
+     * {@link #close()} is called on the session in which the {@link 
TemplateLoadingResult} was created. Except, if
+     * closing the {@link Reader} or {@link InputStream} has thrown an 
exception, the caller should just proceed with
+     * closing the session regardless. After {@link #close()} was called on 
the session, the methods of the
+     * {@link Reader} or {@link InputStream} is allowed to throw an exception, 
or behave in any other erratic way.
+     * (Because the caller of this interface is usually FreeMarker (the {@link 
TemplateCache}), normally you don't have
+     * to deal with these rules, but it's useful to know the expectations if 
you implement
+     * {@link TemplateLoaderSession}.)
+     * 
+     * <p>The caller of {@link TemplateLoader#createSession()} has to 
guarantee that {@link #close()} will be called on
+     * the created session.
+     */
+    public void close() throws IOException;
+    
+    /**
+     * Tells if this session is closed.
+     */
+    public boolean isClosed();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLoaderUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLoaderUtils.java 
b/src/main/java/freemarker/cache/TemplateLoaderUtils.java
index e229e76..2c98053 100644
--- a/src/main/java/freemarker/cache/TemplateLoaderUtils.java
+++ b/src/main/java/freemarker/cache/TemplateLoaderUtils.java
@@ -28,11 +28,10 @@ final class TemplateLoaderUtils {
     }
 
     public static String getClassNameForToString(TemplateLoader 
templateLoader) {
-        final Class tlClass = templateLoader.getClass();
+        final Class<? extends TemplateLoader> tlClass = 
templateLoader.getClass();
         final Package tlPackage = tlClass.getPackage();
         return tlPackage == Configuration.class.getPackage() || tlPackage == 
TemplateLoader.class.getPackage()
                 ? tlClass.getSimpleName() : tlClass.getName();
     }
-
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLoadingResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLoadingResult.java 
b/src/main/java/freemarker/cache/TemplateLoadingResult.java
new file mode 100644
index 0000000..87dcac9
--- /dev/null
+++ b/src/main/java/freemarker/cache/TemplateLoadingResult.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.cache;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.util.Date;
+
+import freemarker.core.TemplateConfiguration;
+import freemarker.template.Configuration;
+import freemarker.template.utility.NullArgumentException;
+
+/**
+ * Return value of {@link TemplateLoader#load(String, TemplateLoadingSource, 
Serializable, TemplateLoaderSession)}
+ */
+public final class TemplateLoadingResult {
+    private final TemplateLoadingResultStatus status;
+    private final TemplateLoadingSource source;
+    private final Serializable version;
+    private final Reader reader;
+    private final InputStream inputStream;
+    private final TemplateConfiguration templateConfiguration; 
+
+    public static final TemplateLoadingResult NOT_FOUND = new 
TemplateLoadingResult(
+            TemplateLoadingResultStatus.NOT_FOUND);
+    public static final TemplateLoadingResult NOT_MODIFIED = new 
TemplateLoadingResult(
+            TemplateLoadingResultStatus.NOT_MODIFIED);
+
+    /**
+     * Creates an instance with status {@link 
TemplateLoadingResultStatus#OPENED}, for a storage mechanism that
+     * naturally returns the template content as sequence of {@code char}-s as 
opposed to a sequence of {@code byte}-s.
+     * This is the case for example when you store the template in a database 
in a varchar or CLOB. Do <em>not</em> use
+     * this constructor for stores that naturally return binary data instead 
(like files, class loader resources,
+     * BLOB-s, etc.), because using this constructor will disable FreeMarker's 
charset selection mechanism.
+     * 
+     * @param source
+     *            See {@link #getSource()}
+     * @param version
+     *            See {@link #getVersion()} for the meaning of this. Can be 
{@code null}, but use that only if the
+     *            backing storage mechanism doesn't know this information.
+     * @param reader
+     *            Gives the content of the template
+     * @param templateConfiguration
+     *            Usually {@code null}, as usually the backing storage 
mechanism doesn't store such information;
+     *            see {@link #getTemplateConfiguration()}.
+     */
+    public TemplateLoadingResult(TemplateLoadingSource source, Serializable 
version, Reader reader,
+            TemplateConfiguration templateConfiguration) {
+        NullArgumentException.check("templateSource", source);
+        NullArgumentException.check("reader", reader);
+        this.status = TemplateLoadingResultStatus.OPENED;
+        this.source = source;
+        this.version = version;
+        this.reader = reader;
+        this.inputStream = null;
+        this.templateConfiguration = templateConfiguration; 
+    }
+
+    /**
+     * Creates an instance with status {@link 
TemplateLoadingResultStatus#OPENED}, for a storage mechanism that
+     * naturally returns the template content as sequence of {@code byte}-s as 
opposed to a sequence of {@code char}-s.
+     * This is the case for example when you store the template in a file, 
classpath resource, or BLOB. Do <em>not</em>
+     * use this constructor for stores that naturally return text instead 
(like database varchar and CLOB columns).
+     * 
+     * @param source
+     *            See {@link #getSource()}
+     * @param version
+     *            See {@link #getVersion()} for the meaning of this. Can be 
{@code null}, but use that only if the
+     *            backing storage mechanism doesn't know this information.
+     * @param inputStream
+     *            Gives the content of the template
+     * @param templateConfiguration
+     *            Usually {@code null}, as usually the backing storage 
mechanism doesn't store such information; see
+     *            {@link #getTemplateConfiguration()}. The most probable 
application is supplying the charset (encoding)
+     *            used by the {@link InputStream} (via {@link 
TemplateConfiguration#setEncoding(String)}), but only
+     *            do that if the storage mechanism really knows what the 
charset is.
+     */
+    public TemplateLoadingResult(TemplateLoadingSource source, Serializable 
version, InputStream inputStream,
+            TemplateConfiguration templateConfiguration) {
+        NullArgumentException.check("templateSource", source);
+        NullArgumentException.check("inputStream", inputStream);
+        this.status = TemplateLoadingResultStatus.OPENED;
+        this.source = source;
+        this.version = version;
+        this.reader = null;
+        this.inputStream = inputStream;
+        this.templateConfiguration = templateConfiguration; 
+    }
+
+    /**
+     * Used internally for creating the singleton instances which has a state 
where all other fields are {@code null}.
+     */
+    private TemplateLoadingResult(TemplateLoadingResultStatus status) {
+        this.status = status;
+        this.source = null;
+        this.version = null;
+        this.reader = null;
+        this.inputStream = null;
+        this.templateConfiguration = null;
+    }
+
+    /**
+     * Returns non-{@code null} exactly if {@link #getStatus()} is {@link 
TemplateLoadingResultStatus#OPENED} and the
+     * backing store mechanism returns content as {@code byte}-s, as opposed 
to as {@code chars}-s. See also
+     * {@link #TemplateLoadingResult(TemplateLoadingSource, Serializable, 
InputStream, TemplateConfiguration)}. It's the
+     * responsibility of the caller (which is {@link TemplateCache} usually) 
to {@code close()} the {@link InputStream}.
+     * The return value is always the same instance, no mater when and how 
many times this method is called.
+     * 
+     * <p>
+     * The return {@code InputStream} should use buffering if that's useful 
considering the backing storage mechanism.
+     * TODO Is it really needed?
+     * 
+     * @return {@code null} or a {@code InputStream} to read the template 
content; see method description for more.
+     */
+    public InputStream getInputStream() {
+        return inputStream;
+    }
+
+    /**
+     * Tells what kind of result this is; see the documentation of {@link 
TemplateLoadingResultStatus}.
+     */
+    public TemplateLoadingResultStatus getStatus() {
+        return status;
+    }
+
+    /**
+     * Identifies the source on the level of the storage mechanism; stored in 
the cache together with the version
+     * ({@link #getVersion()}). When checking if a cache entry is up to date, 
the sources are compared, and only if they
+     * are equal are the versions compared. See more at {@link 
TemplateLoadingSource}.
+     */
+    public TemplateLoadingSource getSource() {
+        return source;
+    }
+
+    /**
+     * If the result status is {@link TemplateLoadingResultStatus#OPENED} and 
the backing storage stores such
+     * information, the version (usually the last modification time) of the 
loaded template, otherwise {@code null}. The
+     * version is some kind of value which changes when the template in the 
backing storage is updated. Usually, it's
+     * the last modification time (a {@link Date} or {@link Long}), though 
that can be problematic if the template can
+     * change twice within the granularity of the clock used by the storage. 
Thus some storages may use a revision
+     * number instead that's always increased when the template is updated, or 
the cryptographic hash of the template
+     * content as the version. Version objects are compared with each other 
with their {@link Object#equals(Object)}
+     * method, to see if a cache entry is outdated (though only when the 
source objects ({@link #getSource()}) are
+     * equal). Thus, the version object must have proper {@link 
Object#equals(Object)} and {@link Object#hashCode()}
+     * methods.
+     */
+    public Serializable getVersion() {
+        return version;
+    }
+
+    /**
+     * Returns non-{@code null} exactly if {@link #getStatus()} is {@link 
TemplateLoadingResultStatus#OPENED} and the
+     * backing store mechanism returns content as {@code char}-s, as opposed 
to as {@code byte}-s. See also
+     * {@link #TemplateLoadingResult(TemplateLoadingSource, Serializable, 
Reader, TemplateConfiguration)}. It's the
+     * responsibility of the caller (which is {@link TemplateCache} usually) 
to {@code close()} the {@link Reader}. The
+     * return value is always the same instance, no mater when and how many 
times this method is called.
+     * 
+     * @return {@code null} or a {@code Reader} to read the template content; 
see method description for more.
+     */
+    public Reader getReader() {
+        return reader;
+    }
+
+    /**
+     * If {@link #getStatus()} is {@link TemplateLoadingResultStatus#OPENED}, 
and the template loader stores such
+     * information (which is rare) then it returns the {@link 
TemplateConfiguration} applicable to the template,
+     * otherwise it returns {@code null}. If {@link #getStatus()} is not 
{@link TemplateLoadingResultStatus#OPENED},
+     * then this should always return {@code null}. If there are {@link 
TemplateConfiguration}-s coming from other
+     * sources, such as from {@link 
Configuration#getTemplateConfigurations()}, this won't replace them, but will be
+     * merged with them, with properties coming from the returned {@link 
TemplateConfiguration} having the highest
+     * priority.
+     * 
+     * @return {@code null}, or a {@link TemplateConfiguration}. The parent 
configuration of the
+     *         {@link TemplateConfiguration} need not be set. The returned 
{@link TemplateConfiguration} won't be
+     *         modified. (If the caller needs to modify it, such as to call
+     *         {@link 
TemplateConfiguration#setParentConfiguration(Configuration)}, it has to copy it 
first.)
+     */
+    public TemplateConfiguration getTemplateConfiguration() {
+        return templateConfiguration;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLoadingResultStatus.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLoadingResultStatus.java 
b/src/main/java/freemarker/cache/TemplateLoadingResultStatus.java
new file mode 100644
index 0000000..6ea5e50
--- /dev/null
+++ b/src/main/java/freemarker/cache/TemplateLoadingResultStatus.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.cache;
+
+import java.io.Serializable;
+
+/**
+ * Used for the value of {@link TemplateLoadingResult#getStatus()}.
+ */
+public enum TemplateLoadingResultStatus {
+
+    /**
+     * The template with the requested name doesn't exist (not to be confused 
with "wasn't accessible due to error"). If
+     * there was and error because of which we can't know for sure if the 
template is there or not (for example we
+     * couldn't access the backing store due to a network connection error or 
other unexpected I/O error or
+     * authorization problem), this value must not be used, instead an 
exception should be thrown by
+     * {@link TemplateLoader#load(String, TemplateLoadingSource, Serializable, 
TemplateLoaderSession)}.
+     */
+    NOT_FOUND,
+
+    /**
+     * If the template was found, but its source and version is the same as 
that which was provided to
+     * {@link TemplateLoader#load(String, TemplateLoadingSource, Serializable, 
TemplateLoaderSession)} (from a cache
+     * presumably), so its content wasn't opened for reading.
+     */
+    NOT_MODIFIED,
+
+    /**
+     * If the template was found and its content is ready for reading.
+     */
+    OPENED
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLoadingSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLoadingSource.java 
b/src/main/java/freemarker/cache/TemplateLoadingSource.java
new file mode 100644
index 0000000..ce84d24
--- /dev/null
+++ b/src/main/java/freemarker/cache/TemplateLoadingSource.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * The point of {@link TemplateLoadingSource} is that with their {@link 
Object#equals(Object)} method we can tell if two
+ * cache entries were generated from the same physical resource or not. 
Comparing the template names isn't enough,
+ * because a {@link TemplateLoader} may uses some kind of fallback mechanism, 
such as delegating to other
+ * {@link TemplateLoader}-s until the template is found. Like if we have two 
{@link FileTemplateLoader}-s with different
+ * physical root directories, both can contain {@code "foo/bar.ftl"}, but 
obviously the two files aren't the same.
+ * 
+ * <p>
+ * When implementing this interface, check these:
+ * 
+ * <ul>
+ * <li>{@link Object#equals(Object)} must not be based on object identity, 
because two instances of
+ * {@link TemplateLoadingSource} that describe the same resource must be 
equivalent.
+ * 
+ * <li>Each {@link TemplateLoader} implementation should have its own {@link 
TemplateLoadingSource} implementation, so
+ * that {@link TemplateLoadingSource}-s coming from different {@link 
TemplateLoader} implementations can't be
+ * accidentally equal (according to {@link Object#equals(Object)}).
+ * 
+ * <li>{@link Object#equals(Object)} must still work properly if there are 
multiple instances of the same
+ * {@link TemplateLoader} implementation. Like if you have an implementation 
that loads from a database table, the
+ * {@link TemplateLoadingSource} should certain contain the JDBC connection 
string, the table name and the row ID, not
+ * just the row ID.
+ * 
+ * <li>Together with {@link Object#equals(Object)}, {@link Object#hashCode()} 
must be also overridden. The template
+ * source may be used as a {@link HashMap} key.
+ * 
+ * <li>Because {@link TemplateLoadingSource}-s are {@link Serializable}, they 
can't contain non-{@link Serializable}
+ * fields. Most notably, a reference to the creator {@link TemplateLoader}, so 
if it's an inner class of the
+ * {@link TemplateLoader}, it should be static.
+ * 
+ * <li>Consider that cache entries, in which the source is stored, may move 
between JVM-s (because of clustering with a
+ * distributed cache). Thus they should remain meaningful for the purpose of 
{@link Object#equals(Object)} even in
+ * another JVM.
+ * 
+ * <li>A {@link TemplateLoader} may chose not to support distributed caches, 
like {@link ByteArrayTemplateLoader}
+ * doesn't support that for example. In that case the serialization related 
points above can be ignored, but the
+ * {@link TemplateLoadingSource} implementation should define the {@code 
writeObject} method (a Java serialization
+ * feature) and throw an exception there to block serialization attempts.
+ * </ul>
+ */
+public interface TemplateLoadingSource extends Serializable {
+    // Empty
+}

Reply via email to