http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
new file mode 100644
index 0000000..62fe137
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/MruCacheStorage.java
@@ -0,0 +1,325 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
+
+/**
+ * A cache storage that implements a two-level Most Recently Used cache. In the
+ * first level, items are strongly referenced up to the specified maximum. When
+ * the maximum is exceeded, the least recently used item is moved into the  
+ * second level cache, where they are softly referenced, up to another 
+ * specified maximum. When the second level maximum is also exceeded, the 
least 
+ * recently used item is discarded altogether. This cache storage is a 
+ * generalization of both {@link StrongCacheStorage} and 
+ * {@link SoftCacheStorage} - the effect of both of them can be achieved by 
+ * setting one maximum to zero and the other to the largest positive integer. 
+ * On the other hand, if you wish to use this storage in a strong-only mode, or
+ * in a soft-only mode, you might consider using {@link StrongCacheStorage} or
+ * {@link SoftCacheStorage} instead, as they can be used by 
+ * {@link DefaultTemplateResolver} concurrently without any synchronization on 
a 5.0 or 
+ * later JRE.
+ *  
+ * <p>This class is <em>NOT</em> thread-safe. If it's accessed from multiple
+ * threads concurrently, proper synchronization must be provided by the 
callers.
+ * Note that {@link DefaultTemplateResolver}, the natural user of this class 
provides the
+ * necessary synchronizations when it uses the class.
+ * Also you might consider whether you need this sort of a mixed storage at all
+ * in your solution, as in most cases SoftCacheStorage can also be sufficient. 
+ * SoftCacheStorage will use Java soft references, and they already use access 
+ * timestamps internally to bias the garbage collector against clearing 
+ * recently used references, so you can get reasonably good (and 
+ * memory-sensitive) most-recently-used caching through 
+ * {@link SoftCacheStorage} as well.
+ *
+ * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ */
+public class MruCacheStorage implements CacheStorageWithGetSize {
+    private final MruEntry strongHead = new MruEntry();
+    private final MruEntry softHead = new MruEntry();
+    {
+        softHead.linkAfter(strongHead);
+    }
+    private final Map map = new HashMap();
+    private final ReferenceQueue refQueue = new ReferenceQueue();
+    private final int strongSizeLimit;
+    private final int softSizeLimit;
+    private int strongSize = 0;
+    private int softSize = 0;
+    
+    /**
+     * Creates a new MRU cache storage with specified maximum cache sizes. Each
+     * cache size can vary between 0 and {@link Integer#MAX_VALUE}.
+     * @param strongSizeLimit the maximum number of strongly referenced 
templates; when exceeded, the entry used
+     *          the least recently will be moved into the soft cache.
+     * @param softSizeLimit the maximum number of softly referenced templates; 
when exceeded, the entry used
+     *          the least recently will be discarded.
+     */
+    public MruCacheStorage(int strongSizeLimit, int softSizeLimit) {
+        if (strongSizeLimit < 0) throw new 
IllegalArgumentException("strongSizeLimit < 0");
+        if (softSizeLimit < 0) throw new 
IllegalArgumentException("softSizeLimit < 0");
+        this.strongSizeLimit = strongSizeLimit;
+        this.softSizeLimit = softSizeLimit;
+    }
+    
+    public Object get(Object key) {
+        removeClearedReferences();
+        MruEntry entry = (MruEntry) map.get(key);
+        if (entry == null) {
+            return null;
+        }
+        relinkEntryAfterStrongHead(entry, null);
+        Object value = entry.getValue();
+        if (value instanceof MruReference) {
+            // This can only happen with strongSizeLimit == 0
+            return ((MruReference) value).get();
+        }
+        return value;
+    }
+
+    public void put(Object key, Object value) {
+        removeClearedReferences();
+        MruEntry entry = (MruEntry) map.get(key);
+        if (entry == null) {
+            entry = new MruEntry(key, value);
+            map.put(key, entry);
+            linkAfterStrongHead(entry);
+        } else {
+            relinkEntryAfterStrongHead(entry, value);
+        }
+        
+    }
+
+    public void remove(Object key) {
+        removeClearedReferences();
+        removeInternal(key);
+    }
+
+    private void removeInternal(Object key) {
+        MruEntry entry = (MruEntry) map.remove(key);
+        if (entry != null) {
+            unlinkEntryAndInspectIfSoft(entry);
+        }
+    }
+
+    public void clear() {
+        strongHead.makeHead();
+        softHead.linkAfter(strongHead);
+        map.clear();
+        strongSize = softSize = 0;
+        // Quick refQueue processing
+        while (refQueue.poll() != null);
+    }
+
+    private void relinkEntryAfterStrongHead(MruEntry entry, Object newValue) {
+        if (unlinkEntryAndInspectIfSoft(entry) && newValue == null) {
+            // Turn soft reference into strong reference, unless is was cleared
+            MruReference mref = (MruReference) entry.getValue();
+            Object strongValue = mref.get();
+            if (strongValue != null) {
+                entry.setValue(strongValue);
+                linkAfterStrongHead(entry);
+            } else {
+                map.remove(mref.getKey());
+            }
+        } else {
+            if (newValue != null) {
+                entry.setValue(newValue);
+            }
+            linkAfterStrongHead(entry);
+        }
+    }
+
+    private void linkAfterStrongHead(MruEntry entry) {
+        entry.linkAfter(strongHead);
+        if (strongSize == strongSizeLimit) {
+            // softHead.previous is LRU strong entry
+            MruEntry lruStrong = softHead.getPrevious();
+            // Attila: This is equaivalent to strongSizeLimit != 0
+            // DD: But entry.linkAfter(strongHead) was just executed above, so
+            //     lruStrong != strongHead is true even if strongSizeLimit == 
0.
+            if (lruStrong != strongHead) {
+                lruStrong.unlink();
+                if (softSizeLimit > 0) {
+                    lruStrong.linkAfter(softHead);
+                    lruStrong.setValue(new MruReference(lruStrong, refQueue));
+                    if (softSize == softSizeLimit) {
+                        // List is circular, so strongHead.previous is LRU 
soft entry
+                        MruEntry lruSoft = strongHead.getPrevious();
+                        lruSoft.unlink();
+                        map.remove(lruSoft.getKey());
+                    } else {
+                        ++softSize;
+                    }
+                } else {
+                    map.remove(lruStrong.getKey());
+                }
+            }
+        } else {
+            ++strongSize;
+        }
+    }
+
+    private boolean unlinkEntryAndInspectIfSoft(MruEntry entry) {
+        entry.unlink();
+        if (entry.getValue() instanceof MruReference) {
+            --softSize;
+            return true;
+        } else {
+            --strongSize;
+            return false;
+        }
+    }
+    
+    private void removeClearedReferences() {
+        for (; ; ) {
+            MruReference ref = (MruReference) refQueue.poll();
+            if (ref == null) {
+                break;
+            }
+            removeInternal(ref.getKey());
+        }
+    }
+    
+    /**
+     * Returns the configured upper limit of the number of strong cache 
entries.
+     *  
+     * @since 2.3.21
+     */
+    public int getStrongSizeLimit() {
+        return strongSizeLimit;
+    }
+
+    /**
+     * Returns the configured upper limit of the number of soft cache entries.
+     * 
+     * @since 2.3.21
+     */
+    public int getSoftSizeLimit() {
+        return softSizeLimit;
+    }
+
+    /**
+     * Returns the <em>current</em> number of strong cache entries.
+     *  
+     * @see #getStrongSizeLimit()
+     * @since 2.3.21
+     */
+    public int getStrongSize() {
+        return strongSize;
+    }
+
+    /**
+     * Returns a close approximation of the <em>current</em> number of soft 
cache entries.
+     * 
+     * @see #getSoftSizeLimit()
+     * @since 2.3.21
+     */
+    public int getSoftSize() {
+        removeClearedReferences();
+        return softSize;
+    }
+    
+    /**
+     * Returns a close approximation of the current number of cache entries.
+     * 
+     * @see #getStrongSize()
+     * @see #getSoftSize()
+     * @since 2.3.21
+     */
+    public int getSize() {
+        return getSoftSize() + getStrongSize();
+    }
+
+    private static final class MruEntry {
+        private MruEntry prev;
+        private MruEntry next;
+        private final Object key;
+        private Object value;
+        
+        /**
+         * Used solely to construct the head element
+         */
+        MruEntry() {
+            makeHead();
+            key = value = null;
+        }
+        
+        MruEntry(Object key, Object value) {
+            this.key = key;
+            this.value = value;
+        }
+        
+        Object getKey() {
+            return key;
+        }
+        
+        Object getValue() {
+            return value;
+        }
+        
+        void setValue(Object value) {
+            this.value = value;
+        }
+
+        MruEntry getPrevious() {
+            return prev;
+        }
+        
+        void linkAfter(MruEntry entry) {
+            next = entry.next;
+            entry.next = this;
+            prev = entry;
+            next.prev = this;
+        }
+        
+        void unlink() {
+            next.prev = prev;
+            prev.next = next;
+            prev = null;
+            next = null;
+        }
+        
+        void makeHead() {
+            prev = next = this;
+        }
+    }
+    
+    private static class MruReference extends SoftReference {
+        private final Object key;
+        
+        MruReference(MruEntry entry, ReferenceQueue queue) {
+            super(entry.getValue(), queue);
+            this.key = entry.getKey();
+        }
+        
+        Object getKey() {
+            return key;
+        }
+    }
+    
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/MultiTemplateLoader.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/MultiTemplateLoader.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/MultiTemplateLoader.java
new file mode 100644
index 0000000..f7841fb
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/MultiTemplateLoader.java
@@ -0,0 +1,172 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoaderSession;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResultStatus;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
+import org.apache.freemarker.core.util.NullArgumentException;
+
+/**
+ * A {@link TemplateLoader} that uses a set of other loaders to load the 
templates. On every request, loaders are
+ * queried in the order of their appearance in the array of loaders provided 
to the constructor. Except, when the
+ * {@linkplain #setSticky(boolean)} sticky} setting is set to {@code true} 
(default is false {@code false}), if
+ * a request for some template name was already satisfied in the past by one 
of the loaders, that loader is queried
+ * first (stickiness).
+ * 
+ * <p>This class is thread-safe.
+ */
+// TODO JUnit test
+public class MultiTemplateLoader implements TemplateLoader {
+
+    private final TemplateLoader[] templateLoaders;
+    private final Map<String, TemplateLoader> lastTemplateLoaderForName = new 
ConcurrentHashMap<String, TemplateLoader>();
+    
+    private boolean sticky = false;
+
+    /**
+     * Creates a new instance that will use the specified template loaders.
+     * 
+     * @param templateLoaders
+     *            the template loaders that are used to load templates, in the 
order as they will be searched
+     *            (except where {@linkplain #setSticky(boolean) stickiness} 
says otherwise).
+     */
+    public MultiTemplateLoader(TemplateLoader... templateLoaders) {
+        NullArgumentException.check("templateLoaders", templateLoaders);
+        this.templateLoaders = templateLoaders.clone();
+    }
+
+    /**
+     * Clears the sickiness memory, also resets the state of all enclosed 
{@link TemplateLoader}-s.
+     */
+    @Override
+    public void resetState() {
+        lastTemplateLoaderForName.clear();
+        for (TemplateLoader templateLoader : templateLoaders) {
+            templateLoader.resetState();
+        }
+    }
+
+    /**
+     * Show class name and some details that are useful in template-not-found 
errors.
+     * 
+     * @since 2.3.21
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("MultiTemplateLoader(");
+        for (int i = 0; i < templateLoaders.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append("loader").append(i + 1).append(" = 
").append(templateLoaders[i]);
+        }
+        sb.append(")");
+        return sb.toString();
+    }
+
+    /**
+     * Returns the number of {@link TemplateLoader}-s directly inside this 
{@link TemplateLoader}.
+     * 
+     * @since 2.3.23
+     */
+    public int getTemplateLoaderCount() {
+        return templateLoaders.length;
+    }
+
+    /**
+     * Returns the {@link TemplateLoader} at the given index.
+     * 
+     * @param index
+     *            Must be below {@link #getTemplateLoaderCount()}.
+     */
+    public TemplateLoader getTemplateLoader(int index) {
+        return templateLoaders[index];
+    }
+
+    /**
+     * Getter pair of {@link #setSticky(boolean)}.
+     */
+    public boolean isSticky() {
+        return sticky;
+    }
+
+    /**
+     * Sets if for a name that was already loaded earlier the same {@link 
TemplateLoader} will be tried first, or
+     * we always try the {@link TemplateLoader}-s strictly in the order as it 
was specified in the constructor.
+     * The default is {@code false}.
+     */
+    public void setSticky(boolean sticky) {
+        this.sticky = sticky;
+    }
+
+    @Override
+    public TemplateLoaderSession createSession() {
+        return null;
+    }
+
+    @Override
+    public TemplateLoadingResult load(String name, TemplateLoadingSource 
ifSourceDiffersFrom,
+            Serializable ifVersionDiffersFrom, TemplateLoaderSession session) 
throws IOException {
+        TemplateLoader lastLoader = null;
+        if (sticky) {
+            // Use soft affinity - give the loader that last found this
+            // resource a chance to find it again first.
+            lastLoader = lastTemplateLoaderForName.get(name);
+            if (lastLoader != null) {
+                TemplateLoadingResult result = lastLoader.load(name, 
ifSourceDiffersFrom, ifVersionDiffersFrom, session);
+                if (result.getStatus() != 
TemplateLoadingResultStatus.NOT_FOUND) {
+                    return result;
+                }
+            }
+        }
+
+        // If there is no affine loader, or it could not find the resource
+        // again, try all loaders in order of appearance. If any manages
+        // to find the resource, then associate it as the new affine loader
+        // for this resource.
+        for (TemplateLoader templateLoader : templateLoaders) {
+            if (lastLoader != templateLoader) {
+                TemplateLoadingResult result = templateLoader.load(
+                        name, ifSourceDiffersFrom, ifVersionDiffersFrom, 
session);
+                if (result.getStatus() != 
TemplateLoadingResultStatus.NOT_FOUND) {
+                    if (sticky) {
+                        lastTemplateLoaderForName.put(name, templateLoader);
+                    }
+                    return result;
+                }
+            }
+        }
+
+        if (sticky) {
+            lastTemplateLoaderForName.remove(name);
+        }
+        return TemplateLoadingResult.NOT_FOUND;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
new file mode 100644
index 0000000..9bc24eb
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/NullCacheStorage.java
@@ -0,0 +1,70 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
+import org.apache.freemarker.core.templateresolver.ConcurrentCacheStorage;
+
+/**
+ * A cache storage that doesn't store anything. Use this if you
+ * don't want caching.
+ *
+ * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ * 
+ * @since 2.3.17
+ */
+public class NullCacheStorage implements ConcurrentCacheStorage, 
CacheStorageWithGetSize {
+    
+    /**
+     * @since 2.3.22
+     */
+    public static final NullCacheStorage INSTANCE = new NullCacheStorage();
+
+    public boolean isConcurrent() {
+        return true;
+    }
+    
+    public Object get(Object key) {
+        return null;
+    }
+
+    public void put(Object key, Object value) {
+        // do nothing
+    }
+
+    public void remove(Object key) {
+        // do nothing
+    }
+    
+    public void clear() {
+        // do nothing
+    }
+
+    /**
+     * Always returns 0.
+     * 
+     * @since 2.3.21
+     */
+    public int getSize() {
+        return 0;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
new file mode 100644
index 0000000..cb0d963
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/SoftCacheStorage.java
@@ -0,0 +1,144 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
+import org.apache.freemarker.core.templateresolver.ConcurrentCacheStorage;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ * Soft cache storage is a cache storage that uses {@link SoftReference} 
objects to hold the objects it was passed,
+ * therefore allows the garbage collector to purge the cache when it 
determines that it wants to free up memory. This
+ * class is thread-safe to the extent that its underlying map is. The 
parameterless constructor uses a thread-safe map
+ * since 2.3.24 or Java 5.
+ *
+ * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ */
+public class SoftCacheStorage implements ConcurrentCacheStorage, 
CacheStorageWithGetSize {
+    private static final Method atomicRemove = getAtomicRemoveMethod();
+    
+    private final ReferenceQueue queue = new ReferenceQueue();
+    private final Map map;
+    private final boolean concurrent;
+    
+    /**
+     * Creates an instance that uses a {@link ConcurrentMap} internally.
+     */
+    public SoftCacheStorage() {
+        this(new ConcurrentHashMap());
+    }
+    
+    /**
+     * Returns true if the underlying Map is a {@code ConcurrentMap}.
+     */
+    public boolean isConcurrent() {
+        return concurrent;
+    }
+    
+    public SoftCacheStorage(Map backingMap) {
+        map = backingMap;
+        this.concurrent = map instanceof ConcurrentMap;
+    }
+    
+    public Object get(Object key) {
+        processQueue();
+        Reference ref = (Reference) map.get(key);
+        return ref == null ? null : ref.get();
+    }
+
+    public void put(Object key, Object value) {
+        processQueue();
+        map.put(key, new SoftValueReference(key, value, queue));
+    }
+
+    public void remove(Object key) {
+        processQueue();
+        map.remove(key);
+    }
+
+    public void clear() {
+        map.clear();
+        processQueue();
+    }
+    
+    /**
+     * Returns a close approximation of the number of cache entries.
+     * 
+     * @since 2.3.21
+     */
+    public int getSize() {
+        processQueue();
+        return map.size();
+    }
+
+    private void processQueue() {
+        for (; ; ) {
+            SoftValueReference ref = (SoftValueReference) queue.poll();
+            if (ref == null) {
+                return;
+            }
+            Object key = ref.getKey();
+            if (concurrent) {
+                try {
+                    atomicRemove.invoke(map, new Object[] { key, ref });
+                } catch (IllegalAccessException e) {
+                    throw new UndeclaredThrowableException(e);
+                } catch (InvocationTargetException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+            } else if (map.get(key) == ref) {
+                map.remove(key);
+            }
+        }
+    }
+
+    private static final class SoftValueReference extends SoftReference {
+        private final Object key;
+
+        SoftValueReference(Object key, Object value, ReferenceQueue queue) {
+            super(value, queue);
+            this.key = key;
+        }
+
+        Object getKey() {
+            return key;
+        }
+    }
+    
+    private static Method getAtomicRemoveMethod() {
+        try {
+            return 
Class.forName("java.util.concurrent.ConcurrentMap").getMethod("remove", new 
Class[] { Object.class, Object.class });
+        } catch (ClassNotFoundException e) {
+            return null;
+        } catch (NoSuchMethodException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.java
new file mode 100644
index 0000000..f56ac32
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/StringTemplateLoader.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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoaderSession;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
+import org.apache.freemarker.core.util.StringUtil;
+
+/**
+ * A {@link TemplateLoader} that uses a {@link Map} with {@code String} as its 
source of templates. This is similar to
+ * {@link StringTemplateLoader}, but uses {@code String} instead of {@link 
String}; see more details there.
+ * 
+ * <p>Note that {@link StringTemplateLoader} can't be used with a distributed 
(cluster-wide) {@link CacheStorage},
+ * as it produces {@link TemplateLoadingSource}-s that deliberately throw 
exception on serialization (because the
+ * content is only accessible within a single JVM, and is also volatile).
+ */
+// TODO JUnit tests
+public class StringTemplateLoader implements TemplateLoader {
+    
+    private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
+    
+    private final long instanceId = INSTANCE_COUNTER.get();
+    private final AtomicLong templatesRevision = new AtomicLong();
+    private final ConcurrentMap<String, ContentHolder> templates = new 
ConcurrentHashMap<>();
+    
+    /**
+     * Puts a template into the template loader. The name can contain slashes 
to denote logical directory structure, but
+     * must not start with a slash. Each template will get an unique revision 
number, thus replacing a template will
+     * cause the template cache to reload it (when the update delay expires).
+     * 
+     * <p>This method is thread-safe.
+     * 
+     * @param name
+     *            the name of the template.
+     * @param content
+     *            the source code of the template.
+     */
+    public void putTemplate(String name, String content) {
+        templates.put(
+                name,
+                new ContentHolder(content, new Source(instanceId, name), 
templatesRevision.incrementAndGet()));
+    }
+    
+    /**
+     * Removes the template with the specified name if it was added earlier.
+     * 
+     * <p>
+     * This method is thread-safe.
+     * 
+     * @param name
+     *            Exactly the key with which the template was added.
+     * 
+     * @return Whether a template was found with the given key (and hence was 
removed now)
+     */ 
+    public boolean removeTemplate(String name) {
+        return templates.remove(name) != null;
+    }
+    
+    @Override
+    public TemplateLoaderSession createSession() {
+        return null;
+    }
+
+    @Override
+    public TemplateLoadingResult load(String name, TemplateLoadingSource 
ifSourceDiffersFrom,
+            Serializable ifVersionDiffersFrom, TemplateLoaderSession session) 
throws IOException {
+        ContentHolder contentHolder = templates.get(name);
+        if (contentHolder == null) {
+            return TemplateLoadingResult.NOT_FOUND;
+        } else if (ifSourceDiffersFrom != null && 
ifSourceDiffersFrom.equals(contentHolder.source)
+                && Objects.equals(ifVersionDiffersFrom, 
contentHolder.version)) {
+            return TemplateLoadingResult.NOT_MODIFIED;
+        } else {
+            return new TemplateLoadingResult(
+                    contentHolder.source, contentHolder.version,
+                    new StringReader(contentHolder.content),
+                    null);
+        }
+    }
+
+    @Override
+    public void resetState() {
+        // Do nothing
+    }
+
+    /**
+     * Show class name and some details that are useful in template-not-found 
errors.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_TemplateLoaderUtils.getClassNameForToString(this));
+        sb.append("(Map { ");
+        int cnt = 0;
+        for (String name : templates.keySet()) {
+            cnt++;
+            if (cnt != 1) {
+                sb.append(", ");
+            }
+            if (cnt > 10) {
+                sb.append("...");
+                break;
+            }
+            sb.append(StringUtil.jQuote(name));
+            sb.append("=...");
+        }
+        if (cnt != 0) {
+            sb.append(' ');
+        }
+        sb.append("})");
+        return sb.toString();
+    }
+
+    private static class ContentHolder {
+        private final String content;
+        private final Source source;
+        private final long version;
+        
+        public ContentHolder(String content, Source source, long version) {
+            this.content = content;
+            this.source = source;
+            this.version = version;
+        }
+        
+    }
+    
+    @SuppressWarnings("serial")
+    private static class Source implements TemplateLoadingSource {
+        
+        private final long instanceId;
+        private final String name;
+        
+        public Source(long instanceId, String name) {
+            this.instanceId = instanceId;
+            this.name = name;
+        }
+    
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (int) (instanceId ^ (instanceId >>> 32));
+            result = prime * result + ((name == null) ? 0 : name.hashCode());
+            return result;
+        }
+    
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            Source other = (Source) obj;
+            if (instanceId != other.instanceId) return false;
+            if (name == null) {
+                if (other.name != null) return false;
+            } else if (!name.equals(other.name)) {
+                return false;
+            }
+            return true;
+        }
+        
+        private void writeObject(ObjectOutputStream out) throws IOException {
+            throw new IOException(StringTemplateLoader.class.getName()
+                    + " sources can't be serialized, as they don't support 
clustering.");
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
new file mode 100644
index 0000000..fa13778
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/StrongCacheStorage.java
@@ -0,0 +1,71 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
+import org.apache.freemarker.core.templateresolver.ConcurrentCacheStorage;
+
+/**
+ * Strong cache storage is a cache storage that simply wraps a {@link Map}. It 
holds a strong reference to all objects
+ * it was passed, therefore prevents the cache from being purged during 
garbage collection. This class is always
+ * thread-safe since 2.3.24, before that if we are running on Java 5 or later.
+ *
+ * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
+ */
+public class StrongCacheStorage implements ConcurrentCacheStorage, 
CacheStorageWithGetSize {
+    
+    private final Map map = new ConcurrentHashMap();
+
+    /**
+     * Always returns {@code true}.
+     */
+    public boolean isConcurrent() {
+        return true;
+    }
+    
+    public Object get(Object key) {
+        return map.get(key);
+    }
+
+    public void put(Object key, Object value) {
+        map.put(key, value);
+    }
+
+    public void remove(Object key) {
+        map.remove(key);
+    }
+    
+    /**
+     * Returns a close approximation of the number of cache entries.
+     * 
+     * @since 2.3.21
+     */
+    public int getSize() {
+        return map.size();
+    }
+    
+    public void clear() {
+        map.clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.java
new file mode 100644
index 0000000..0d5a95c
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupContext.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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
+import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
+
+/**
+ * Base class for implementing a {@link TemplateLookupContext} that works with 
{@link TemplateLoader}-s.
+ */
+public abstract class TemplateLoaderBasedTemplateLookupContext
+        extends TemplateLookupContext<TemplateLoaderBasedTemplateLookupResult> 
{
+
+    private final TemplateLoadingSource cachedResultSource;
+    private final Serializable cachedResultVersion;
+
+    protected TemplateLoaderBasedTemplateLookupContext(String templateName, 
Locale templateLocale,
+            Object customLookupCondition, TemplateLoadingSource 
cachedResultSource, Serializable cachedResultVersion) {
+        super(templateName, templateLocale, customLookupCondition);
+        this.cachedResultSource = cachedResultSource;
+        this.cachedResultVersion = cachedResultVersion;
+    }
+    
+    protected TemplateLoadingSource getCachedResultSource() {
+        return cachedResultSource;
+    }
+
+    protected Serializable getCachedResultVersion() {
+        return cachedResultVersion;
+    }
+
+    @Override
+    public final TemplateLoaderBasedTemplateLookupResult 
createNegativeLookupResult() {
+        return TemplateLoaderBasedTemplateLookupResult.getNegativeResult();
+    }
+
+    /**
+     * Creates a positive or negative lookup result depending on {@link 
TemplateLoadingResult#getStatus()}.
+     */
+    protected final TemplateLoaderBasedTemplateLookupResult createLookupResult(
+            String templateSourceName, TemplateLoadingResult 
templateLoaderResult) {
+        return 
TemplateLoaderBasedTemplateLookupResult.from(templateSourceName, 
templateLoaderResult);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
new file mode 100644
index 0000000..09a0324
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
@@ -0,0 +1,124 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResultStatus;
+import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
+import org.apache.freemarker.core.util.NullArgumentException;
+
+/**
+ * Class of {@link TemplateLookupResult} instances created by {@link 
TemplateLoaderBasedTemplateLookupContext}. To
+ * create instances of this inside your own {@link 
TemplateLoaderBasedTemplateLookupContext} subclass, call
+ * {@link TemplateLoaderBasedTemplateLookupContext#createLookupResult(String, 
TemplateLoadingResult)} and
+ * {@link 
TemplateLoaderBasedTemplateLookupContext#createNegativeLookupResult()}. You 
should not try to create instances
+ * anywhere else. Also, this class deliberately can't be subclassed (except 
inside FreeMarker).
+ */
+public abstract class TemplateLoaderBasedTemplateLookupResult extends 
TemplateLookupResult {
+    
+    /** Used internally to get a not-found result (currently just a static 
singleton). */
+    static TemplateLoaderBasedTemplateLookupResult getNegativeResult() {
+        return NegativeTemplateLookupResult.INSTANCE;
+    }
+    
+    /** Used internally to create the appropriate kind of result from the 
parameters. */
+    static TemplateLoaderBasedTemplateLookupResult from(String 
templateSourceName, TemplateLoadingResult templateLoaderResult) {
+        return templateLoaderResult.getStatus() != 
TemplateLoadingResultStatus.NOT_FOUND
+                ? new PositiveTemplateLookupResult(templateSourceName, 
templateLoaderResult)
+                : getNegativeResult();
+    }
+    
+    private TemplateLoaderBasedTemplateLookupResult() {
+        //
+    }
+    
+    /**
+     * Used internally to extract the {@link TemplateLoadingResult}; {@code 
null} if {@link #isPositive()} is
+     * {@code false}.
+     */
+    public abstract TemplateLoadingResult getTemplateLoaderResult();
+
+    private static final class PositiveTemplateLookupResult extends 
TemplateLoaderBasedTemplateLookupResult {
+
+        private final String templateSourceName;
+        private final TemplateLoadingResult templateLoaderResult;
+
+        /**
+         * @param templateSourceName
+         *            The name of the matching template found. This is not 
necessarily the same as the template name
+         *            with which the template was originally requested. For 
example, one may gets a template for the
+         *            {@code "foo.ftl"} name, but due to localized lookup the 
template is actually loaded from
+         *            {@code "foo_de.ftl"}. Then this parameter must be {@code 
"foo_de.ftl"}, not {@code "foo.ftl"}. Not
+         *            {@code null}.
+         * 
+         * @param templateLoaderResult
+         *            See {@link TemplateLoader#load} to understand what that 
means. Not
+         *            {@code null}.
+         */
+        private PositiveTemplateLookupResult(String templateSourceName, 
TemplateLoadingResult templateLoaderResult) {
+            NullArgumentException.check("templateName", templateSourceName);
+            NullArgumentException.check("templateLoaderResult", 
templateLoaderResult);
+
+            this.templateSourceName = templateSourceName;
+            this.templateLoaderResult = templateLoaderResult;
+        }
+
+        @Override
+        public String getTemplateSourceName() {
+            return templateSourceName;
+        }
+
+        @Override
+        public TemplateLoadingResult getTemplateLoaderResult() {
+            return templateLoaderResult;
+        }
+
+        @Override
+        public boolean isPositive() {
+            return true;
+        }
+    }
+
+    private static final class NegativeTemplateLookupResult extends 
TemplateLoaderBasedTemplateLookupResult {
+        
+        private static final 
TemplateLoaderBasedTemplateLookupResult.NegativeTemplateLookupResult INSTANCE = 
new NegativeTemplateLookupResult();
+                
+        private NegativeTemplateLookupResult() {
+            // nop
+        }
+
+        @Override
+        public String getTemplateSourceName() {
+            return null;
+        }
+
+        @Override
+        public TemplateLoadingResult getTemplateLoaderResult() {
+            return null;
+        }
+
+        @Override
+        public boolean isPositive() {
+            return false;
+        }
+        
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
new file mode 100644
index 0000000..fdb56b1
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoader.java
@@ -0,0 +1,229 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Objects;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoaderSession;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
+import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+import org.slf4j.Logger;
+
+/**
+ * This is an abstract template loader that can load templates whose location 
can be described by an URL. Subclasses
+ * only need to override the {@link #getURL(String)}, {@link 
#extractNegativeResult(URLConnection)}, and perhaps the
+ * {@link #prepareConnection(URLConnection)} method.
+ */
+// TODO JUnit test (including implementing a HTTP-based template loader to 
test the new protected methods)
+public abstract class URLTemplateLoader implements TemplateLoader {
+    
+    private static final Logger LOG = _CoreLogs.TEMPLATE_RESOLVER;
+    
+    private Boolean urlConnectionUsesCaches = false;
+    
+    /**
+     * Getter pair of {@link #setURLConnectionUsesCaches(Boolean)}.
+     * 
+     * @since 2.3.21
+     */
+    public Boolean getURLConnectionUsesCaches() {
+        return urlConnectionUsesCaches;
+    }
+
+    /**
+     * Sets if {@link URLConnection#setUseCaches(boolean)} will be called, and 
with what value. By default this is
+     * {@code false}, because FreeMarker has its own template cache with its 
own update delay setting
+     * ({@link Configuration#setTemplateUpdateDelay(int)}). If this is set to 
{@code null},
+     * {@link URLConnection#setUseCaches(boolean)} won't be called.
+     */
+    public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
+        this.urlConnectionUsesCaches = urlConnectionUsesCaches;
+    }
+
+    @Override
+    public TemplateLoaderSession createSession() {
+        return null;
+    }
+
+    @Override
+    public TemplateLoadingResult load(String name, TemplateLoadingSource 
ifSourceDiffersFrom,
+            Serializable ifVersionDiffersFrom, TemplateLoaderSession session) 
throws IOException {
+        URL url = getURL(name);
+        if (url == null) {
+            return TemplateLoadingResult.NOT_FOUND;             
+        }
+        
+        URLConnection conn = url.openConnection();
+        Boolean urlConnectionUsesCaches = getURLConnectionUsesCaches();
+        if (urlConnectionUsesCaches != null) {
+            conn.setUseCaches(urlConnectionUsesCaches);
+        }
+        
+        prepareConnection(conn);
+        conn.connect();
+        
+        InputStream inputStream = null;
+        Long version;
+        URLTemplateLoadingSource source;
+        try {
+            TemplateLoadingResult negativeResult = extractNegativeResult(conn);
+            if (negativeResult != null) {
+                return negativeResult;
+            }
+            
+            // To prevent clustering issues, 
getLastModified(fallbackToJarLMD=false)
+            long lmd = getLastModified(conn, false);
+            version = lmd != -1 ? lmd : null;
+            
+            source = new URLTemplateLoadingSource(url);
+            
+            if (ifSourceDiffersFrom != null && 
ifSourceDiffersFrom.equals(source)
+                    && Objects.equals(ifVersionDiffersFrom, version)) {
+                return TemplateLoadingResult.NOT_MODIFIED;
+            }
+            
+            inputStream = conn.getInputStream();
+        } catch (Throwable e) {
+            try {
+                if (inputStream == null) {
+                    // There's no URLConnection.close(), so we do this hack. 
In case of HttpURLConnection we could call
+                    // disconnect(), but that's perhaps too aggressive.
+                    conn.getInputStream().close();
+                }
+            } catch (IOException e2) {
+                LOG.debug("Failed to close connection inputStream", e2);
+            }
+            throw e;
+        }
+        return new TemplateLoadingResult(source, version, inputStream, null);
+    }
+
+    @Override
+    public void resetState() {
+        // Do nothing
+    }
+
+    /**
+     * {@link URLConnection#getLastModified()} with JDK bug workarounds. 
Because of JDK-6956385, for files inside a jar,
+     * it returns the last modification time of the jar itself, rather than 
the last modification time of the file
+     * inside the jar.
+     * 
+     * @param fallbackToJarLMD
+     *            Tells if the file is in side jar, then we should return the 
last modification time of the jar itself,
+     *            or -1 (to work around JDK-6956385).
+     */
+    public static long getLastModified(URLConnection conn, boolean 
fallbackToJarLMD) throws IOException {
+        if (conn instanceof JarURLConnection) {
+            // There is a bug in sun's jar url connection that causes file 
handle leaks when calling getLastModified()
+            // (see https://bugs.openjdk.java.net/browse/JDK-6956385).
+            // Since the time stamps of jar file contents can't vary 
independent from the jar file timestamp, just use
+            // the jar file timestamp
+            if (fallbackToJarLMD) {
+                URL jarURL = ((JarURLConnection) conn).getJarFileURL();
+                if (jarURL.getProtocol().equals("file")) {
+                    // Return the last modified time of the underlying file - 
saves some opening and closing
+                    return new File(jarURL.getFile()).lastModified();
+                } else {
+                    // Use the URL mechanism
+                    URLConnection jarConn = null;
+                    try {
+                        jarConn = jarURL.openConnection();
+                        return jarConn.getLastModified();
+                    } finally {
+                        try {
+                            if (jarConn != null) {
+                                jarConn.getInputStream().close();
+                            }
+                        } catch (IOException e) {
+                            LOG.warn("Failed to close URL connection for: {}", 
conn, e);
+                        }
+                    }
+                }
+            } else {
+                return -1;
+            }
+        } else {
+          return conn.getLastModified();
+        }
+    }
+
+    /**
+     * Given a template name (plus potential locale decorations) retrieves an 
URL that points the template source.
+     * 
+     * @param name
+     *            the name of the sought template (including the locale 
decorations, or other decorations the
+     *            {@link TemplateLookupStrategy} uses).
+     *            
+     * @return An URL that points to the template source, or null if it can be 
determined that the template source does
+     *         not exist. For many implementations the existence of the 
template can't be decided at this point, and you
+     *         rely on {@link #extractNegativeResult(URLConnection)} instead.
+     */
+    protected abstract URL getURL(String name);
+
+    /**
+     * Called before the resource if read, checks if we can immediately return 
a {@link TemplateLoadingResult#NOT_FOUND}
+     * or {@link TemplateLoadingResult#NOT_MODIFIED}, or throw an {@link 
IOException}. For example, for a HTTP-based
+     * storage, the HTTP response status 404 could result in {@link 
TemplateLoadingResult#NOT_FOUND}, 304 in
+     * {@link TemplateLoadingResult#NOT_MODIFIED}, and some others, like 500 
in throwing an {@link IOException}.
+     * 
+     * <p>Some
+     * implementations rely on {@link #getURL(String)} returning {@code null} 
when a template is missing, in which case
+     * this method is certainly not applicable.
+     */
+    protected abstract TemplateLoadingResult 
extractNegativeResult(URLConnection conn) throws IOException;
+
+    /**
+     * Called before anything that causes the connection to actually build up. 
This is where
+     * {@link URLConnection#setIfModifiedSince(long)} and such can be called 
if someone overrides this.
+     * The default implementation in {@link URLTemplateLoader} does nothing. 
+     */
+    protected void prepareConnection(URLConnection conn) {
+        // Does nothing
+    }
+
+    /**
+     * Can be used by subclasses to canonicalize URL path prefixes.
+     * @param prefix the path prefix to canonicalize
+     * @return the canonicalized prefix. All backslashes are replaced with
+     * forward slashes, and a trailing slash is appended if the original
+     * prefix wasn't empty and didn't already end with a slash.
+     */
+    protected static String canonicalizePrefix(String prefix) {
+        // make it foolproof
+        prefix = prefix.replace('\\', '/');
+        // ensure there's a trailing slash
+        if (prefix.length() > 0 && !prefix.endsWith("/")) {
+            prefix += "/";
+        }
+        return prefix;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java
new file mode 100644
index 0000000..13ebe4d
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/URLTemplateLoadingSource.java
@@ -0,0 +1,58 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import java.net.URL;
+
+import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
+import org.apache.freemarker.core.util.NullArgumentException;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+@SuppressWarnings("serial")
+public class URLTemplateLoadingSource implements TemplateLoadingSource {
+
+    private final URL url;
+
+    public URLTemplateLoadingSource(URL url) {
+        NullArgumentException.check("url", url);
+        this.url = url;
+    }
+
+    public URL getUrl() {
+        return url;
+    }
+
+    @Override
+    public int hashCode() {
+        return url.hashCode();
+    }
+
+    @Override
+    @SuppressFBWarnings("EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS")
+    public boolean equals(Object obj) {
+        return url.equals(obj);
+    }
+
+    @Override
+    public String toString() {
+        return url.toString();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java
 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java
new file mode 100644
index 0000000..763ce4c
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/templateresolver/impl/_TemplateLoaderUtils.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.freemarker.core.templateresolver.impl;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+
+/**
+ * For internal use only; don't depend on this, there's no backward 
compatibility guarantee at all!
+ * This class is to work around the lack of module system in Java, i.e., so 
that other FreeMarker packages can
+ * access things inside this package that users shouldn't. 
+ */ 
+public final class _TemplateLoaderUtils {
+
+    private _TemplateLoaderUtils() {
+        // Not meant to be instantiated
+    }
+
+    public static String getClassNameForToString(TemplateLoader 
templateLoader) {
+        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/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/package.html
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/templateresolver/package.html 
b/src/main/java/org/apache/freemarker/core/templateresolver/package.html
index bf806df..4b1b6cb 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/package.html
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/package.html
@@ -19,12 +19,7 @@
 <html>
 <head>
 </head>
-<body bgcolor="white">
-<p>Template <em>loading</em> and caching.
-Beside the actual template cache, it contains loaders that can load template
-files from the file system, from the classpath, or from a web application
-context. If you have specific needs, you can plug custom template loaders into 
-the system by implementing the template loader interface.
-</p>
+<body>
+<p>Template lookup, loading, caching and template naming rules.</p>
 </body>
 </html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java 
b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
index 666ae48..2f11ad8 100644
--- a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -55,10 +55,10 @@ import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
-import org.apache.freemarker.core.templateresolver.ClassTemplateLoader;
-import org.apache.freemarker.core.templateresolver.FileTemplateLoader;
-import org.apache.freemarker.core.templateresolver.MultiTemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
 import org.apache.freemarker.core.util.SecurityUtilities;
 import org.apache.freemarker.core.util.StringUtil;
 import org.apache.freemarker.servlet.jsp.TaglibFactory;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/InitParamParser.java 
b/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
index 17a3d12..35f4680 100644
--- a/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
+++ b/src/main/java/org/apache/freemarker/servlet/InitParamParser.java
@@ -31,10 +31,10 @@ import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core._TemplateAPI;
 import org.apache.freemarker.core.ast._ObjectBuilderSettingEvaluator;
 import org.apache.freemarker.core.ast._SettingEvaluationEnvironment;
-import org.apache.freemarker.core.templateresolver.ClassTemplateLoader;
-import org.apache.freemarker.core.templateresolver.FileTemplateLoader;
-import org.apache.freemarker.core.templateresolver.MultiTemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
 import org.apache.freemarker.core.util.StringUtil;
 import org.slf4j.Logger;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java 
b/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
index 1c2da86..8e74033 100644
--- a/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
+++ b/src/main/java/org/apache/freemarker/servlet/WebAppTemplateLoader.java
@@ -35,13 +35,13 @@ import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core._CoreLogs;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLoaderSession;
-import org.apache.freemarker.core.templateresolver._TemplateLoaderUtils;
 import org.apache.freemarker.core.util.CollectionUtils;
 import org.apache.freemarker.core.util.NullArgumentException;
 import org.apache.freemarker.core.util.StringUtil;
 import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
 import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
-import org.apache.freemarker.core.templateresolver.URLTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.URLTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl._TemplateLoaderUtils;
 import org.slf4j.Logger;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 2e9015b..f635e88 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -29115,7 +29115,7 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                   This allows specifying the naming rules used by FreeMarker.
                   For now, custom implementations aren't allowed, and you can
                   only chose between
-                  <literal>TemplateNameFormat.DEFAULT_2_3_0</literal> (the
+                  <literal>DefaultTemplateNameFormatFM2.INSTANCE</literal> (the
                   default) and <literal>DEFAULT_2_4_0</literal> (recommended,
                   at least for new projects). <literal>DEFAULT_2_4_0</literal>
                   has several advantages, but isn't fully backward compatible
@@ -29134,7 +29134,7 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                   list of differences in the <link
                   
xlink:href="http://freemarker.org/docs/api/freemarker/cache/TemplateNameFormat.html#DEFAULT_2_4_0";>Java
                   API documentation of
-                  
<literal>TemplateNameFormat.DEFAULT_2_4_0</literal></link>.</para>
+                  
<literal>DefaultTemplateNameFormat.INSTANCE</literal></link>.</para>
                 </listitem>
 
                 <listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/manual/zh_CN/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/zh_CN/book.xml b/src/manual/zh_CN/book.xml
index 327b239..fea72e7 100644
--- a/src/manual/zh_CN/book.xml
+++ b/src/manual/zh_CN/book.xml
@@ -21893,7 +21893,7 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                   
(<literal>Configuration.setTemplateNameFormat(TemplateNameFormat)</literal>)。
                                  它允许指定 FreeMarker 
使用的命名规则。现在,不再允许自定义实现了,
                                  仅可以选择在 
-                  <literal>TemplateNameFormat.DEFAULT_2_3_0</literal> 
(默认的) 和 
+                  <literal>DefaultTemplateNameFormatFM2.INSTANCE</literal> 
(默认的) 和 
                                  <literal>DEFAULT_2_4_0</literal> 
(推荐的,至少对新项目选择)之中选择。
                                  <literal>DEFAULT_2_4_0</literal> 
有一些优点,但是不能完全向后兼容
                   (可是很多应用程序不会被影响)。 å…
¸åž‹çš„错误是使用反斜杠来代替斜杠,
@@ -21907,7 +21907,7 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                   (bugs),大多数都是和翻译特殊步骤之后的 
<literal>..</literal> 相关,
                                  比如 <literal>.</literal> 或 
<literal>*</literal>。请在 <link
                   
xlink:href="http://freemarker.org/docs/api/freemarker/cache/TemplateNameFormat.html#DEFAULT_2_4_0";>
 
-                  <literal>TemplateNameFormat.DEFAULT_2_4_0</literal>的 Java 
API 文档</link> 
+                  <literal>DefaultTemplateNameFormat.INSTANCE</literal>的 
Java API 文档</link> 
                                  中参考各种相异之处。</para>
                 </listitem>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java 
b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 6e14cc3..9cec543 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -75,17 +75,19 @@ import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
 import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
 import org.apache.freemarker.core.model.impl.beans.StringModel;
-import org.apache.freemarker.core.templateresolver.ByteArrayTemplateLoader;
 import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize;
-import org.apache.freemarker.core.templateresolver.NullCacheStorage;
-import org.apache.freemarker.core.templateresolver.SoftCacheStorage;
-import org.apache.freemarker.core.templateresolver.StringTemplateLoader;
-import org.apache.freemarker.core.templateresolver.StrongCacheStorage;
-import org.apache.freemarker.core.templateresolver.DefaultTemplateResolver;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
-import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
+import 
org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+import org.apache.freemarker.core.templateresolver.impl.NullCacheStorage;
+import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StrongCacheStorage;
 import org.apache.freemarker.core.util.DateUtil;
 import org.apache.freemarker.core.util.NullArgumentException;
 import org.apache.freemarker.core.util.NullWriter;
@@ -239,9 +241,9 @@ public class ConfigurationTest extends TestCase {
         }
         
         assertFalse(cfg.isTemplateLookupStrategyExplicitlySet());
-        assertSame(TemplateLookupStrategy.DEFAULT_2_3_0, 
cfg.getTemplateLookupStrategy());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, 
cfg.getTemplateLookupStrategy());
         //
-        cfg.setTemplateLookupStrategy(TemplateLookupStrategy.DEFAULT_2_3_0);
+        cfg.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
         assertTrue(cfg.isTemplateLookupStrategyExplicitlySet());
         //
         for (int i = 0; i < 2; i++) {
@@ -250,16 +252,16 @@ public class ConfigurationTest extends TestCase {
         }
         
         assertFalse(cfg.isTemplateNameFormatExplicitlySet());
-        assertSame(TemplateNameFormat.DEFAULT_2_3_0, 
cfg.getTemplateNameFormat());
+        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, 
cfg.getTemplateNameFormat());
         //
-        cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_4_0);
+        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
         assertTrue(cfg.isTemplateNameFormatExplicitlySet());
-        assertSame(TemplateNameFormat.DEFAULT_2_4_0, 
cfg.getTemplateNameFormat());
+        assertSame(DefaultTemplateNameFormat.INSTANCE, 
cfg.getTemplateNameFormat());
         //
         for (int i = 0; i < 2; i++) {
             cfg.unsetTemplateNameFormat();
             assertFalse(cfg.isTemplateNameFormatExplicitlySet());
-            assertSame(TemplateNameFormat.DEFAULT_2_3_0, 
cfg.getTemplateNameFormat());
+            assertSame(DefaultTemplateNameFormatFM2.INSTANCE, 
cfg.getTemplateNameFormat());
         }
         
         assertFalse(cfg.isCacheStorageExplicitlySet());
@@ -682,15 +684,15 @@ public class ConfigurationTest extends TestCase {
         assertEquals(0, cache.getSize());
         cfg.getTemplate("toCache1.ftl");
         assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_3_0);
+        cfg.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
         assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_4_0);
+        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
         assertEquals(0, cache.getSize());
         cfg.getTemplate("toCache1.ftl");
         assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_4_0);
+        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
         assertEquals(1, cache.getSize());
-        cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_3_0);
+        cfg.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE);
         assertEquals(0, cache.getSize());
     }
 
@@ -709,7 +711,7 @@ public class ConfigurationTest extends TestCase {
             assertEquals("In a/b.ftl", template.toString());
         }
         
-        cfg.setTemplateNameFormat(TemplateNameFormat.DEFAULT_2_4_0);
+        cfg.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
         
         {
             final Template template = cfg.getTemplate("a/./../b.ftl");
@@ -721,11 +723,11 @@ public class ConfigurationTest extends TestCase {
 
     public void testTemplateNameFormatSetSetting() throws Exception {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
-        assertSame(TemplateNameFormat.DEFAULT_2_3_0, 
cfg.getTemplateNameFormat());
+        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, 
cfg.getTemplateNameFormat());
         cfg.setSetting(Configuration.TEMPLATE_NAME_FORMAT_KEY, 
"defAult_2_4_0");
-        assertSame(TemplateNameFormat.DEFAULT_2_4_0, 
cfg.getTemplateNameFormat());
+        assertSame(DefaultTemplateNameFormat.INSTANCE, 
cfg.getTemplateNameFormat());
         cfg.setSetting(Configuration.TEMPLATE_NAME_FORMAT_KEY, 
"defaUlt_2_3_0");
-        assertSame(TemplateNameFormat.DEFAULT_2_3_0, 
cfg.getTemplateNameFormat());
+        assertSame(DefaultTemplateNameFormatFM2.INSTANCE, 
cfg.getTemplateNameFormat());
         assertTrue(cfg.isTemplateNameFormatExplicitlySet());
         cfg.setSetting(Configuration.TEMPLATE_NAME_FORMAT_KEY, "defauLt");
         assertFalse(cfg.isTemplateNameFormatExplicitlySet());
@@ -770,10 +772,10 @@ public class ConfigurationTest extends TestCase {
     
     public void testTemplateLookupStrategyDefaultAndSet() throws IOException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
-        assertSame(TemplateLookupStrategy.DEFAULT_2_3_0, 
cfg.getTemplateLookupStrategy());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, 
cfg.getTemplateLookupStrategy());
         
         cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
-        assertSame(TemplateLookupStrategy.DEFAULT_2_3_0, 
cfg.getTemplateLookupStrategy());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, 
cfg.getTemplateLookupStrategy());
         
         CacheStorageWithGetSize cache = (CacheStorageWithGetSize) 
cfg.getCacheStorage();
         cfg.setClassForTemplateLoading(ConfigurationTest.class, "");
@@ -781,7 +783,7 @@ public class ConfigurationTest extends TestCase {
         cfg.getTemplate("toCache1.ftl");
         assertEquals(1, cache.getSize());
         
-        cfg.setTemplateLookupStrategy(TemplateLookupStrategy.DEFAULT_2_3_0);
+        cfg.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
         assertEquals(1, cache.getSize());
         
         final TemplateLookupStrategy myStrategy = new TemplateLookupStrategy() 
{
@@ -799,7 +801,7 @@ public class ConfigurationTest extends TestCase {
         cfg.setTemplateLookupStrategy(myStrategy);
         assertEquals(1, cache.getSize());
         
-        cfg.setTemplateLookupStrategy(TemplateLookupStrategy.DEFAULT_2_3_0);
+        cfg.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
         assertEquals(0, cache.getSize());
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/ExceptionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ExceptionTest.java 
b/src/test/java/org/apache/freemarker/core/ExceptionTest.java
index ef907e7..710f0bc 100644
--- a/src/test/java/org/apache/freemarker/core/ExceptionTest.java
+++ b/src/test/java/org/apache/freemarker/core/ExceptionTest.java
@@ -36,7 +36,7 @@ import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.ast.ParseException;
-import org.apache.freemarker.core.templateresolver.StringTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.core.util.NullWriter;
 
 import junit.framework.TestCase;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java 
b/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
index 8860683..0f887c5 100644
--- a/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
@@ -26,7 +26,7 @@ import java.io.StringWriter;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.templateresolver.StringTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 
 import junit.framework.TestCase;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java 
b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
index ae171f2..6c65c5b 100644
--- 
a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
+++ 
b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
@@ -34,7 +34,7 @@ import 
org.apache.freemarker.core.ast.InvalidReferenceException;
 import org.apache.freemarker.core.ast.LibraryLoad;
 import org.apache.freemarker.core.ast.Environment.Namespace;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.templateresolver.StringTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.core.util.NullWriter;
 import org.junit.Test;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/TemplateLanguageVersionTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/TemplateLanguageVersionTest.java 
b/src/test/java/org/apache/freemarker/core/TemplateLanguageVersionTest.java
index 0ee6dd4..ab67d25 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateLanguageVersionTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateLanguageVersionTest.java
@@ -27,7 +27,7 @@ import java.io.IOException;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.templateresolver.StringTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.test.util.TestUtil;
 import org.junit.Test;
 public class TemplateLanguageVersionTest {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java 
b/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
index 60b68ae..f8b443a 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateLookupStrategyTest.java
@@ -29,14 +29,11 @@ import java.io.IOException;
 import java.io.StringWriter;
 import java.util.Locale;
 
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.TemplateNotFoundException;
 import org.apache.freemarker.core.ast.ParseException;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
 import org.apache.freemarker.test.MonitoredTemplateLoader;
 import org.junit.Test;
 
@@ -47,13 +44,13 @@ public class TemplateLookupStrategyTest {
     @Test
     public void testSetSetting() throws TemplateException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
-        assertSame(TemplateLookupStrategy.DEFAULT_2_3_0, 
cfg.getTemplateLookupStrategy());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, 
cfg.getTemplateLookupStrategy());
 
         cfg.setSetting(Configuration.TEMPLATE_LOOKUP_STRATEGY_KEY, 
MyTemplateLookupStrategy.class.getName() + "()");
         assertTrue(cfg.getTemplateLookupStrategy() instanceof 
MyTemplateLookupStrategy);
         
         cfg.setSetting(Configuration.TEMPLATE_LOOKUP_STRATEGY_KEY, "dEfault");
-        assertSame(TemplateLookupStrategy.DEFAULT_2_3_0, 
cfg.getTemplateLookupStrategy());
+        assertSame(DefaultTemplateLookupStrategy.INSTANCE, 
cfg.getTemplateLookupStrategy());
     }
     
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java 
b/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
index 6a7084c..7113a2a 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateNotFoundMessageTest.java
@@ -31,14 +31,14 @@ import java.io.IOException;
 
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateNotFoundException;
-import org.apache.freemarker.core.templateresolver.ClassTemplateLoader;
-import org.apache.freemarker.core.templateresolver.FileTemplateLoader;
-import org.apache.freemarker.core.templateresolver.MultiTemplateLoader;
-import org.apache.freemarker.core.templateresolver.StringTemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
 import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.servlet.WebAppTemplateLoader;
 import org.junit.Test;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/test/java/org/apache/freemarker/core/ast/CanonicalFormTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/ast/CanonicalFormTest.java 
b/src/test/java/org/apache/freemarker/core/ast/CanonicalFormTest.java
index 85e74d6..7c584cd 100644
--- a/src/test/java/org/apache/freemarker/core/ast/CanonicalFormTest.java
+++ b/src/test/java/org/apache/freemarker/core/ast/CanonicalFormTest.java
@@ -25,8 +25,8 @@ import java.io.StringWriter;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateNotFoundException;
 import org.apache.freemarker.core.ast.ParseException;
-import org.apache.freemarker.core.templateresolver.ClassTemplateLoader;
 import 
org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader;
 import org.apache.freemarker.test.util.FileTestCase;
 


Reply via email to