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
+}