You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/14 10:53:00 UTC

[17/51] [partial] incubator-freemarker git commit: Migrated from Ant to Gradle, and modularized the project. This is an incomplete migration; there are some TODO-s in the build scripts, and release related tasks are still missing. What works: Building th

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XMLOutputFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XMLOutputFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XMLOutputFormat.java
new file mode 100644
index 0000000..644f323
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XMLOutputFormat.java
@@ -0,0 +1,77 @@
+/*
+ * 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.outputformat.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.CommonMarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents the XML output format (MIME type "application/xml", name "XML"). This format escapes by default (via
+ * {@link _StringUtil#XMLEnc(String)}). The {@code ?html}, {@code ?xhtml} and {@code ?xml} built-ins silently bypass
+ * template output values of the type produced by this output format ({@link TemplateXHTMLOutputModel}).
+ * 
+ * @since 2.3.24
+ */
+public final class XMLOutputFormat extends CommonMarkupOutputFormat<TemplateXMLOutputModel> {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final XMLOutputFormat INSTANCE = new XMLOutputFormat();
+
+    private XMLOutputFormat() {
+        // Only to decrease visibility
+    }
+
+    @Override
+    public String getName() {
+        return "XML";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/xml";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, TemplateModelException {
+        _StringUtil.XMLEnc(textToEsc, out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return _StringUtil.XMLEnc(plainTextContent);
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("xml");
+    }
+
+    @Override
+    protected TemplateXMLOutputModel newTemplateMarkupOutputModel(String plainTextContent, String markupContent) {
+        return new TemplateXMLOutputModel(plainTextContent, markupContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/package.html
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/package.html b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/package.html
new file mode 100644
index 0000000..6cb5c21
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/package.html
@@ -0,0 +1,26 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p>Template output format: Standard implementations. This package is part of the
+published API, that is, user code can safely depend on it.</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/package.html
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/package.html b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/package.html
new file mode 100644
index 0000000..b25de83
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/package.html
@@ -0,0 +1,25 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p>Template output format: Base classes/interfaces</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/package.html
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/package.html b/freemarker-core/src/main/java/org/apache/freemarker/core/package.html
new file mode 100644
index 0000000..be9dab9
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/package.html
@@ -0,0 +1,27 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p><b>The most commonly used API-s of FreeMarker;</b>
+start with {@link freemarker.template.Configuration Configuration} (see also the
+<a href="http://freemarker.org/docs/pgui_quickstart.html" target="_blank">Getting Stared</a> in the Manual.)</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/AndMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/AndMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/AndMatcher.java
new file mode 100644
index 0000000..27b4156
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/AndMatcher.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+/**
+ * Logical "and" operation among the given matchers.
+ * 
+ * @since 2.3.24
+ */
+public class AndMatcher extends TemplateSourceMatcher {
+    
+    private final TemplateSourceMatcher[] matchers;
+    
+    public AndMatcher(TemplateSourceMatcher... matchers) {
+        if (matchers.length == 0) throw new IllegalArgumentException("Need at least 1 matcher, had 0.");
+        this.matchers = matchers;
+    }
+
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        for (TemplateSourceMatcher matcher : matchers) {
+            if (!(matcher.matches(sourceName, templateSource))) return false;
+        }
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
new file mode 100644
index 0000000..c70fa94
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorage.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.apache.freemarker.core.Configuration;
+
+/**
+ * Cache storage abstracts away the storage aspects of a cache - associating
+ * an object with a key, retrieval and removal via the key. It is actually a
+ * small subset of the {@link java.util.Map} interface. 
+ * The implementations must be thread safe.
+ *
+ * @see Configuration#getCacheStorage()
+ */
+public interface CacheStorage {
+    Object get(Object key);
+    void put(Object key, Object value);
+    void remove(Object key);
+    void clear();
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorageWithGetSize.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorageWithGetSize.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorageWithGetSize.java
new file mode 100644
index 0000000..945d049
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/CacheStorageWithGetSize.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * A cache storage that has a {@code getSize()} method for returning the current number of cache entries.
+ * 
+ * @since 2.3.21
+ */
+public interface CacheStorageWithGetSize extends CacheStorage {
+    
+    /**
+     * Returns the current number of cache entries. This is intended to be used for monitoring. Note that depending on
+     * the implementation, the cost of this operation is not necessary trivial, although calling it a few times per
+     * minute should not be a problem.
+     */
+    int getSize();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
new file mode 100644
index 0000000..8fab61f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/ConditionalTemplateConfigurationFactory.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.TemplateConfiguration;
+
+/**
+ * Returns the given {@link TemplateConfiguration} directly, or another {@link TemplateConfigurationFactory}'s result, when
+ * the specified matcher matches the template source.
+ * 
+ * @since 2.3.24
+ */
+public class ConditionalTemplateConfigurationFactory extends TemplateConfigurationFactory {
+
+    private final TemplateSourceMatcher matcher;
+    private final TemplateConfiguration templateConfiguration;
+    private final TemplateConfigurationFactory templateConfigurationFactory;
+
+    public ConditionalTemplateConfigurationFactory(
+            TemplateSourceMatcher matcher, TemplateConfigurationFactory templateConfigurationFactory) {
+        this.matcher = matcher;
+        templateConfiguration = null;
+        this.templateConfigurationFactory = templateConfigurationFactory;
+    }
+    
+    public ConditionalTemplateConfigurationFactory(
+            TemplateSourceMatcher matcher, TemplateConfiguration templateConfiguration) {
+        this.matcher = matcher;
+        this.templateConfiguration = templateConfiguration;
+        templateConfigurationFactory = null;
+    }
+
+    @Override
+    public TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
+            throws IOException, TemplateConfigurationFactoryException {
+        if (matcher.matches(sourceName, templateLoadingSource)) {
+            if (templateConfigurationFactory != null) {
+                return templateConfigurationFactory.get(sourceName, templateLoadingSource);
+            } else {
+                return templateConfiguration;
+            }
+        } else {
+            return null;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileExtensionMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileExtensionMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileExtensionMatcher.java
new file mode 100644
index 0000000..c89a478
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileExtensionMatcher.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+/**
+ * Matches the file extension; unlike other matchers, by default case <em>insensitive</em>. A name (a path) is
+ * considered to have the given extension exactly if it ends with a dot plus the extension. 
+ * 
+ * @since 2.3.24
+ */
+public class FileExtensionMatcher extends TemplateSourceMatcher {
+
+    private final String extension;
+    private boolean caseInsensitive = true;
+    
+    /**
+     * @param extension
+     *            The file extension (without the initial dot). Can't contain there characters:
+     *            {@code '/'}, {@code '*'}, {@code '?'}. May contains {@code '.'}, but can't start with it.
+     */
+    public FileExtensionMatcher(String extension) {
+        if (extension.indexOf('/') != -1) {
+            throw new IllegalArgumentException("A file extension can't contain \"/\": " + extension);
+        }
+        if (extension.indexOf('*') != -1) {
+            throw new IllegalArgumentException("A file extension can't contain \"*\": " + extension);
+        }
+        if (extension.indexOf('?') != -1) {
+            throw new IllegalArgumentException("A file extension can't contain \"*\": " + extension);
+        }
+        if (extension.startsWith(".")) {
+            throw new IllegalArgumentException("A file extension can't start with \".\": " + extension);
+        }
+        this.extension = extension;
+    }
+
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        int ln = sourceName.length();
+        int extLn = extension.length();
+        if (ln < extLn + 1 || sourceName.charAt(ln - extLn - 1) != '.') {
+            return false;
+        }
+        
+        return sourceName.regionMatches(caseInsensitive, ln - extLn, extension, 0, extLn);
+    }
+    
+    public boolean isCaseInsensitive() {
+        return caseInsensitive;
+    }
+    
+    /**
+     * Sets if the matching will be case insensitive (UNICODE compliant); default is {@code true}.
+     */
+    public void setCaseInsensitive(boolean caseInsensitive) {
+        this.caseInsensitive = caseInsensitive;
+    }
+    
+    /**
+     * Fluid API variation of {@link #setCaseInsensitive(boolean)}
+     */
+    public FileExtensionMatcher caseInsensitive(boolean caseInsensitive) {
+        setCaseInsensitive(caseInsensitive);
+        return this;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileNameGlobMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileNameGlobMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileNameGlobMatcher.java
new file mode 100644
index 0000000..7a9692a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FileNameGlobMatcher.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * As opposed to {@link PathGlobMatcher}, it only compares the "file name" part (the part after the last {@code /}) of
+ * the source name with the given glob. For example, the file name glob {@code *.ftlh} matches both {@code foo.ftlh} and
+ * {@code foo/bar.ftlh}. With other words, that file name glob is equivalent with the {@code **}{@code /*.ftlh})
+ * <em>path</em> glob ( {@link PathGlobMatcher}).
+ * 
+ * @since 2.3.24
+ */
+public class FileNameGlobMatcher extends TemplateSourceMatcher {
+
+    private final String glob;
+    
+    private Pattern pattern;
+    private boolean caseInsensitive;
+    
+    /**
+     * @param glob
+     *            Glob with the syntax defined by {@link _StringUtil#globToRegularExpression(String, boolean)}. Must not
+     *            start with {@code /}.
+     */
+    public FileNameGlobMatcher(String glob) {
+        if (glob.indexOf('/') != -1) {
+            throw new IllegalArgumentException("A file name glob can't contain \"/\": " + glob);
+        }
+        this.glob = glob;
+        buildPattern();
+    }
+
+    private void buildPattern() {
+        pattern = _StringUtil.globToRegularExpression("**/" + glob, caseInsensitive);
+    }
+
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        return pattern.matcher(sourceName).matches();
+    }
+    
+    public boolean isCaseInsensitive() {
+        return caseInsensitive;
+    }
+    
+    /**
+     * Sets if the matching will be case insensitive (UNICODE compliant); default is {@code false}.
+     */
+    public void setCaseInsensitive(boolean caseInsensitive) {
+        boolean lastCaseInsensitive = this.caseInsensitive;
+        this.caseInsensitive = caseInsensitive;
+        if (lastCaseInsensitive != caseInsensitive) {
+            buildPattern();
+        }
+    }
+    
+    /**
+     * Fluid API variation of {@link #setCaseInsensitive(boolean)}
+     */
+    public FileNameGlobMatcher caseInsensitive(boolean caseInsensitive) {
+        setCaseInsensitive(caseInsensitive);
+        return this;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
new file mode 100644
index 0000000..0f09d3d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/FirstMatchTemplateConfigurationFactory.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.TemplateConfiguration;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Returns the first non-{@code null} result of the child factories, ignoring all further child factories. The child
+ * factories are called in the order as they were added.
+ */
+public class FirstMatchTemplateConfigurationFactory extends TemplateConfigurationFactory {
+    
+    private final TemplateConfigurationFactory[] templateConfigurationFactories;
+    private boolean allowNoMatch;
+    private String noMatchErrorDetails;
+    
+    public FirstMatchTemplateConfigurationFactory(TemplateConfigurationFactory... templateConfigurationFactories) {
+        this.templateConfigurationFactories = templateConfigurationFactories;
+    }
+
+    @Override
+    public TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
+            throws IOException, TemplateConfigurationFactoryException {
+        for (TemplateConfigurationFactory tcf : templateConfigurationFactories) {
+            TemplateConfiguration tc = tcf.get(sourceName, templateLoadingSource); 
+            if (tc != null) {
+                return tc;
+            }
+        }
+        if (!allowNoMatch) {
+            throw new TemplateConfigurationFactoryException(
+                    FirstMatchTemplateConfigurationFactory.class.getSimpleName()
+                    + " has found no matching choice for source name "
+                    + _StringUtil.jQuote(sourceName) + ". "
+                    + (noMatchErrorDetails != null
+                            ? "Error details: " + noMatchErrorDetails 
+                            : "(Set the noMatchErrorDetails property of the factory bean to give a more specific error "
+                                    + "message. Set allowNoMatch to true if this shouldn't be an error.)"));
+        }
+        return null;
+    }
+
+    /**
+     * Getter pair of {@link #setAllowNoMatch(boolean)}.
+     */
+    public boolean getAllowNoMatch() {
+        return allowNoMatch;
+    }
+
+    /**
+     * Use this to specify if having no matching choice is an error. The default is {@code false}, that is, it's an
+     * error if there was no matching choice.
+     * 
+     * @see #setNoMatchErrorDetails(String)
+     */
+    public void setAllowNoMatch(boolean allowNoMatch) {
+        this.allowNoMatch = allowNoMatch;
+    }
+
+    /**
+     * Use this to specify the text added to the exception error message when there was no matching choice.
+     * The default is {@code null} (no error details).
+     * 
+     * @see #setAllowNoMatch(boolean)
+     */
+    public String getNoMatchErrorDetails() {
+        return noMatchErrorDetails;
+    }
+
+    
+    public void setNoMatchErrorDetails(String noMatchErrorDetails) {
+        this.noMatchErrorDetails = noMatchErrorDetails;
+    }
+    
+    /**
+     * Same as {@link #setAllowNoMatch(boolean)}, but return this object to support "fluent API" style. 
+     */
+    public FirstMatchTemplateConfigurationFactory allowNoMatch(boolean allow) {
+        setAllowNoMatch(allow);
+        return this;
+    }
+
+    /**
+     * Same as {@link #setNoMatchErrorDetails(String)}, but return this object to support "fluent API" style. 
+     */
+    public FirstMatchTemplateConfigurationFactory noMatchErrorDetails(String message) {
+        setNoMatchErrorDetails(message);
+        return this;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/GetTemplateResult.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/GetTemplateResult.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/GetTemplateResult.java
new file mode 100644
index 0000000..58c9ea9
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/GetTemplateResult.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+import org.apache.freemarker.core.Template;
+
+/**
+ * Used for the return value of {@link TemplateResolver#getTemplate(String, Locale, Serializable)} .
+ * 
+ * @since 3.0.0
+ */
+//TODO DRAFT only [FM3]
+public final class GetTemplateResult {
+    
+    private final Template template;
+    private final String missingTemplateNormalizedName;
+    private final String missingTemplateReason;
+    private final Exception missingTemplateCauseException;
+    
+    public GetTemplateResult(Template template) {
+        this.template = template;
+        missingTemplateNormalizedName = null;
+        missingTemplateReason = null;
+        missingTemplateCauseException = null;
+    }
+    
+    public GetTemplateResult(String normalizedName, Exception missingTemplateCauseException) {
+        template = null;
+        missingTemplateNormalizedName = normalizedName;
+        missingTemplateReason = null;
+        this.missingTemplateCauseException = missingTemplateCauseException;
+    }
+    
+    public GetTemplateResult(String normalizedName, String missingTemplateReason) {
+        template = null;
+        missingTemplateNormalizedName = normalizedName;
+        this.missingTemplateReason = missingTemplateReason;
+        missingTemplateCauseException = null;
+    }
+    
+    /**
+     * The {@link Template} if it wasn't missing, otherwise {@code null}.
+     */
+    public Template getTemplate() {
+        return template;
+    }
+
+    /**
+     * When the template was missing, this <em>possibly</em> contains the explanation, or {@code null}. If the
+     * template wasn't missing (i.e., when {@link #getTemplate()} return non-{@code null}) this is always
+     * {@code null}.
+     */
+    public String getMissingTemplateReason() {
+        return missingTemplateReason != null
+                ? missingTemplateReason
+                : (missingTemplateCauseException != null
+                        ? missingTemplateCauseException.getMessage()
+                        : null);
+    }
+    
+    /**
+     * When the template was missing, this <em>possibly</em> contains its normalized name. If the template wasn't
+     * missing (i.e., when {@link #getTemplate()} return non-{@code null}) this is always {@code null}. When the
+     * template is missing, it will be {@code null} for example if the normalization itself was unsuccessful.
+     */
+    public String getMissingTemplateNormalizedName() {
+        return missingTemplateNormalizedName;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java
new file mode 100644
index 0000000..cf19e93
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MalformedTemplateNameException.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.TemplateNotFoundException;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Indicates that the template name given was malformed according the {@link TemplateNameFormat} in use. Note that for
+ * backward compatibility, {@link DefaultTemplateNameFormatFM2} doesn't throw this exception,
+ * {@link DefaultTemplateNameFormat} does. This exception extends {@link IOException} for backward compatibility.
+ * 
+ * @since 2.3.22
+ * 
+ * @see TemplateNotFoundException
+ * @see Configuration#getTemplate(String)
+ */
+@SuppressWarnings("serial")
+public class MalformedTemplateNameException extends IOException {
+    
+    private final String templateName;
+    private final String malformednessDescription;
+
+    public MalformedTemplateNameException(String templateName, String malformednessDescription) {
+        super("Malformed template name, " + _StringUtil.jQuote(templateName) + ": " + malformednessDescription);
+        this.templateName = templateName;
+        this.malformednessDescription = malformednessDescription;
+    }
+
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    public String getMalformednessDescription() {
+        return malformednessDescription;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
new file mode 100644
index 0000000..9b3106f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/MergingTemplateConfigurationFactory.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.TemplateConfiguration;
+
+/**
+ * Returns the merged results of all the child factories. The factories are merged in the order as they were added.
+ * {@code null} results from the child factories will be ignored. If all child factories return {@code null}, the result
+ * of this factory will be {@code null} too.
+ * 
+ * @since 2.3.24
+ */
+public class MergingTemplateConfigurationFactory extends TemplateConfigurationFactory {
+    
+    private final TemplateConfigurationFactory[] templateConfigurationFactories;
+    
+    public MergingTemplateConfigurationFactory(TemplateConfigurationFactory... templateConfigurationFactories) {
+        this.templateConfigurationFactories = templateConfigurationFactories;
+    }
+
+    @Override
+    public TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
+            throws IOException, TemplateConfigurationFactoryException {
+        TemplateConfiguration.Builder mergedTCBuilder = null;
+        TemplateConfiguration firstResultTC = null;
+        for (TemplateConfigurationFactory tcf : templateConfigurationFactories) {
+            TemplateConfiguration tc = tcf.get(sourceName, templateLoadingSource);
+            if (tc != null) {
+                if (firstResultTC == null) {
+                    firstResultTC = tc;
+                } else {
+                    if (mergedTCBuilder == null) {
+                        mergedTCBuilder = new TemplateConfiguration.Builder();
+                        mergedTCBuilder.merge(firstResultTC);
+                    }
+                    mergedTCBuilder.merge(tc);
+                }
+            }
+        }
+
+        return mergedTCBuilder == null ? firstResultTC /* Maybe null */ : mergedTCBuilder.build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/NotMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/NotMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/NotMatcher.java
new file mode 100644
index 0000000..d608282
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/NotMatcher.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+/**
+ * Logical "not" operation on the given matcher.
+ * 
+ * @since 2.3.24
+ */
+public class NotMatcher extends TemplateSourceMatcher {
+    
+    private final TemplateSourceMatcher matcher;
+    
+    public NotMatcher(TemplateSourceMatcher matcher) {
+        this.matcher = matcher;
+    }
+
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        return !matcher.matches(sourceName, templateSource);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/OrMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/OrMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/OrMatcher.java
new file mode 100644
index 0000000..922f293
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/OrMatcher.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+/**
+ * Logical "or" operation among the given matchers.
+ * 
+ * @since 2.3.24
+ */
+public class OrMatcher extends TemplateSourceMatcher {
+    
+    private final TemplateSourceMatcher[] matchers;
+    
+    public OrMatcher(TemplateSourceMatcher... matchers) {
+        if (matchers.length == 0) throw new IllegalArgumentException("Need at least 1 matcher, had 0.");
+        this.matchers = matchers;
+    }
+
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        for (TemplateSourceMatcher matcher : matchers) {
+            if ((matcher.matches(sourceName, templateSource))) return true;
+        }
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathGlobMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathGlobMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathGlobMatcher.java
new file mode 100644
index 0000000..fa4213a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathGlobMatcher.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Matches the whole template source name (also known as template source path) with the given glob.
+ * Note that the template source name is relative to the template storage root defined by the {@link TemplateLoader};
+ * it's not the full path of a file on the file system.
+ * 
+ * <p>This glob implementation recognizes {@code **} (Ant-style directory wildcard) among others. For more details see
+ * {@link _StringUtil#globToRegularExpression(String, boolean)}.
+ * 
+ * <p>About the usage of {@code /} (slash):
+ * <ul>
+ *   <li>You aren't allowed to start the glob with {@code /}, because template names (template paths) never start with
+ *       it. 
+ *   <li>Future FreeMarker versions (compared to 2.3.24) might will support importing whole directories. Directory paths
+ *       in FreeMarker should end with {@code /}. Hence, {@code foo/bar} refers to the file {bar}, while
+ *       {@code foo/bar/} refers to the {bar} directory.
+ * </ul>
+ * 
+ * <p>By default the glob is case sensitive, but this can be changed with {@link #setCaseInsensitive(boolean)} (or
+ * {@link #caseInsensitive(boolean)}).
+ * 
+ * @since 2.3.24
+ */
+public class PathGlobMatcher extends TemplateSourceMatcher {
+    
+    private final String glob;
+    
+    private Pattern pattern;
+    private boolean caseInsensitive;
+    
+    /**
+     * @param glob
+     *            Glob with the syntax defined by {@link _StringUtil#globToRegularExpression(String, boolean)}. Must not
+     *            start with {@code /}.
+     */
+    public PathGlobMatcher(String glob) {
+        if (glob.startsWith("/")) {
+            throw new IllegalArgumentException("Absolute template paths need no inital \"/\"; remove it from: " + glob);
+        }
+        this.glob = glob;
+        buildPattern();
+    }
+
+    private void buildPattern() {
+        pattern = _StringUtil.globToRegularExpression(glob, caseInsensitive);
+    }
+    
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        return pattern.matcher(sourceName).matches();
+    }
+    
+    public boolean isCaseInsensitive() {
+        return caseInsensitive;
+    }
+    
+    /**
+     * Sets if the matching will be case insensitive (UNICODE compliant); default is {@code false}.
+     */
+    public void setCaseInsensitive(boolean caseInsensitive) {
+        boolean lastCaseInsensitive = this.caseInsensitive;
+        this.caseInsensitive = caseInsensitive;
+        if (lastCaseInsensitive != caseInsensitive) {
+            buildPattern();
+        }
+    }
+    
+    /**
+     * Fluid API variation of {@link #setCaseInsensitive(boolean)}
+     */
+    public PathGlobMatcher caseInsensitive(boolean caseInsensitive) {
+        setCaseInsensitive(caseInsensitive);
+        return this;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathRegexMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathRegexMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathRegexMatcher.java
new file mode 100644
index 0000000..d015b1e
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/PathRegexMatcher.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Matches the whole template source name (also known as template source path) with the given regular expression.
+ * Note that the template source name is relative to the template storage root defined by the {@link TemplateLoader};
+ * it's not the full path of a file on the file system.
+ * 
+ * @since 2.3.24
+ */
+public class PathRegexMatcher extends TemplateSourceMatcher {
+    
+    private final Pattern pattern;
+    
+    /**
+     * @param regex
+     *            Glob with the syntax defined by {@link _StringUtil#globToRegularExpression(String)}. Must not
+     *            start with {@code /}.
+     */
+    public PathRegexMatcher(String regex) {
+        if (regex.startsWith("/")) {
+            throw new IllegalArgumentException("Absolute template paths need no inital \"/\"; remove it from: " + regex);
+        }
+        pattern = Pattern.compile(regex);
+    }
+
+    @Override
+    public boolean matches(String sourceName, Object templateSource) throws IOException {
+        return pattern.matcher(sourceName).matches();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
new file mode 100644
index 0000000..fe9255d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateConfiguration;
+
+/**
+ * Creates (or returns) {@link TemplateConfiguration}-s for template sources.
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateConfigurationFactory {
+
+    /**
+     * Returns (maybe creates) the {@link TemplateConfiguration} for the given template source.
+     * 
+     * @param sourceName
+     *            The name (path) that was used for {@link TemplateLoader#load}. See
+     *            {@link Template#getSourceName()} for details.
+     * @param templateLoadingSource
+     *            The object returned by {@link TemplateLoadingResult#getSource()}.
+     * 
+     * @return The {@link TemplateConfiguration} to apply, or {@code null} if the there's no {@link TemplateConfiguration} for
+     *         this template source.
+     * 
+     * @throws IOException
+     *             Typically, if there factory needs further I/O to find out more about the template source, but that
+     *             fails.
+     * @throws TemplateConfigurationFactoryException
+     *             If there's a problem that's specific to the factory logic.
+     */
+    public abstract TemplateConfiguration get(String sourceName, TemplateLoadingSource templateLoadingSource)
+            throws IOException, TemplateConfigurationFactoryException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryException.java
new file mode 100644
index 0000000..26c4c7e
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateConfigurationFactoryException.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Non-I/O exception thrown by {@link TemplateConfigurationFactory}-s.  
+ * 
+ * @since 2.3.24
+ */
+public class TemplateConfigurationFactoryException extends Exception {
+
+    public TemplateConfigurationFactoryException(String message) {
+        super(message);
+    }
+
+    public TemplateConfigurationFactoryException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
new file mode 100644
index 0000000..fc6a4aa
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.TemplateNotFoundException;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+
+/**
+ * FreeMarker loads template "files" through objects that implement this interface, thus the templates need not be real
+ * files, and can come from any kind of data source (like classpath, servlet context, database, etc). While FreeMarker
+ * provides a few template loader implementations out-of-the-box, it's normal for embedding frameworks to use their own
+ * implementations.
+ * 
+ * <p>
+ * The {@link TemplateLoader} used by FreeMaker is specified by the {@link Configuration#getTemplateLoader()
+ * templateLoader} configuration setting.
+ * 
+ * <p>
+ * Implementations of this interface should be thread-safe.
+ * 
+ * <p>
+ * Implementations should override {@link Object#toString()} to show information about from where the
+ * {@link TemplateLoader} loads the templates. For example, for a template loader that loads template from database
+ * table {@code toString} could return something like
+ * {@code "MyDatabaseTemplateLoader(user=\"cms\", table=\"mail_templates\")"}. This string will be shown in
+ * {@link TemplateNotFoundException} exception messages, next to the template name.
+ * 
+ * <p>
+ * For those who has to dig deeper, note that the {@link TemplateLoader} is actually stored inside the
+ * {@link DefaultTemplateResolver} of the {@link Configuration}, and is normally only accessed directly by the
+ * {@link DefaultTemplateResolver}, and templates are get via the {@link DefaultTemplateResolver} API-s.
+ */
+public interface TemplateLoader {
+
+    /**
+     * Creates a new session, or returns {@code null} if the template loader implementation doesn't support sessions.
+     * See {@link TemplateLoaderSession} for more information about sessions.
+     */
+    TemplateLoaderSession createSession();
+    
+    /**
+     * Loads the template content together with meta-data such as the version (usually the last modification time).
+     * 
+     * @param name
+     *            The name (template root directory relative path) of the template, already localized and normalized by
+     *            the {@link org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver cache}. It is completely up to the loader implementation to
+     *            interpret the name, however it should expect to receive hierarchical paths where path components are
+     *            separated by a slash (not backslash). Backslashes (or any other OS specific separator character) are
+     *            not considered as separators by FreeMarker, and thus they will not be replaced with slash before
+     *            passing to this method, so it's up to the template loader to handle them (say, by throwing an
+     *            exception that tells the user that the path (s)he has entered is invalid, as (s)he must use slash --
+     *            typical mistake of Windows users). The passed names are always considered relative to some
+     *            loader-defined root location (often referred as the "template root directory"), and will never start
+     *            with a slash, nor will they contain a path component consisting of either a single or a double dot --
+     *            these are all resolved by the template cache before passing the name to the loader. As a side effect,
+     *            paths that trivially reach outside template root directory, such as <tt>../my.ftl</tt>, will be
+     *            rejected by the template cache, so they never reach the template loader. Note again, that if the path
+     *            uses backslash as path separator instead of slash as (the template loader should not accept that), the
+     *            normalization will not properly happen, as FreeMarker (the cache) recognizes only the slashes as
+     *            separators.
+     * @param ifSourceDiffersFrom
+     *            If we only want to load the template if its source differs from this. {@code null} if you want the
+     *            template to be loaded unconditionally. If this is {@code null} then the
+     *            {@code ifVersionDiffersFrom} parameter must be {@code null} too. See
+     *            {@link TemplateLoadingResult#getSource()} for more about sources.
+     * @param ifVersionDiffersFrom
+     *            If we only want to load the template if its version (which is usually the last modification time)
+     *            differs from this. {@code null} if {@code ifSourceDiffersFrom} is {@code null}, or if the backing
+     *            storage from which the {@code ifSourceDiffersFrom} template source comes from doesn't store a version.
+     *            See {@link TemplateLoadingResult#getVersion()} for more about versions.
+     * 
+     * @return Not {@code null}.
+     */
+    TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom, Serializable ifVersionDiffersFrom,
+            TemplateLoaderSession session) throws IOException;
+    
+    /**
+     * Invoked by {@link Configuration#clearTemplateCache()} to instruct this template loader to throw away its current
+     * state (some kind of cache usually) and start afresh. For most {@link TemplateLoader} implementations this does
+     * nothing.
+     */
+    void resetState();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
new file mode 100644
index 0000000..6bf1b1f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Serializable;
+
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+
+/**
+ * Stores shared state between {@link TemplateLoader} operations that are executed close to each other in the same
+ * thread. For example, a {@link TemplateLoader} that reads from a database might wants to store the database
+ * connection in it for reuse. The goal of sessions is mostly to increase performance. However, because a
+ * {@link DefaultTemplateResolver#getTemplate(String, java.util.Locale, Serializable)} call is executed inside a single
+ * session, sessions can be also be utilized to ensure that the template lookup (see {@link TemplateLookupStrategy})
+ * happens on a consistent view (a snapshot) of the backing storage, if the backing storage mechanism supports such
+ * thing.
+ * 
+ * <p>
+ * The {@link TemplateLoaderSession} implementation is (usually) specific to the {@link TemplateLoader}
+ * implementation. If your {@link TemplateLoader} implementation can't take advantage of sessions, you don't have to
+ * implement this interface, just return {@code null} for {@link TemplateLoader#createSession()}.
+ * 
+ * <p>
+ * {@link TemplateLoaderSession}-s should be lazy, that is, creating an instance should be very fast and should not
+ * cause I/O. Only when (and if ever) the shared resource stored in the session is needed for the first time should the
+ * shared resource be initialized.
+ *
+ * <p>
+ * {@link TemplateLoaderSession}-s need not be thread safe.
+ */
+public interface TemplateLoaderSession {
+
+    /**
+     * Closes this session, freeing any resources it holds. Further operations involving this session should fail, with
+     * the exception of {@link #close()} itself, which should be silently ignored.
+     * 
+     * <p>
+     * The {@link Reader} or {@link InputStream} contained in the {@link TemplateLoadingResult} must be closed before
+     * {@link #close()} is called on the session in which the {@link TemplateLoadingResult} was created. Except, if
+     * closing the {@link Reader} or {@link InputStream} has thrown an exception, the caller should just proceed with
+     * closing the session regardless. After {@link #close()} was called on the session, the methods of the
+     * {@link Reader} or {@link InputStream} is allowed to throw an exception, or behave in any other erratic way.
+     * (Because the caller of this interface is usually FreeMarker (the {@link DefaultTemplateResolver}), normally you don't have
+     * to deal with these rules, but it's useful to know the expectations if you implement
+     * {@link TemplateLoaderSession}.)
+     * 
+     * <p>The caller of {@link TemplateLoader#createSession()} has to guarantee that {@link #close()} will be called on
+     * the created session.
+     */
+    void close() throws IOException;
+    
+    /**
+     * Tells if this session is closed.
+     */
+    boolean isClosed();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
new file mode 100644
index 0000000..c685d93
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
@@ -0,0 +1,208 @@
+/*
+ * 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;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.util.Date;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Configuration.ExtendableBuilder;
+import org.apache.freemarker.core.TemplateConfiguration;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+/**
+ * Return value of {@link TemplateLoader#load(String, TemplateLoadingSource, Serializable, TemplateLoaderSession)}
+ */
+public final class TemplateLoadingResult {
+    private final TemplateLoadingResultStatus status;
+    private final TemplateLoadingSource source;
+    private final Serializable version;
+    private final Reader reader;
+    private final InputStream inputStream;
+    private final TemplateConfiguration templateConfiguration; 
+
+    public static final TemplateLoadingResult NOT_FOUND = new TemplateLoadingResult(
+            TemplateLoadingResultStatus.NOT_FOUND);
+    public static final TemplateLoadingResult NOT_MODIFIED = new TemplateLoadingResult(
+            TemplateLoadingResultStatus.NOT_MODIFIED);
+
+    /**
+     * Creates an instance with status {@link TemplateLoadingResultStatus#OPENED}, for a storage mechanism that
+     * naturally returns the template content as sequence of {@code char}-s as opposed to a sequence of {@code byte}-s.
+     * This is the case for example when you store the template in a database in a varchar or CLOB. Do <em>not</em> use
+     * this constructor for stores that naturally return binary data instead (like files, class loader resources,
+     * BLOB-s, etc.), because using this constructor will disable FreeMarker's charset selection mechanism.
+     * 
+     * @param source
+     *            See {@link #getSource()}
+     * @param version
+     *            See {@link #getVersion()} for the meaning of this. Can be {@code null}, but use that only if the
+     *            backing storage mechanism doesn't know this information.
+     * @param reader
+     *            Gives the content of the template. It will be read in few thousand character chunks by FreeMarker, so
+     *            generally it need not be a {@link BufferedReader}.
+     * @param templateConfiguration
+     *            Usually {@code null}, as usually the backing storage mechanism doesn't store such information;
+     *            see {@link #getTemplateConfiguration()}.
+     */
+    public TemplateLoadingResult(TemplateLoadingSource source, Serializable version, Reader reader,
+            TemplateConfiguration templateConfiguration) {
+        _NullArgumentException.check("templateSource", source);
+        _NullArgumentException.check("reader", reader);
+        status = TemplateLoadingResultStatus.OPENED;
+        this.source = source;
+        this.version = version;
+        this.reader = reader;
+        inputStream = null;
+        this.templateConfiguration = templateConfiguration; 
+    }
+
+    /**
+     * Creates an instance with status {@link TemplateLoadingResultStatus#OPENED}, for a storage mechanism that
+     * naturally returns the template content as sequence of {@code byte}-s as opposed to a sequence of {@code char}-s.
+     * This is the case for example when you store the template in a file, classpath resource, or BLOB. Do <em>not</em>
+     * use this constructor for stores that naturally return text instead (like database varchar and CLOB columns).
+     * 
+     * @param source
+     *            See {@link #getSource()}
+     * @param version
+     *            See {@link #getVersion()} for the meaning of this. Can be {@code null}, but use that only if the
+     *            backing storage mechanism doesn't know this information.
+     * @param inputStream
+     *            Gives the content of the template. It will be read in few thousand byte chunks by FreeMarker, so
+     *            generally it need not be a {@link BufferedInputStream}.
+     * @param templateConfiguration
+     *            Usually {@code null}, as usually the backing storage mechanism doesn't store such information; see
+     *            {@link #getTemplateConfiguration()}. The most probable application is supplying the charset (encoding)
+     *            used by the {@link InputStream} (via
+     *            {@link ExtendableBuilder#setSourceEncoding(Charset)}), but only do that if the storage
+     *            mechanism really knows what the charset is.
+     */
+    public TemplateLoadingResult(TemplateLoadingSource source, Serializable version, InputStream inputStream,
+            TemplateConfiguration templateConfiguration) {
+        _NullArgumentException.check("templateSource", source);
+        _NullArgumentException.check("inputStream", inputStream);
+        status = TemplateLoadingResultStatus.OPENED;
+        this.source = source;
+        this.version = version;
+        reader = null;
+        this.inputStream = inputStream;
+        this.templateConfiguration = templateConfiguration; 
+    }
+
+    /**
+     * Used internally for creating the singleton instances which has a state where all other fields are {@code null}.
+     */
+    private TemplateLoadingResult(TemplateLoadingResultStatus status) {
+        this.status = status;
+        source = null;
+        version = null;
+        reader = null;
+        inputStream = null;
+        templateConfiguration = null;
+    }
+
+    /**
+     * Returns non-{@code null} exactly if {@link #getStatus()} is {@link TemplateLoadingResultStatus#OPENED} and the
+     * backing store mechanism returns content as {@code byte}-s, as opposed to as {@code chars}-s. See also
+     * {@link #TemplateLoadingResult(TemplateLoadingSource, Serializable, InputStream, TemplateConfiguration)}. It's the
+     * responsibility of the caller (which is {@link DefaultTemplateResolver} usually) to {@code close()} the {@link InputStream}.
+     * The return value is always the same instance, no mater when and how many times this method is called.
+     * 
+     * <p>
+     * The returned {@code InputStream} will be read in few kilobyte chunks by FreeMarker, so generally it need not
+     * be a {@link BufferedInputStream}. 
+     * 
+     * @return {@code null} or a {@code InputStream} to read the template content; see method description for more.
+     */
+    public InputStream getInputStream() {
+        return inputStream;
+    }
+
+    /**
+     * Tells what kind of result this is; see the documentation of {@link TemplateLoadingResultStatus}.
+     */
+    public TemplateLoadingResultStatus getStatus() {
+        return status;
+    }
+
+    /**
+     * Identifies the source on the level of the storage mechanism; stored in the cache together with the version
+     * ({@link #getVersion()}). When checking if a cache entry is up to date, the sources are compared, and only if they
+     * are equal are the versions compared. See more at {@link TemplateLoadingSource}.
+     */
+    public TemplateLoadingSource getSource() {
+        return source;
+    }
+
+    /**
+     * If the result status is {@link TemplateLoadingResultStatus#OPENED} and the backing storage stores such
+     * information, the version (usually the last modification time) of the loaded template, otherwise {@code null}. The
+     * version is some kind of value which changes when the template in the backing storage is updated. Usually, it's
+     * the last modification time (a {@link Date} or {@link Long}), though that can be problematic if the template can
+     * change twice within the granularity of the clock used by the storage. Thus some storages may use a revision
+     * number instead that's always increased when the template is updated, or the cryptographic hash of the template
+     * content as the version. Version objects are compared with each other with their {@link Object#equals(Object)}
+     * method, to see if a cache entry is outdated (though only when the source objects ({@link #getSource()}) are
+     * equal). Thus, the version object must have proper {@link Object#equals(Object)} and {@link Object#hashCode()}
+     * methods.
+     */
+    public Serializable getVersion() {
+        return version;
+    }
+
+    /**
+     * Returns non-{@code null} exactly if {@link #getStatus()} is {@link TemplateLoadingResultStatus#OPENED} and the
+     * backing store mechanism returns content as {@code char}-s, as opposed to as {@code byte}-s. See also
+     * {@link #TemplateLoadingResult(TemplateLoadingSource, Serializable, Reader, TemplateConfiguration)}. It's the
+     * responsibility of the caller (which is {@link DefaultTemplateResolver} usually) to {@code close()} the {@link Reader}. The
+     * return value is always the same instance, no mater when and how many times this method is called.
+     * 
+     * <p>
+     * The returned {@code Reader} will be read in few thousand character chunks by FreeMarker, so generally it need not
+     * be a {@link BufferedReader}. 
+     * 
+     * @return {@code null} or a {@code Reader} to read the template content; see method description for more.
+     */
+    public Reader getReader() {
+        return reader;
+    }
+
+    /**
+     * If {@link #getStatus()} is {@link TemplateLoadingResultStatus#OPENED}, and the template loader stores such
+     * information (which is rare) then it returns the {@link TemplateConfiguration} applicable to the template,
+     * otherwise it returns {@code null}. If {@link #getStatus()} is not {@link TemplateLoadingResultStatus#OPENED},
+     * then this should always return {@code null}. If there are {@link TemplateConfiguration}-s coming from other
+     * sources, such as from {@link Configuration#getTemplateConfigurations()}, this won't replace them, but will be
+     * merged with them, with properties coming from the returned {@link TemplateConfiguration} having the highest
+     * priority.
+     * 
+     * @return {@code null}, or a {@link TemplateConfiguration}.
+     */
+    public TemplateConfiguration getTemplateConfiguration() {
+        return templateConfiguration;
+    }
+    
+}
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
new file mode 100644
index 0000000..bfe47e4
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+
+/**
+ * The point of {@link TemplateLoadingSource} is that with their {@link Object#equals(Object)} method we can tell if two
+ * cache entries were generated from the same physical resource or not. Comparing the template names isn't enough,
+ * because a {@link TemplateLoader} may uses some kind of fallback mechanism, such as delegating to other
+ * {@link TemplateLoader}-s until the template is found. Like if we have two {@link FileTemplateLoader}-s with different
+ * physical root directories, both can contain {@code "foo/bar.ftl"}, but obviously the two files aren't the same.
+ * 
+ * <p>
+ * When implementing this interface, check these:
+ * 
+ * <ul>
+ * <li>{@link Object#equals(Object)} must not be based on object identity, because two instances of
+ * {@link TemplateLoadingSource} that describe the same resource must be equivalent.
+ * 
+ * <li>Each {@link TemplateLoader} implementation should have its own {@link TemplateLoadingSource} implementation, so
+ * that {@link TemplateLoadingSource}-s coming from different {@link TemplateLoader} implementations can't be
+ * accidentally equal (according to {@link Object#equals(Object)}).
+ * 
+ * <li>{@link Object#equals(Object)} must still work properly if there are multiple instances of the same
+ * {@link TemplateLoader} implementation. Like if you have an implementation that loads from a database table, the
+ * {@link TemplateLoadingSource} should certain contain the JDBC connection string, the table name and the row ID, not
+ * just the row ID.
+ * 
+ * <li>Together with {@link Object#equals(Object)}, {@link Object#hashCode()} must be also overridden. The template
+ * source may be used as a {@link HashMap} key.
+ * 
+ * <li>Because {@link TemplateLoadingSource}-s are {@link Serializable}, they can't contain non-{@link Serializable}
+ * fields. Most notably, a reference to the creator {@link TemplateLoader}, so if it's an inner class of the
+ * {@link TemplateLoader}, it should be static.
+ * 
+ * <li>Consider that cache entries, in which the source is stored, may move between JVM-s (because of clustering with a
+ * distributed cache). Thus they should remain meaningful for the purpose of {@link Object#equals(Object)} even in
+ * another JVM.
+ * 
+ * <li>A {@link TemplateLoader} may chose not to support distributed caches, like {@link ByteArrayTemplateLoader}
+ * doesn't support that for example. In that case the serialization related points above can be ignored, but the
+ * {@link TemplateLoadingSource} implementation should define the {@code writeObject} method (a Java serialization
+ * feature) and throw an exception there to block serialization attempts.
+ * </ul>
+ */
+public interface TemplateLoadingSource extends Serializable {
+    // Empty
+}