http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLookupContext.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLookupContext.java 
b/src/main/java/freemarker/cache/TemplateLookupContext.java
index c605e15..72827e6 100644
--- a/src/main/java/freemarker/cache/TemplateLookupContext.java
+++ b/src/main/java/freemarker/cache/TemplateLookupContext.java
@@ -20,6 +20,7 @@
 package freemarker.cache;
 
 import java.io.IOException;
+import java.io.Serializable;
 import java.util.Locale;
 
 import freemarker.template.Configuration;
@@ -35,18 +36,25 @@ public abstract class TemplateLookupContext {
     private final String templateName;
     private final Locale templateLocale;
     private final Object customLookupCondition;
+    private final TemplateLoadingSource cachedResultSource;
+    private final Serializable cachedResultVersion;
 
     /**
      * Finds the template source based on its <em>normalized</em> name; 
handles {@code *} steps (so called acquisition),
-     * otherwise it just calls {@link 
TemplateLoader#findTemplateSource(String)}.
+     * otherwise it just calls {@link TemplateLoader#load(String, 
TemplateLoadingSource, Serializable,
+     * TemplateLoaderSession)}.
      * 
      * @param templateName
      *            Must be a normalized name, like {@code "foo/bar/baaz.ftl"}. 
A name is not normalized when, among
-     *            others, it starts with {@code /}, or contains {@code .} or 
{@code ..} paths steps, or it uses
-     *            backslash ({@code \}) instead of {@code /}. A normalized 
name might contains "*" steps.
+     *            others, it starts with {@code /}, or contains {@code .} or 
{@code ..} path steps, or it uses
+     *            backslash ({@code \}) instead of {@code /}. A normalized 
name might contains "*" path steps
+     *            (acquisition).
      * 
      * @return The result of the lookup. Not {@code null}; check {@link 
TemplateLookupResult#isPositive()} to see if the
-     *         lookup has found anything.
+     *         lookup has found anything. Note that in a positive result the 
content of the template is possibly
+     *         also already loaded (this is the case for {@link 
TemplateLoader}-s when the cached content is stale, but
+     *         not for {@link TemplateLoader}-s). Hence discarding a positive 
result and looking for another can
+     *         generate substantial extra I/O.
      */
     public abstract TemplateLookupResult lookupWithAcquisitionStrategy(String 
templateName) throws IOException;
 
@@ -62,10 +70,13 @@ public abstract class TemplateLookupContext {
             Locale templateLocale) throws IOException;
     
     /** Default visibility to prevent extending the class from outside this 
package. */
-    TemplateLookupContext(String templateName, Locale templateLocale, Object 
customLookupCondition) {
+    TemplateLookupContext(String templateName, Locale templateLocale, Object 
customLookupCondition,
+            TemplateLoadingSource cachedResultSource, Serializable 
cachedResultVersion) {
         this.templateName = templateName;
         this.templateLocale = templateLocale;
         this.customLookupCondition = customLookupCondition;
+        this.cachedResultSource = cachedResultSource;
+        this.cachedResultVersion = cachedResultVersion;
     }
 
     /**
@@ -103,5 +114,13 @@ public abstract class TemplateLookupContext {
     public TemplateLookupResult createNegativeLookupResult() {
         return TemplateLookupResult.createNegativeResult();
     }
+
+    TemplateLoadingSource getCachedResultSource() {
+        return cachedResultSource;
+    }
+
+    Serializable getCachedResultVersion() {
+        return cachedResultVersion;
+    }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/TemplateLookupResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/TemplateLookupResult.java 
b/src/main/java/freemarker/cache/TemplateLookupResult.java
index cb7a58b..3fa29d3 100644
--- a/src/main/java/freemarker/cache/TemplateLookupResult.java
+++ b/src/main/java/freemarker/cache/TemplateLookupResult.java
@@ -37,9 +37,9 @@ public abstract class TemplateLookupResult {
     }
     
     /** Used internally to create the appropriate kind of result from the 
parameters. */
-    static TemplateLookupResult from(String templateSourceName, Object 
templateSource) {
-        return templateSource != null
-                ? new PositiveTemplateLookupResult(templateSourceName, 
templateSource)
+    static TemplateLookupResult from(String templateSourceName, 
TemplateLoadingResult templateLoaderResult) {
+        return templateLoaderResult.getStatus() != 
TemplateLoadingResultStatus.NOT_FOUND
+                ? new PositiveTemplateLookupResult(templateSourceName, 
templateLoaderResult)
                 : createNegativeResult();
     }
     
@@ -59,15 +59,15 @@ public abstract class TemplateLookupResult {
     public abstract boolean isPositive();
 
     /**
-     * Used internally to extract the {@link TemplateLoader} source; {@code 
null} if
-     * {@link #isPositive()} is {@code false}.
+     * Used internally to extract the {@link TemplateLoadingResult}; {@code 
null} if {@link #isPositive()} is
+     * {@code false}.
      */
-    abstract Object getTemplateSource();
+    abstract TemplateLoadingResult getTemplateLoaderResult();
 
     private static final class PositiveTemplateLookupResult extends 
TemplateLookupResult {
 
         private final String templateSourceName;
-        private final Object templateSource;
+        private final TemplateLoadingResult templateLoaderResult;
 
         /**
          * @param templateSourceName
@@ -77,20 +77,16 @@ public abstract class TemplateLookupResult {
          *            {@code "foo_de.ftl"}. Then this parameter must be {@code 
"foo_de.ftl"}, not {@code "foo.ftl"}. Not
          *            {@code null}.
          * 
-         * @param templateSource
-         *            See {@link TemplateLoader#findTemplateSource(String)} to 
understand what that means. Not
+         * @param templateLoaderResult
+         *            See {@link TemplateLoader#load} to understand what that 
means. Not
          *            {@code null}.
          */
-        private PositiveTemplateLookupResult(String templateSourceName, Object 
templateSource) {
+        private PositiveTemplateLookupResult(String templateSourceName, 
TemplateLoadingResult templateLoaderResult) {
             NullArgumentException.check("templateName", templateSourceName);
-            NullArgumentException.check("templateSource", templateSource);
-
-            if (templateSource instanceof TemplateLookupResult) {
-                throw new IllegalArgumentException();
-            }
+            NullArgumentException.check("templateLoaderResult", 
templateLoaderResult);
 
             this.templateSourceName = templateSourceName;
-            this.templateSource = templateSource;
+            this.templateLoaderResult = templateLoaderResult;
         }
 
         @Override
@@ -99,8 +95,8 @@ public abstract class TemplateLookupResult {
         }
 
         @Override
-        Object getTemplateSource() {
-            return templateSource;
+        TemplateLoadingResult getTemplateLoaderResult() {
+            return templateLoaderResult;
         }
 
         @Override
@@ -123,7 +119,7 @@ public abstract class TemplateLookupResult {
         }
 
         @Override
-        Object getTemplateSource() {
+        TemplateLoadingResult getTemplateLoaderResult() {
             return null;
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/URLTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/URLTemplateLoader.java 
b/src/main/java/freemarker/cache/URLTemplateLoader.java
index e975869..bd5219c 100644
--- a/src/main/java/freemarker/cache/URLTemplateLoader.java
+++ b/src/main/java/freemarker/cache/URLTemplateLoader.java
@@ -20,29 +20,30 @@
 
 package freemarker.cache;
 
+import java.io.File;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
+import java.io.Serializable;
+import java.net.JarURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import freemarker.template.Configuration;
 
 /**
  * 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)} method. Both {@link ClassTemplateLoader} and
- * {@link WebappTemplateLoader} are (quite trivial) subclasses of this class.
+ * the {@link #getURL(String)} method.
  */
+// TODO JUnit test
 public abstract class URLTemplateLoader implements TemplateLoader {
     
-    private Boolean urlConnectionUsesCaches;
+    private static final Logger LOG = 
LoggerFactory.getLogger("freemarker.cache");
     
-    public Object findTemplateSource(String name)
-    throws IOException {
-        URL url = getURL(name);
-        return url == null ? null : new URLTemplateSource(url, 
getURLConnectionUsesCaches());
-    }
+    private Boolean urlConnectionUsesCaches = false;
     
     /**
      * Given a template name (plus potential locale decorations) retrieves
@@ -54,22 +55,6 @@ public abstract class URLTemplateLoader implements 
TemplateLoader {
      */
     protected abstract URL getURL(String name);
     
-    public long getLastModified(Object templateSource) {
-        return ((URLTemplateSource) templateSource).lastModified();
-    }
-    
-    public Reader getReader(Object templateSource, String encoding)
-    throws IOException {
-        return new InputStreamReader(
-                ((URLTemplateSource) templateSource).getInputStream(),
-                encoding);
-    }
-    
-    public void closeTemplateSource(Object templateSource)
-    throws IOException {
-        ((URLTemplateSource) templateSource).close();
-    }
-
     /**
      * Can be used by subclasses to canonicalize URL path prefixes.
      * @param prefix the path prefix to canonicalize
@@ -98,22 +83,94 @@ public abstract class URLTemplateLoader implements 
TemplateLoader {
 
     /**
      * Sets if {@link URLConnection#setUseCaches(boolean)} will be called, and 
with what value. By default this is
-     * {@code null}; see the behavior then below. The recommended value is 
{@code false}, so that FreeMarker can always
-     * reliably detect when a template was changed. The default is {@code 
null} only for backward compatibility,
-     * and certainly will be changed to {@code false} in 2.4.0. As FreeMarker 
has its own template cache with its
-     * own update delay setting ({@link 
Configuration#setTemplateUpdateDelay(int)}), setting this to {@code false}
-     * shouldn't cause performance problems.
-     * 
-     * <p>Regarding {@code null} value: By default then {@link 
URLConnection#setUseCaches(boolean)} won't be called,
-     * and so the default of the {@link URLConnection} subclass will be in 
effect (usually {@code true}). That's the
-     * 2.3.0-compatible mode. However, if {@link 
Configuration#getIncompatibleImprovements()} is at least 2.3.21, then
-     * when {@code Configuration.getTemplate} is used, {@code null} will mean 
{@code false}. Note that this 2.3.21 trick
-     * only works if the template is loaded through {@code 
Configuration.getTemplate} (or {@link TemplateCache}). 
-     * 
-     * @since 2.3.21
+     * {@code false}, becase 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);
+        }
+        
+        // To prevent clustering issues, 
getLastModified(fallbackToJarLMD=false)
+        long lmd = getLastModified(conn, false);
+        Long version = lmd != -1 ? lmd : null;
+        
+        URLTemplateLoadingSource source = new URLTemplateLoadingSource(url);
+        
+        if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(source)
+                && Objects.equals(ifVersionDiffersFrom, version)) {
+            return TemplateLoadingResult.NOT_MODIFIED;
+        }
+        
+        return new TemplateLoadingResult(source, version, 
conn.getInputStream(), 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();
+        }
+    }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/URLTemplateLoadingSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/URLTemplateLoadingSource.java 
b/src/main/java/freemarker/cache/URLTemplateLoadingSource.java
new file mode 100644
index 0000000..bf84bef
--- /dev/null
+++ b/src/main/java/freemarker/cache/URLTemplateLoadingSource.java
@@ -0,0 +1,56 @@
+/*
+ * 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.net.URL;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import freemarker.template.utility.NullArgumentException;
+
+@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/3d0fdc8c/src/main/java/freemarker/cache/URLTemplateSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/URLTemplateSource.java 
b/src/main/java/freemarker/cache/URLTemplateSource.java
deleted file mode 100644
index 716b642..0000000
--- a/src/main/java/freemarker/cache/URLTemplateSource.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.JarURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-
-/**
- * Wraps a <code>java.net.URL</code>, and implements methods required for a 
typical template source.
- */
-class URLTemplateSource {
-    private final URL url;
-    private URLConnection conn;
-    private InputStream inputStream;
-    private Boolean useCaches;
-
-    /**
-     * @param useCaches {@code null} if this aspect wasn't set in the parent 
{@link TemplateLoader}.
-     */
-    URLTemplateSource(URL url, Boolean useCaches) throws IOException {
-        this.url = url;
-        this.conn = url.openConnection();
-        this.useCaches = useCaches;
-        if (useCaches != null) {
-            conn.setUseCaches(useCaches.booleanValue());
-        }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof URLTemplateSource) {
-            return url.equals(((URLTemplateSource) o).url);
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        return url.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return url.toString();
-    }
-    
-    long lastModified() {
-        if (conn instanceof JarURLConnection) {
-          // There is a bug in sun's jar url connection that causes file 
handle leaks when calling getLastModified()
-          // Since the time stamps of jar file contents can't vary independent 
from the jar file timestamp, just use
-          // the jar file timestamp
-          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();
-            } catch (IOException e) {
-              return -1;
-            } finally {
-              try {
-                if (jarConn != null) jarConn.getInputStream().close();
-              } catch (IOException e) { }
-            }
-          }
-        } else {
-          long lastModified = conn.getLastModified();
-          if (lastModified == -1L && url.getProtocol().equals("file")) {
-              // Hack for obtaining accurate last modified time for
-              // URLs that point to the local file system. This is fixed
-              // in JDK 1.4, but prior JDKs returns -1 for file:// URLs.
-              return new File(url.getFile()).lastModified();
-          } else {
-              return lastModified;
-          }
-        }
-    }
-
-    InputStream getInputStream() throws IOException {
-        if (inputStream != null) {
-            // Ensure that the returned InputStream reads from the beginning 
of the resource when getInputStream()
-            // is called for the second time:
-            try {
-                inputStream.close();
-            } catch (IOException e) {
-                // Ignore; this is maybe because it was closed for the 2nd 
time now
-            }
-            this.conn = url.openConnection();
-        }
-        inputStream = conn.getInputStream();
-        return inputStream;
-    }
-
-    void close() throws IOException {
-        try {
-          if (inputStream != null) {
-              inputStream.close();
-          } else {
-              conn.getInputStream().close();
-          }
-        } finally {
-          inputStream = null;
-          conn = null;
-        }
-    }
-
-    Boolean getUseCaches() {
-        return useCaches;
-    }
-
-    void setUseCaches(boolean useCaches) {
-        if (this.conn != null) {
-            conn.setUseCaches(useCaches);
-            this.useCaches = Boolean.valueOf(useCaches);
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/WebAppTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/WebAppTemplateLoader.java 
b/src/main/java/freemarker/cache/WebAppTemplateLoader.java
new file mode 100644
index 0000000..1ddaa68
--- /dev/null
+++ b/src/main/java/freemarker/cache/WebAppTemplateLoader.java
@@ -0,0 +1,296 @@
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Objects;
+
+import javax.servlet.ServletContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import freemarker.template.Configuration;
+import freemarker.template.utility.CollectionUtils;
+import freemarker.template.utility.NullArgumentException;
+import freemarker.template.utility.StringUtil;
+
+/**
+ * A {@link TemplateLoader} that uses streams reachable through {@link 
ServletContext#getResource(String)} as its source
+ * of templates.  
+ */
+public class WebAppTemplateLoader implements TemplateLoader {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger("freemarker.cache");
+
+    private final ServletContext servletContext;
+    private final String subdirPath;
+
+    private Boolean urlConnectionUsesCaches = false;
+
+    private boolean attemptFileAccess = true;
+
+    /**
+     * Creates a template loader that will use the specified servlet context 
to load the resources. It will use
+     * the base path of <code>"/"</code> meaning templates will be resolved 
relative to the servlet context root
+     * location.
+     * 
+     * @param servletContext
+     *            the servlet context whose {@link 
ServletContext#getResource(String)} will be used to load the
+     *            templates.
+     */
+    public WebAppTemplateLoader(ServletContext servletContext) {
+        this(servletContext, "/");
+    }
+
+    /**
+     * Creates a template loader that will use the specified servlet context 
to load the resources. It will use the
+     * specified base path, which is interpreted relatively to the context 
root (does not mater if you start it with "/"
+     * or not). Path components should be separated by forward slashes 
independently of the separator character used by
+     * the underlying operating system.
+     * 
+     * @param servletContext
+     *            the servlet context whose {@link 
ServletContext#getResource(String)} will be used to load the
+     *            templates.
+     * @param subdirPath
+     *            the base path to template resources.
+     */
+    public WebAppTemplateLoader(ServletContext servletContext, String 
subdirPath) {
+        NullArgumentException.check("servletContext", servletContext);
+        NullArgumentException.check("subdirPath", subdirPath);
+
+        subdirPath = subdirPath.replace('\\', '/');
+        if (!subdirPath.endsWith("/")) {
+            subdirPath += "/";
+        }
+        if (!subdirPath.startsWith("/")) {
+            subdirPath = "/" + subdirPath;
+        }
+        this.subdirPath = subdirPath;
+        this.servletContext = servletContext;
+    }
+
+    /**
+     * Getter pair of {@link #setURLConnectionUsesCaches(Boolean)}.
+     * 
+     * @since 2.3.21
+     */
+    public Boolean getURLConnectionUsesCaches() {
+        return urlConnectionUsesCaches;
+    }
+
+    /**
+     * It does the same as {@link 
URLTemplateLoader#setURLConnectionUsesCaches(Boolean)}; see there.
+     * 
+     * @since 2.3.21
+     */
+    public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
+        this.urlConnectionUsesCaches = urlConnectionUsesCaches;
+    }
+
+    /**
+     * Show class name and some details that are useful in template-not-found 
errors.
+     * 
+     * @since 2.3.21
+     */
+    @Override
+    public String toString() {
+        return TemplateLoaderUtils.getClassNameForToString(this)
+                + "(subdirPath=" + StringUtil.jQuote(subdirPath)
+                + ", servletContext={contextPath=" + 
StringUtil.jQuote(getContextPath())
+                + ", displayName=" + 
StringUtil.jQuote(servletContext.getServletContextName()) + "})";
+    }
+
+    /** Gets the context path if we are on Servlet 2.5+, or else returns 
failure description string. */
+    private String getContextPath() {
+        try {
+            Method m = servletContext.getClass().getMethod("getContextPath", 
CollectionUtils.EMPTY_CLASS_ARRAY);
+            return (String) m.invoke(servletContext, 
CollectionUtils.EMPTY_OBJECT_ARRAY);
+        } catch (Throwable e) {
+            return "[can't query before Serlvet 2.5]";
+        }
+    }
+
+    /**
+     * Getter pair of {@link #setAttemptFileAccess(boolean)}.
+     * 
+     * @since 2.3.23
+     */
+    public boolean getAttemptFileAccess() {
+        return attemptFileAccess;
+    }
+
+    /**
+     * Specifies that before loading templates with {@link 
ServletContext#getResource(String)}, it should try to load
+     * the template as {@link File}; default is {@code true}, though it's not 
always recommended anymore. This is a
+     * workaround for the case when the servlet container doesn't show 
template modifications after the template was
+     * already loaded earlier. But it's certainly better to counter this 
problem by disabling the URL connection cache
+     * with {@link #setURLConnectionUsesCaches(Boolean)}, which is also the 
default behavior with
+     * {@link 
Configuration#setIncompatibleImprovements(freemarker.template.Version) 
incompatible_improvements} 2.3.21
+     * and later.
+     * 
+     * @since 2.3.23
+     */
+    public void setAttemptFileAccess(boolean attemptLoadingFromFile) {
+        this.attemptFileAccess = attemptLoadingFromFile;
+    }
+
+    @Override
+    public TemplateLoaderSession createSession() {
+        return null;
+    }
+
+    @Override
+    public TemplateLoadingResult load(String name, TemplateLoadingSource 
ifSourceDiffersFrom,
+            Serializable ifVersionDiffersFrom, TemplateLoaderSession session) 
throws IOException {
+        WebAppTemplateLoadingSource source = createSource(name);
+        if (source == null) {
+            return TemplateLoadingResult.NOT_FOUND;
+        }
+        
+        if (source.url != null) {
+            URLConnection conn = source.url.openConnection();
+            Boolean urlConnectionUsesCaches = getURLConnectionUsesCaches();
+            if (urlConnectionUsesCaches != null) {
+                conn.setUseCaches(urlConnectionUsesCaches);
+            }
+            
+            // To prevent clustering issues, 
getLastModified(fallbackToJarLMD=false)
+            long lmd = URLTemplateLoader.getLastModified(conn, false);
+            Long version = lmd != -1 ? lmd : null;
+            
+            if (ifSourceDiffersFrom != null && 
ifSourceDiffersFrom.equals(source)
+                    && Objects.equals(ifVersionDiffersFrom, version)) {
+                return TemplateLoadingResult.NOT_MODIFIED;
+            }
+            
+            return new TemplateLoadingResult(source, version, 
conn.getInputStream(), null);
+        } else { // source.file != null
+            long lmd = source.file.lastModified();
+            Long version = lmd != -1 ? lmd : null;
+
+            if (ifSourceDiffersFrom != null && 
ifSourceDiffersFrom.equals(source)
+                    && Objects.equals(ifVersionDiffersFrom, version)) {
+                return TemplateLoadingResult.NOT_MODIFIED;
+            }
+            
+            return new TemplateLoadingResult(source, version, new 
FileInputStream(source.file), null);
+        }
+    }
+    
+    private WebAppTemplateLoadingSource createSource(String name) {
+        String fullPath = subdirPath + name;
+
+        if (attemptFileAccess) {
+            // First try to open as plain file (to bypass servlet container 
resource caches).
+            try {
+                String realPath = servletContext.getRealPath(fullPath);
+                if (realPath != null) {
+                    File file = new File(realPath);
+                    if (file.canRead() && file.isFile()) {
+                        return new WebAppTemplateLoadingSource(file);
+                    }
+                }
+            } catch (SecurityException e) {
+                ;// ignore
+            }
+        }
+
+        // If it fails, try to open it with servletContext.getResource.
+        URL url = null;
+        try {
+            url = servletContext.getResource(fullPath);
+        } catch (MalformedURLException e) {
+            if (LOG.isWarnEnabled()) {
+                LOG.warn("Could not retrieve resource " + 
StringUtil.jQuoteNoXSS(fullPath), e);
+            }
+            return null;
+        }
+        return url == null ? null : new WebAppTemplateLoadingSource(url);
+    }
+
+    @Override
+    public void resetState() {
+        // Do nothing
+    }
+    
+    @SuppressWarnings("serial")
+    private class WebAppTemplateLoadingSource implements TemplateLoadingSource 
{
+        private final File file;
+        private final URL url;
+        
+        WebAppTemplateLoadingSource(File file) {
+            this.file = file;
+            this.url = null;
+        }
+
+        WebAppTemplateLoadingSource(URL url) {
+            this.file = null;
+            this.url = url;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + getOuterType().hashCode();
+            result = prime * result + ((file == null) ? 0 : file.hashCode());
+            result = prime * result + ((url == null) ? 0 : url.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;
+            WebAppTemplateLoadingSource other = (WebAppTemplateLoadingSource) 
obj;
+            if (!getOuterType().equals(other.getOuterType()))
+                return false;
+            if (file == null) {
+                if (other.file != null)
+                    return false;
+            } else if (!file.equals(other.file))
+                return false;
+            if (url == null) {
+                if (other.url != null)
+                    return false;
+            } else if (!url.equals(other.url))
+                return false;
+            return true;
+        }
+
+        private WebAppTemplateLoader getOuterType() {
+            return WebAppTemplateLoader.this;
+        }
+        
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/cache/WebappTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/cache/WebappTemplateLoader.java 
b/src/main/java/freemarker/cache/WebappTemplateLoader.java
deleted file mode 100644
index 57fb1f4..0000000
--- a/src/main/java/freemarker/cache/WebappTemplateLoader.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * 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.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.servlet.ServletContext;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import freemarker.template.Configuration;
-import freemarker.template.utility.CollectionUtils;
-import freemarker.template.utility.StringUtil;
-
-/**
- * A {@link TemplateLoader} that uses streams reachable through {@link 
ServletContext#getResource(String)} as its source
- * of templates.  
- */
-public class WebappTemplateLoader implements TemplateLoader {
-
-    private static final Logger LOG = 
LoggerFactory.getLogger("freemarker.cache");
-
-    private final ServletContext servletContext;
-    private final String subdirPath;
-
-    private Boolean urlConnectionUsesCaches;
-
-    private boolean attemptFileAccess = true;
-
-    /**
-     * Creates a template loader that will use the specified servlet context 
to load the resources. It will use
-     * the base path of <code>"/"</code> meaning templates will be resolved 
relative to the servlet context root
-     * location.
-     * 
-     * @param servletContext
-     *            the servlet context whose {@link 
ServletContext#getResource(String)} will be used to load the
-     *            templates.
-     */
-    public WebappTemplateLoader(ServletContext servletContext) {
-        this(servletContext, "/");
-    }
-
-    /**
-     * Creates a template loader that will use the specified servlet context 
to load the resources. It will use the
-     * specified base path, which is interpreted relatively to the context 
root (does not mater if you start it with "/"
-     * or not). Path components should be separated by forward slashes 
independently of the separator character used by
-     * the underlying operating system.
-     * 
-     * @param servletContext
-     *            the servlet context whose {@link 
ServletContext#getResource(String)} will be used to load the
-     *            templates.
-     * @param subdirPath
-     *            the base path to template resources.
-     */
-    public WebappTemplateLoader(ServletContext servletContext, String 
subdirPath) {
-        if (servletContext == null) {
-            throw new IllegalArgumentException("servletContext == null");
-        }
-        if (subdirPath == null) {
-            throw new IllegalArgumentException("path == null");
-        }
-
-        subdirPath = subdirPath.replace('\\', '/');
-        if (!subdirPath.endsWith("/")) {
-            subdirPath += "/";
-        }
-        if (!subdirPath.startsWith("/")) {
-            subdirPath = "/" + subdirPath;
-        }
-        this.subdirPath = subdirPath;
-        this.servletContext = servletContext;
-    }
-
-    @Override
-    public Object findTemplateSource(String name) throws IOException {
-        String fullPath = subdirPath + name;
-
-        if (attemptFileAccess) {
-            // First try to open as plain file (to bypass servlet container 
resource caches).
-            try {
-                String realPath = servletContext.getRealPath(fullPath);
-                if (realPath != null) {
-                    File file = new File(realPath);
-                    if (file.canRead() && file.isFile()) {
-                        return file;
-                    }
-                }
-            } catch (SecurityException e) {
-                ;// ignore
-            }
-        }
-
-        // If it fails, try to open it with servletContext.getResource.
-        URL url = null;
-        try {
-            url = servletContext.getResource(fullPath);
-        } catch (MalformedURLException e) {
-            if (LOG.isWarnEnabled()) {
-                LOG.warn("Could not retrieve resource " + 
StringUtil.jQuoteNoXSS(fullPath), e);
-            }
-            return null;
-        }
-        return url == null ? null : new URLTemplateSource(url, 
getURLConnectionUsesCaches());
-    }
-
-    @Override
-    public long getLastModified(Object templateSource) {
-        if (templateSource instanceof File) {
-            return ((File) templateSource).lastModified();
-        } else {
-            return ((URLTemplateSource) templateSource).lastModified();
-        }
-    }
-
-    @Override
-    public Reader getReader(Object templateSource, String encoding)
-            throws IOException {
-        if (templateSource instanceof File) {
-            return new InputStreamReader(
-                    new FileInputStream((File) templateSource),
-                    encoding);
-        } else {
-            return new InputStreamReader(
-                    ((URLTemplateSource) templateSource).getInputStream(),
-                    encoding);
-        }
-    }
-
-    @Override
-    public void closeTemplateSource(Object templateSource) throws IOException {
-        if (templateSource instanceof File) {
-            // Do nothing.
-        } else {
-            ((URLTemplateSource) templateSource).close();
-        }
-    }
-
-    /**
-     * Getter pair of {@link #setURLConnectionUsesCaches(Boolean)}.
-     * 
-     * @since 2.3.21
-     */
-    public Boolean getURLConnectionUsesCaches() {
-        return urlConnectionUsesCaches;
-    }
-
-    /**
-     * It does the same as {@link 
URLTemplateLoader#setURLConnectionUsesCaches(Boolean)}; see there.
-     * 
-     * @since 2.3.21
-     */
-    public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
-        this.urlConnectionUsesCaches = urlConnectionUsesCaches;
-    }
-
-    /**
-     * Show class name and some details that are useful in template-not-found 
errors.
-     * 
-     * @since 2.3.21
-     */
-    @Override
-    public String toString() {
-        return TemplateLoaderUtils.getClassNameForToString(this)
-                + "(subdirPath=" + StringUtil.jQuote(subdirPath)
-                + ", servletContext={contextPath=" + 
StringUtil.jQuote(getContextPath())
-                + ", displayName=" + 
StringUtil.jQuote(servletContext.getServletContextName()) + "})";
-    }
-
-    /** Gets the context path if we are on Servlet 2.5+, or else returns 
failure description string. */
-    private String getContextPath() {
-        try {
-            Method m = servletContext.getClass().getMethod("getContextPath", 
CollectionUtils.EMPTY_CLASS_ARRAY);
-            return (String) m.invoke(servletContext, 
CollectionUtils.EMPTY_OBJECT_ARRAY);
-        } catch (Throwable e) {
-            return "[can't query before Serlvet 2.5]";
-        }
-    }
-
-    /**
-     * Getter pair of {@link #setAttemptFileAccess(boolean)}.
-     * 
-     * @since 2.3.23
-     */
-    public boolean getAttemptFileAccess() {
-        return attemptFileAccess;
-    }
-
-    /**
-     * Specifies that before loading templates with {@link 
ServletContext#getResource(String)}, it should try to load
-     * the template as {@link File}; default is {@code true}, though it's not 
always recommended anymore. This is a
-     * workaround for the case when the servlet container doesn't show 
template modifications after the template was
-     * already loaded earlier. But it's certainly better to counter this 
problem by disabling the URL connection cache
-     * with {@link #setURLConnectionUsesCaches(Boolean)}, which is also the 
default behavior with
-     * {@link 
Configuration#setIncompatibleImprovements(freemarker.template.Version) 
incompatible_improvements} 2.3.21
-     * and later.
-     * 
-     * @since 2.3.23
-     */
-    public void setAttemptFileAccess(boolean attemptLoadingFromFile) {
-        this.attemptFileAccess = attemptLoadingFromFile;
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java 
b/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
index 1b67eed..4d7634b 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
@@ -80,7 +80,7 @@ class BuiltInsForStringsMisc {
                     }
                     
                     FMParser parser = new FMParser(
-                            parentTemplate, false, tkMan, pCfg);
+                            parentTemplate, false, tkMan, pCfg, 
TemplateSpecifiedEncodingHandler.DEFAULT);
                     
                     exp = parser.Expression();
                 } catch (TokenMgrError e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java 
b/src/main/java/freemarker/core/Configurable.java
index 5dc7c49..b5486ee 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -2145,7 +2145,7 @@ public class Configurable {
      *   <li><p>{@code "template_loader"}:
      *       See: {@link Configuration#setTemplateLoader(TemplateLoader)}.
      *       <br>String value: {@code "default"} (case insensitive) for the 
default, or else interpreted as an
-     *       <a href="#fm_obe">object builder expression</a>.
+     *       <a href="#fm_obe">object builder expression</a>. {@code "null"} 
is also allowed.
      *       
      *   <li><p>{@code "template_lookup_strategy"}:
      *       See: {@link 
Configuration#setTemplateLookupStrategy(freemarker.cache.TemplateLookupStrategy)}.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java 
b/src/main/java/freemarker/core/Environment.java
index 740a9a9..7e61587 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -2416,7 +2416,7 @@ public final class Environment extends Configurable {
     }
 
     /**
-     * Emulates <code>include</code> directive, except that <code>name</code> 
must be tempate root relative.
+     * Emulates <code>include</code> directive, except that <code>name</code> 
must be template root relative.
      *
      * <p>
      * It's the same as <code>include(getTemplateForInclusion(name, encoding, 
parse))</code>. But, you may want to
@@ -2488,7 +2488,7 @@ public final class Environment extends Configurable {
 
     private String getIncludedTemplateEncoding() {
         String encoding;
-        // This branch shouldn't exist, as it doesn't make much sense to 
inherit encoding. But we have to keep BC.
+        // [FM3] This branch shouldn't exist, as it doesn't make much sense to 
inherit encoding. But we have to keep BC.
         encoding = getTemplate().getEncoding();
         if (encoding == null) {
             encoding = configuration.getEncoding(this.getLocale());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
 
b/src/main/java/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
new file mode 100644
index 0000000..9fb8050
--- /dev/null
+++ 
b/src/main/java/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.core;
+
+import java.io.InputStream;
+
+import freemarker.template.Template.WrongEncodingException;
+
+/**
+ * A {@link TemplateSpecifiedEncodingHandler} that discards the mark of the 
specified {@link InputStream} when
+ * the template parsing gets to a point where it's known that we can't receive 
a template specified encoding anymore.
+ * This allows freeing up the mark buffer early during parsing.
+ * 
+ * @since 2.3.16
+ */
+public class MarkReleaserTemplateSpecifiedEncodingHandler implements 
TemplateSpecifiedEncodingHandler {
+
+    private final InputStream markedInputStream;
+
+    /**
+     * @param markedInputStream Input stream with marked template content 
start position
+     */
+    public MarkReleaserTemplateSpecifiedEncodingHandler(InputStream 
markedInputStream) {
+        this.markedInputStream = markedInputStream;
+    }
+
+    public void handle(String templateSpecificEncoding, String 
constructorSpecifiedEncoding)
+            throws WrongEncodingException {
+        
TemplateSpecifiedEncodingHandler.DEFAULT.handle(templateSpecificEncoding, 
constructorSpecifiedEncoding);
+        
+        // There was no WrongEncodingException exception, release mark buffer:
+        markedInputStream.mark(0); // 
+    }
+
+    public InputStream getMarkedInputStream() {
+        return markedInputStream;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/StringLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/StringLiteral.java 
b/src/main/java/freemarker/core/StringLiteral.java
index da1c1bd..a81f726 100644
--- a/src/main/java/freemarker/core/StringLiteral.java
+++ b/src/main/java/freemarker/core/StringLiteral.java
@@ -63,7 +63,8 @@ final class StringLiteral extends Expression implements 
TemplateScalarModel {
                 FMParserTokenManager tkMan = new FMParserTokenManager(
                         simpleCharacterStream);
                 
-                FMParser parser = new FMParser(parentTemplate, false, tkMan, 
pcfg);
+                FMParser parser = new FMParser(parentTemplate, false, tkMan, 
pcfg,
+                        TemplateSpecifiedEncodingHandler.DEFAULT);
                 // We continue from the parent parser's current state:
                 parser.setupStringLiteralMode(parentTkMan, outputFormat);
                 try {
@@ -129,6 +130,7 @@ final class StringLiteral extends Expression implements 
TemplateScalarModel {
         }
     }
 
+    @Override
     public String getAsString() {
         return value;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java 
b/src/main/java/freemarker/core/TemplateConfiguration.java
index 353d453..019f547 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -49,7 +49,7 @@ import freemarker.template.utility.NullArgumentException;
  * <p>
  * Note that the result value of the reader methods (getter and "is" methods) 
is usually not useful unless the value of
  * that setting was already set on this object. Otherwise you will get the 
value from the parent {@link Configuration},
- * which is {@link Configuration#getDefaultConfiguration()} before this object 
is associated to a {@link Configuration}.
+ * or an {@link IllegalStateException} before this object is associated to a 
{@link Configuration}.
  * 
  * <p>
  * If you are using this class for your own template loading and caching 
solution, rather than with the standard one,
@@ -146,6 +146,11 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     public Configuration getParentConfiguration() {
         return parentConfigurationSet ? (Configuration) getParent() : null;
     }
+
+    private Configuration getNonNullParentConfiguration() {
+        checkParentConfigurationSet();
+        return (Configuration) getParent();
+    }
     
     /**
      * Set all settings in this {@link TemplateConfiguration} that were set in 
the parameter
@@ -271,8 +276,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
      *             If the parent configuration wasn't yet set.
      */
     public void apply(Template template) {
-        checkParentConfigurationSet();
-        Configuration cfg = getParentConfiguration();
+        Configuration cfg = getNonNullParentConfiguration();
         if (template.getConfiguration() != cfg) {
             // This is actually not a problem right now, but for future BC we 
enforce this.
             throw new IllegalArgumentException(
@@ -377,7 +381,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
      */
     @Override
     public int getTagSyntax() {
-        return tagSyntax != null ? tagSyntax.intValue() : 
getParentConfiguration().getTagSyntax();
+        return tagSyntax != null ? tagSyntax.intValue() : 
getNonNullParentConfiguration().getTagSyntax();
     }
 
     /**
@@ -400,7 +404,8 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
      */
     @Override
     public int getNamingConvention() {
-        return namingConvention != null ? namingConvention.intValue() : 
getParentConfiguration().getNamingConvention();
+        return namingConvention != null ? namingConvention.intValue()
+                : getNonNullParentConfiguration().getNamingConvention();
     }
 
     /**
@@ -423,7 +428,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     @Override
     public boolean getWhitespaceStripping() {
         return whitespaceStripping != null ? whitespaceStripping.booleanValue()
-                : getParentConfiguration().getWhitespaceStripping();
+                : getNonNullParentConfiguration().getWhitespaceStripping();
     }
 
     /**
@@ -447,7 +452,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     @Override
     public int getAutoEscapingPolicy() {
         return autoEscapingPolicy != null ? autoEscapingPolicy.intValue()
-                : getParentConfiguration().getAutoEscapingPolicy();
+                : getNonNullParentConfiguration().getAutoEscapingPolicy();
     }
 
     /**
@@ -470,7 +475,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
      */
     @Override
     public OutputFormat getOutputFormat() {
-        return outputFormat != null ? outputFormat : 
getParentConfiguration().getOutputFormat();
+        return outputFormat != null ? outputFormat : 
getNonNullParentConfiguration().getOutputFormat();
     }
 
     /**
@@ -493,7 +498,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     @Override
     public boolean getRecognizeStandardFileExtensions() {
         return recognizeStandardFileExtensions != null ? 
recognizeStandardFileExtensions.booleanValue()
-                : 
getParentConfiguration().getRecognizeStandardFileExtensions();
+                : 
getNonNullParentConfiguration().getRecognizeStandardFileExtensions();
     }
     
     /**
@@ -502,7 +507,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     public boolean isRecognizeStandardFileExtensionsSet() {
         return recognizeStandardFileExtensions != null;
     }
-
+    
     @Override
     public void setStrictBeanModels(boolean strict) {
         throw new UnsupportedOperationException(
@@ -510,7 +515,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     }
 
     public String getEncoding() {
-        return encoding != null ? encoding : 
getParentConfiguration().getDefaultEncoding();
+        return encoding != null ? encoding : 
getNonNullParentConfiguration().getDefaultEncoding();
     }
 
     /**
@@ -552,7 +557,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
     @Override
     public int getTabSize() {
         return tabSize != null ? tabSize.intValue()
-                : getParentConfiguration().getTabSize();
+                : getNonNullParentConfiguration().getTabSize();
     }
     
     /**
@@ -573,8 +578,7 @@ public final class TemplateConfiguration extends 
Configurable implements ParserC
      */
     @Override
     public Version getIncompatibleImprovements() {
-        checkParentConfigurationSet();
-        return getParentConfiguration().getIncompatibleImprovements();
+        return getNonNullParentConfiguration().getIncompatibleImprovements();
     }
 
     private void checkParentConfigurationSet() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/TemplateSpecifiedEncodingHandler.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/freemarker/core/TemplateSpecifiedEncodingHandler.java 
b/src/main/java/freemarker/core/TemplateSpecifiedEncodingHandler.java
new file mode 100644
index 0000000..2c3f0bf
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateSpecifiedEncodingHandler.java
@@ -0,0 +1,61 @@
+/*
+ * 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.core;
+
+import freemarker.template.Template;
+import freemarker.template.Template.WrongEncodingException;
+
+/**
+ * Specifies the behavior when the template specifies its own encoding (via 
{@code <#ftl encoding=...>}) in the template
+ * content itself, and also when it doesn't do that.
+ */
+public interface TemplateSpecifiedEncodingHandler {
+    
+    TemplateSpecifiedEncodingHandler DEFAULT = new 
TemplateSpecifiedEncodingHandler() {
+
+        @Override
+        public void handle(String templateSpecificEncoding, String 
constructorSpecifiedEncoding)
+                throws WrongEncodingException {
+            if (constructorSpecifiedEncoding != null && 
templateSpecificEncoding != null
+                    && 
!constructorSpecifiedEncoding.equalsIgnoreCase(templateSpecificEncoding)) {
+                throw new 
Template.WrongEncodingException(templateSpecificEncoding, 
constructorSpecifiedEncoding);
+            }
+        }
+        
+    };
+
+    /**
+     * Called once during template parsing, either when the {@code #ftl} 
directive is processed, or near the beginning
+     * of the template processing when there's no {@code #ftl} directive in 
the template.
+     * 
+     * @param templateSpecificEncoding
+     *            The encoding specified via {@code <#ftl encoding=...>}, or 
{@code null} if that was missing (either
+     *            the {@code encoding} parameter or the whole {@code #ftl} 
directive).
+     * @param constructorSpecifiedEncoding
+     *            The encoding specified to the {@link Template} constructor; 
also the value of
+     *            {@link Template#getEncoding()}. If there was an encoding 
used for decoding the template file, it
+     *            should be that, or if there was no encoding, it should be 
{@code null}.
+     * 
+     * @throws WrongEncodingException
+     *             If the template "file" has to be re-read and the {@link 
Template} re-created with the encoding
+     *             specified in the constructor of {@link 
WrongEncodingException}.
+     */
+    void handle(String templateSpecificEncoding, String 
constructorSpecifiedEncoding) throws WrongEncodingException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/core/_CoreAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_CoreAPI.java 
b/src/main/java/freemarker/core/_CoreAPI.java
index 56a81b6..9f75ac5 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -215,8 +215,9 @@ public class _CoreAPI {
         return te.getChild(index);
     }
     
-    public static FMParser newFMParser(Template template, Reader reader, 
ParserConfiguration pCfg) {
-        return new FMParser(template, reader, pCfg);
+    public static FMParser newFMParser(Template template, Reader reader, 
ParserConfiguration pCfg,
+            TemplateSpecifiedEncodingHandler templateSpecifiedEncodingHandler) 
{
+        return new FMParser(template, reader, pCfg, 
templateSpecifiedEncodingHandler);
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java 
b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index ea78ac4..cf15f02 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -22,6 +22,7 @@ package freemarker.ext.servlet;
 import java.io.IOException;
 import java.io.Writer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -48,7 +49,7 @@ import freemarker.cache.ClassTemplateLoader;
 import freemarker.cache.FileTemplateLoader;
 import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
-import freemarker.cache.WebappTemplateLoader;
+import freemarker.cache.WebAppTemplateLoader;
 import freemarker.core.Configurable;
 import freemarker.core.Environment;
 import freemarker.core.OutputFormat;
@@ -129,8 +130,8 @@ import freemarker.template.utility.StringUtil;
  * Any of the above can have a {@code ?setting(name=value, ...)} postfix to 
set the JavaBeans properties of the
  * {@link TemplateLoader} created. For example,
  * {@code /templates?settings(attemptFileAccess=false, 
URLConnectionUsesCaches=true)} calls
- * {@link WebappTemplateLoader#setAttemptFileAccess(boolean)} and
- * {@link WebappTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune 
the {@link WebappTemplateLoader}. For
+ * {@link WebAppTemplateLoader#setAttemptFileAccess(boolean)} and
+ * {@link WebAppTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune 
the {@link WebAppTemplateLoader}. For
  * backward compatibility (not recommended!), you can use the {@code class://} 
prefix, like in
  * <tt>class://com/example/templates</tt> format, which is similar to {@code 
classpath:}, except that it uses the
  * defining class loader of this servlet's class. This can cause 
template-not-found errors, if that class (in
@@ -206,8 +207,9 @@ import freemarker.template.utility.StringUtil;
  * <li>{@value #INIT_PARAM_VALUE_FROM_TEMPLATE}: This should be used in most 
applications, but it's not the default for
  * backward compatibility. It reads the {@link 
Configurable#getOutputEncoding()} setting of the template (note that the
  * template usually just inherits that from the {@link Configuration}), and if 
that's not set, then reads the source
- * charset of the template, just like {@value #INIT_PARAM_VALUE_LEGACY}. Then 
it passes the charset acquired this way to
- * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link 
Environment#setOutputEncoding(String)}. (It
+ * charset of the template, just like {@value #INIT_PARAM_VALUE_LEGACY}, and 
if that's {@code null} (which happens if
+ * the template was loaded from a non-binary source) then it will be UTF-8. 
Then it passes the charset acquired this way
+ * to {@link HttpServletResponse#setCharacterEncoding(String)} and {@link 
Environment#setOutputEncoding(String)}. (It
  * doesn't call the legacy {@link HttpServletResponse#setContentType(String)} 
API to set the charset.) (Note that if the
  * template has a {@code content_type} template attribute (which is 
deprecated) that specifies a charset, it will be
  * used as the output charset of that template.)
@@ -747,7 +749,7 @@ public class FreemarkerServlet extends HttpServlet {
     /**
      * Create the template loader. The default implementation will create a 
{@link ClassTemplateLoader} if the template
      * path starts with {@code "class://"}, a {@link FileTemplateLoader} if 
the template path starts with
-     * {@code "file://"}, and a {@link WebappTemplateLoader} otherwise. Also, 
if
+     * {@code "file://"}, and a {@link WebAppTemplateLoader} otherwise. Also, 
if
      * {@link Configuration#Configuration(freemarker.template.Version) 
incompatible_improvements} is 2.3.22 or higher,
      * it will create a {@link MultiTemplateLoader} if the template path 
starts with {@code "["}.
      * 
@@ -839,7 +841,7 @@ public class FreemarkerServlet extends HttpServlet {
                 if (responseCharacterEncoding == 
ResponseCharacterEncoding.LEGACY && !contentType.containsCharset) {
                     // In legacy mode we don't call 
response.setCharacterEncoding, so the charset must be set here:
                     response.setContentType(
-                            contentType.httpHeaderValue + "; charset=" + 
getTemplateSpecificOutputEncoding(template));
+                            contentType.httpHeaderValue + "; charset=" + 
getOutputEncodingForTemplate(template));
                 } else {
                     response.setContentType(contentType.httpHeaderValue);
                 }
@@ -851,7 +853,7 @@ public class FreemarkerServlet extends HttpServlet {
             // Using the Servlet 2.4 way of setting character encoding.
             if (responseCharacterEncoding != 
ResponseCharacterEncoding.FORCE_CHARSET) {
                 if (!tempSpecContentTypeContainsCharset) {
-                    
response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+                    
response.setCharacterEncoding(getOutputEncodingForTemplate(template));
                 }
             } else {
                 
response.setCharacterEncoding(forcedResponseCharacterEncoding.name());
@@ -911,10 +913,13 @@ public class FreemarkerServlet extends HttpServlet {
         env.process();
     }
 
-    private String getTemplateSpecificOutputEncoding(Template template) {
+    private String getOutputEncodingForTemplate(Template template) {
         String outputEncoding = responseCharacterEncoding == 
ResponseCharacterEncoding.LEGACY ? null
                 : template.getOutputEncoding();
-        return outputEncoding != null ? outputEncoding : 
template.getEncoding();
+        // [FM3] Don't use template.getEncoding() here; it might can't encode 
the dynamic values inserted.
+        return outputEncoding != null ? outputEncoding
+                : template.getEncoding() != null ? template.getEncoding()
+                : StandardCharsets.UTF_8.name();
     }
 
     private ContentType getTemplateSpecificContentType(final Template 
template) {
@@ -928,7 +933,9 @@ public class FreemarkerServlet extends HttpServlet {
         if (outputFormatMimeType != null) {
             if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) 
{
                 // In legacy mode we won't call 
serlvetResponse.setCharacterEncoding(...), so:
-                return new ContentType(outputFormatMimeType + "; charset=" + 
getTemplateSpecificOutputEncoding(template), true);
+                return new ContentType(
+                        outputFormatMimeType + "; charset=" + 
getOutputEncodingForTemplate(template),
+                        true);
             } else {
                 return new ContentType(outputFormatMimeType, false);
             }
@@ -1630,6 +1637,7 @@ public class FreemarkerServlet extends HttpServlet {
     }
     
     private enum ResponseCharacterEncoding implements InitParamValueEnum {
+        // [FM3] Get rid of LEGACY
         LEGACY(INIT_PARAM_VALUE_LEGACY),
         FROM_TEMPLATE(INIT_PARAM_VALUE_FROM_TEMPLATE),
         DO_NOT_SET(INIT_PARAM_VALUE_DO_NOT_SET),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/ext/servlet/InitParamParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/InitParamParser.java 
b/src/main/java/freemarker/ext/servlet/InitParamParser.java
index c7c4f98..feb5b98 100644
--- a/src/main/java/freemarker/ext/servlet/InitParamParser.java
+++ b/src/main/java/freemarker/ext/servlet/InitParamParser.java
@@ -34,7 +34,7 @@ import freemarker.cache.ClassTemplateLoader;
 import freemarker.cache.FileTemplateLoader;
 import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
-import freemarker.cache.WebappTemplateLoader;
+import freemarker.cache.WebAppTemplateLoader;
 import freemarker.core._ObjectBuilderSettingEvaluator;
 import freemarker.core._SettingEvaluationEnvironment;
 import freemarker.template.Configuration;
@@ -101,7 +101,7 @@ final class InitParamParser {
                 && cfg.getIncompatibleImprovements().intValue() >= 
_TemplateAPI.VERSION_INT_2_3_22) {
             throw new TemplatePathParsingException("Template paths starting 
with \"{\" are reseved for future purposes");
         } else {
-            templateLoader = new WebappTemplateLoader(srvCtx, 
pureTemplatePath);
+            templateLoader = new WebAppTemplateLoader(srvCtx, 
pureTemplatePath);
         }
         
         if (settingAssignmentsStart != -1) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java 
b/src/main/java/freemarker/template/Configuration.java
index ea188dd..b2f9b0e 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -845,7 +845,7 @@ public class Configuration extends Configurable implements 
Cloneable, ParserConf
     
     private void createTemplateCache() {
         cache = new TemplateCache(
-                getDefaultTemplateLoader(),
+                null,
                 getDefaultCacheStorage(),
                 getDefaultTemplateLookupStrategy(),
                 getDefaultTemplateNameFormat(),
@@ -862,7 +862,7 @@ public class Configuration extends Configurable implements 
Cloneable, ParserConf
         TemplateCache oldCache = cache;
         cache = new TemplateCache(
                 loader, storage, templateLookupStrategy, templateNameFormat, 
templateConfigurations, this);
-        cache.clear(); // for fully BC behavior
+        cache.clear(false);
         cache.setDelay(oldCache.getDelay());
         cache.setLocalizedLookup(localizedLookup);
     }
@@ -873,41 +873,6 @@ public class Configuration extends Configurable implements 
Cloneable, ParserConf
                 getTemplateConfigurations());
     }
     
-    private TemplateLoader getDefaultTemplateLoader() {
-        return createDefaultTemplateLoader(getIncompatibleImprovements(), 
getTemplateLoader());
-    }
-
-    static TemplateLoader createDefaultTemplateLoader(Version 
incompatibleImprovements) {
-        return createDefaultTemplateLoader(incompatibleImprovements, null);
-    }
-    
-    private static TemplateLoader createDefaultTemplateLoader(
-            Version incompatibleImprovements, TemplateLoader 
existingTemplateLoader) {
-        if (incompatibleImprovements.intValue() < 
_TemplateAPI.VERSION_INT_2_3_21) {
-            if (existingTemplateLoader instanceof 
LegacyDefaultFileTemplateLoader) {
-                return existingTemplateLoader;
-            }
-            try {
-                return new LegacyDefaultFileTemplateLoader();
-            } catch (Exception e) {
-                LOG.warn("Couldn't create legacy default TemplateLoader which 
accesses the current directory. "
-                        + "(Use new 
Configuration(Configuration.VERSION_2_3_21) or higher to avoid this.)", e);
-                return null;
-            }
-        } else {
-            return null;
-        }
-    }
-    
-    // [FM3] Remove
-    private static class LegacyDefaultFileTemplateLoader extends 
FileTemplateLoader {
-
-        public LegacyDefaultFileTemplateLoader() throws IOException {
-            super();
-        }
-        
-    }
-    
     private TemplateLookupStrategy getDefaultTemplateLookupStrategy() {
         return getDefaultTemplateLookupStrategy(getIncompatibleImprovements());
     }
@@ -1179,7 +1144,7 @@ public class Configuration extends Configurable 
implements Cloneable, ParserConf
      */
     public void unsetTemplateLoader() {
         if (templateLoaderExplicitlySet) {
-            setTemplateLoader(getDefaultTemplateLoader());
+            setTemplateLoader(null);
             templateLoaderExplicitlySet = false;
         }
     }
@@ -1425,9 +1390,9 @@ public class Configuration extends Configurable 
implements Cloneable, ParserConf
 
     /**
      * Sets the servlet context from which to load templates.
-     * This is equivalent to {@code setTemplateLoader(new 
WebappTemplateLoader(sctxt, path))}
-     * or {@code setTemplateLoader(new WebappTemplateLoader(sctxt))} if {@code 
path} was
-     * {@code null}, so see {@code freemarker.cache.WebappTemplateLoader} for 
more details.
+     * This is equivalent to {@code setTemplateLoader(new 
WebAppTemplateLoader(sctxt, path))}
+     * or {@code setTemplateLoader(new WebAppTemplateLoader(sctxt))} if {@code 
path} was
+     * {@code null}, so see {@code freemarker.cache.WebAppTemplateLoader} for 
more details.
      * 
      * @param servletContext the {@code javax.servlet.ServletContext} object. 
(The declared type is {@link Object}
      *        to prevent class loading error when using FreeMarker in an 
environment where
@@ -1439,7 +1404,7 @@ public class Configuration extends Configurable 
implements Cloneable, ParserConf
     public void setServletContextForTemplateLoading(Object servletContext, 
String path) {
         try {
             // Don't introduce linking-time dependency on servlets
-            final Class webappTemplateLoaderClass = 
ClassUtil.forName("freemarker.cache.WebappTemplateLoader");
+            final Class webappTemplateLoaderClass = 
ClassUtil.forName("freemarker.cache.WebAppTemplateLoader");
             
             // Don't introduce linking-time dependency on servlets
             final Class servletContextClass = 
ClassUtil.forName("javax.servlet.ServletContext");
@@ -2943,7 +2908,7 @@ public class Configuration extends Configurable 
implements Cloneable, ParserConf
                     unsetTemplateLoader();
                 } else {
                     setTemplateLoader((TemplateLoader) 
_ObjectBuilderSettingEvaluator.eval(
-                            value, TemplateLoader.class, false, 
_SettingEvaluationEnvironment.getCurrent()));
+                            value, TemplateLoader.class, true, 
_SettingEvaluationEnvironment.getCurrent()));
                 }
             } else if (TEMPLATE_LOOKUP_STRATEGY_KEY_SNAKE_CASE.equals(name)
                     || TEMPLATE_LOOKUP_STRATEGY_KEY_CAMEL_CASE.equals(name)) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/template/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Template.java 
b/src/main/java/freemarker/template/Template.java
index 31149f9..ac9bde2 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -50,10 +50,12 @@ import freemarker.core.ParseException;
 import freemarker.core.ParserConfiguration;
 import freemarker.core.TemplateConfiguration;
 import freemarker.core.TemplateElement;
+import freemarker.core.TemplateSpecifiedEncodingHandler;
 import freemarker.core.TextBlock;
 import freemarker.core.TokenMgrError;
 import freemarker.core._CoreAPI;
 import freemarker.debug.impl.DebuggerService;
+import freemarker.template.utility.NullArgumentException;
 
 /**
  * <p>
@@ -136,11 +138,7 @@ public class Template extends Configurable {
     /**
      * Convenience constructor for {@link #Template(String, String, Reader, 
Configuration, String) Template(name, null,
      * reader, cfg, encoding)}.
-     * 
-     * @deprecated In most applications, use {@link #Template(String, Reader, 
Configuration)} instead, which doesn't
-     *             specify the encoding.
      */
-    @Deprecated
     public Template(String name, Reader reader, Configuration cfg, String 
encoding) throws IOException {
         this(name, null, reader, cfg, encoding);
     }
@@ -168,7 +166,9 @@ public class Template extends Configurable {
      *            See {@link #getSourceName()} for the meaning. Can be {@code 
null}, in which case
      *            {@link #getSourceName()} will return the same as {@link 
#getName()}.
      * @param reader
-     *            The character stream to read from. It will always be closed 
({@link Reader#close()}) by this method.
+     *            The character stream to read from. The {@link Reader} is 
<em>not</em> closed by this method (unlike
+     *            in FreeMarker 2.x.x), so be sure that it's closed somewhere. 
(Except of course, readers like
+     *            {@link StringReader} need not be closed.)
      * @param cfg
      *            The Configuration object that this Template is associated 
with. If this is {@code null}, the "default"
      *            {@link Configuration} object is used, which is highly 
discouraged, because it can easily lead to
@@ -186,20 +186,17 @@ public class Template extends Configurable {
      * recommended).
      *
      * @param encoding
-     *            This is the encoding that we are supposed to be using. But 
it's not really necessary because we have a
-     *            {@link Reader} which is already decoded, but it's kept as 
meta-info. It also has an impact when
-     *            {@code #include}-ing/{@code #import}-ing another template 
from this template, as its default encoding
-     *            will be this. But this behavior of said directives is 
considered to be harmful, and will be probably
-     *            phased out. Until that, it's better to leave this on {@code 
null}, so that the encoding will come from
-     *            the {@link Configuration}. Note that if this is non-{@code 
null} and there's an {@code #ftl} header
-     *            with encoding, they must match, or else a {@link 
WrongEncodingException} is thrown.
-     * 
-     * @deprecated In most applications, use {@link #Template(String, String, 
Reader, Configuration)} instead, which
-     *             doesn't specify the encoding.
+     *            This is the encoding that we are supposed to be using. At 
the first glance it's unnecessary because we
+     *            already have a {@link Reader} (so decoding with the charset 
has already happened), however, if this is
+     *            non-{@code null} and there's an {@code #ftl} header with 
{@code encoding} parameter, they must match,
+     *            or else a {@link WrongEncodingException} is thrown. Thus, it 
should be set if to decode the template,
+     *            we were using an encoding (a charset), otherwise it should 
be {@code null}. It's also kept as
+     *            meta-info (returned by {@link #getEncoding()}). It also has 
an impact when {@code #include}-ing or
+     *            {@code #import}-ing another template from this template, as 
its default encoding will be this. But
+     *            this behavior of said directives is considered to be 
harmful, and will be probably phased out.
      * 
      * @since 2.3.22
      */
-   @Deprecated
    public Template(
            String name, String sourceName, Reader reader, Configuration cfg, 
String encoding) throws IOException {
        this(name, sourceName, reader, cfg, null, encoding);
@@ -223,18 +220,35 @@ public class Template extends Configurable {
      *            {@link Configurable} settings will be set too, because this 
constructor only uses it as a
      *            {@link ParserConfiguration}.
      * @param encoding
-     *            Same as in {@link #Template(String, String, Reader, 
Configuration, String)}. When it's non-{@code
-     *            null}, it overrides the value coming from the {@link 
TemplateConfiguration#getEncoding()} method of
-     *            the {@code templateConfiguration} parameter.
+     *            Same as in {@link #Template(String, String, Reader, 
Configuration, String)}.
      * 
      * @since 2.3.24
      */
-    public Template(
-            String name, String sourceName, Reader reader,
-            Configuration cfg, ParserConfiguration customParserConfiguration,
-            String encoding) throws IOException {
+   public Template(
+           String name, String sourceName, Reader reader,
+           Configuration cfg, ParserConfiguration customParserConfiguration,
+           String encoding) throws IOException {
+       this(name, sourceName, reader, cfg, customParserConfiguration, encoding,
+               TemplateSpecifiedEncodingHandler.DEFAULT);
+    }
+   
+   /**
+    * Same as {@link #Template(String, String, Reader, Configuration, 
ParserConfiguration, String)}, but allows
+    * specifying a non-default (non-{@link 
TemplateSpecifiedEncodingHandler#DEFAULT}) behavior regarding encoding
+    * specified in the template content.
+    *  
+    * @param templateSpecifiedEncodingHandler Not {@code null}.
+    * 
+    * @since 2.3.26
+    */
+   public Template(
+           String name, String sourceName, Reader reader,
+           Configuration cfg, ParserConfiguration customParserConfiguration,
+           String encoding, TemplateSpecifiedEncodingHandler 
templateSpecifiedEncodingHandler) throws IOException {
         this(name, sourceName, cfg, customParserConfiguration);
-        
+       
+        NullArgumentException.check("templateSpecifiedEncodingHandler", 
templateSpecifiedEncodingHandler);
+       
         this.setEncoding(encoding);
         LineTableBuilder ltbReader;
         try {
@@ -247,7 +261,8 @@ public class Template extends Configurable {
             reader = ltbReader;
             
             try {
-                parser = _CoreAPI.newFMParser(this, reader, 
actualParserConfiguration);
+                parser = _CoreAPI.newFMParser(
+                        this, reader, actualParserConfiguration, 
templateSpecifiedEncodingHandler);
                 try {
                     this.rootElement = parser.Root();
                 } catch (IndexOutOfBoundsException exc) {
@@ -271,8 +286,6 @@ public class Template extends Configurable {
         } catch (ParseException e) {
             e.setTemplateName(getSourceName());
             throw e;
-        } finally {
-            reader.close();
         }
         
         // Throws any exception that JavaCC has silently treated as EOF:
@@ -606,7 +619,8 @@ public class Template extends Configurable {
     }
 
     /**
-     * Returns the default character encoding used for reading included files.
+     * Returns the default character encoding used for reading 
included/imported files; if {@code null}, then
+     * the encoding returned by {@link 
Configuration#getEncoding(java.util.Locale)} should be used instead.
      */
     public String getEncoding() {
         return this.encoding;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d0fdc8c/src/main/java/freemarker/template/_TemplateAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java 
b/src/main/java/freemarker/template/_TemplateAPI.java
index 52606f6..bf6b227 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -22,7 +22,6 @@ package freemarker.template;
 import java.util.Set;
 
 import freemarker.cache.CacheStorage;
-import freemarker.cache.TemplateLoader;
 import freemarker.cache.TemplateLookupStrategy;
 import freemarker.cache.TemplateNameFormat;
 import freemarker.core.Expression;
@@ -83,10 +82,6 @@ public class _TemplateAPI {
         return 
Configuration.getDefaultLogTemplateExceptions(incompatibleImprovements);
     }
 
-    public static TemplateLoader createDefaultTemplateLoader(Version 
incompatibleImprovements) {
-        return 
Configuration.createDefaultTemplateLoader(incompatibleImprovements);
-    }
-
     public static CacheStorage createDefaultCacheStorage(Version 
incompatibleImprovements) {
         return 
Configuration.createDefaultCacheStorage(incompatibleImprovements);
     }

Reply via email to