freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [16/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
Date Sun, 14 May 2017 10:52:59 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
new file mode 100644
index 0000000..0a3b33a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
@@ -0,0 +1,112 @@
+/*
+ * 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 java.util.Locale;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
+
+/**
+ * Used as the parameter of {@link TemplateLookupStrategy#lookup(TemplateLookupContext)}.
+ * You can't invoke instances of this, only receive them from FreeMarker.
+ * 
+ * @since 2.3.22
+ */
+public abstract class TemplateLookupContext<R extends TemplateLookupResult> {
+    
+    private final String templateName;
+    private final Locale templateLocale;
+    private final Object customLookupCondition;
+
+    /**
+     * Finds the template source based on its <em>normalized</em> name; handles {@code *} steps (so called acquisition),
+     * otherwise it just calls {@link TemplateLoader#load(String, TemplateLoadingSource, Serializable,
+     * TemplateLoaderSession)}.
+     * 
+     * @param templateName
+     *            Must be a normalized name, like {@code "foo/bar/baaz.ftl"}. A name is not normalized when, among
+     *            others, it starts with {@code /}, or contains {@code .} or {@code ..} path steps, or it uses
+     *            backslash ({@code \}) instead of {@code /}. A normalized name might contains "*" path steps
+     *            (acquisition).
+     * 
+     * @return The result of the lookup. Not {@code null}; check {@link TemplateLookupResult#isPositive()} to see if the
+     *         lookup has found anything. Note that in a positive result the content of the template is possibly
+     *         also already loaded (this is the case for {@link TemplateLoader}-s when the cached content is stale, but
+     *         not for {@link TemplateLoader}-s). Hence discarding a positive result and looking for another can
+     *         generate substantial extra I/O.
+     */
+    public abstract R lookupWithAcquisitionStrategy(String templateName) throws IOException;
+
+    /**
+     * Finds the template source based on its <em>normalized</em> name; tries localized variations going from most
+     * specific to less specific, and for each variation it delegates to {@link #lookupWithAcquisitionStrategy(String)}.
+     * If {@code templateLocale} is {@code null} (typically, because {@link Configuration#getLocalizedLookup()} is
+     * {@code false})), then it's the same as calling {@link #lookupWithAcquisitionStrategy(String)} directly. This is
+     * the default strategy of FreeMarker (at least in 2.3.x), so for more information, see
+     * {@link DefaultTemplateLookupStrategy#INSTANCE}.
+     */
+    public abstract R lookupWithLocalizedThenAcquisitionStrategy(String templateName,
+            Locale templateLocale) throws IOException;
+    
+    /** Default visibility to prevent extending the class from outside this package. */
+    protected TemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition) {
+        this.templateName = templateName;
+        this.templateLocale = templateLocale;
+        this.customLookupCondition = customLookupCondition;
+    }
+
+    /**
+     * The normalized name (path) of the template (relatively to the {@link TemplateLoader}). Not {@code null}. 
+     */
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    /**
+     * {@code null} if localized lookup is disabled (see {@link Configuration#getLocalizedLookup()}), otherwise the
+     * locale requested.
+     */
+    public Locale getTemplateLocale() {
+        return templateLocale;
+    }
+
+    /**
+     * Returns the value of the {@code customLookupCondition} parameter of
+     * {@link Configuration#getTemplate(String, Locale, Serializable, boolean)}; see requirements there, such
+     * as having a proper {@link Object#equals(Object)} and {@link Object#hashCode()} method. The interpretation of this
+     * value is up to the custom {@link TemplateLookupStrategy}. Usually, it's used similarly to as the default lookup
+     * strategy uses {@link #getTemplateLocale()}, that is, to look for a template variation that satisfies the
+     * condition, and then maybe fall back to more generic template if that's missing.
+     */
+    public Object getCustomLookupCondition() {
+        return customLookupCondition;
+    }
+
+    /**
+     * Creates a not-found lookup result that then can be used as the return value of
+     * {@link TemplateLookupStrategy#lookup(TemplateLookupContext)}. (In the current implementation it just always
+     * returns the same static singleton, but that might need to change in the future.)
+     */
+    public abstract R createNegativeLookupResult();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java
new file mode 100644
index 0000000..d9a2594
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.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 org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.templateresolver.impl.TemplateLoaderBasedTemplateLookupResult;
+
+/**
+ * The return value of {@link TemplateLookupStrategy#lookup(TemplateLookupContext)} and similar lookup methods. You
+ * usually get one from {@link TemplateLookupContext#lookupWithAcquisitionStrategy(String)} or
+ * {@link TemplateLookupContext#createNegativeLookupResult()}.
+ * 
+ * <p>
+ * Subclass this only if you are implementing a {@link TemplateLookupContext}; if the {@link TemplateLookupContext} that
+ * you are implementing uses {@link TemplateLoader}-s, consider using {@link TemplateLoaderBasedTemplateLookupResult}
+ * instead of writing your own subclass.
+ * 
+ * @since 2.3.22
+ */
+public abstract class TemplateLookupResult {
+
+    protected TemplateLookupResult() {
+        // nop
+    }
+    
+    /**
+     * The source name of the template found (see {@link Template#getSourceName()}), or {@code null} if
+     * {@link #isPositive()} is {@code false}.
+     */
+    public abstract String getTemplateSourceName();
+
+    /**
+     * Tells if the lookup has found a matching template.
+     */
+    public abstract boolean isPositive();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
new file mode 100644
index 0000000..7021b5b
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Template;
+
+/**
+ * Finds the {@link TemplateLoader}-level (storage-level) template source for the template name with which the template
+ * was requested (as in {@link Configuration#getTemplate(String)}). This usually means trying various
+ * {@link TemplateLoader}-level template names (so called source names; see also {@link Template#getSourceName()}) that
+ * were deduced from the requested name. Trying a name usually means calling
+ * {@link TemplateLookupContext#lookupWithAcquisitionStrategy(String)} with it and checking the value of
+ * {@link TemplateLookupResult#isPositive()}.
+ * 
+ * <p>
+ * Before you write your own lookup strategy, know that:
+ * <ul>
+ * <li>A template lookup strategy meant to operate solely with template names, not with {@link TemplateLoader}-s
+ * directly. Basically, it's a mapping between the template names that templates and API-s like
+ * {@link Configuration#getTemplate(String)} see, and those that the underlying {@link TemplateLoader} sees.
+ * <li>A template lookup strategy doesn't influence the template's name ({@link Template#getLookupName()}), which is the
+ * normalized form of the template name as it was requested (with {@link Configuration#getTemplate(String)}, etc.). It
+ * only influences the so called source name of the template ({@link Template#getSourceName()}). The template's name is
+ * used as the basis for resolving relative inclusions/imports in the template. The source name is pretty much only used
+ * in error messages as error location, and of course, to actually load the template "file".
+ * <li>Understand the impact of the last point if your template lookup strategy fiddles not only with the file name part
+ * of the template name, but also with the directory part. For example, one may want to map "foo.ftl" to "en/foo.ftl",
+ * "fr/foo.ftl", etc. That's legal, but the result is kind of like if you had several root directories ("en/", "fr/",
+ * etc.) that are layered over each other to form a single merged directory. (This is what's desirable in typical
+ * applications, yet it can be confusing.)
+ * </ul>
+ * 
+ * @see Configuration#getTemplateLookupStrategy()
+ * 
+ * @since 2.3.22
+ */
+public abstract class TemplateLookupStrategy {
+
+    /**
+     * Finds the template source that matches the template name, locale (if not {@code null}) and other parameters
+     * specified in the {@link TemplateLookupContext}. See also the class-level {@link TemplateLookupStrategy}
+     * documentation to understand lookup strategies more.
+     * 
+     * @param ctx
+     *            Contains the parameters for which the matching template need to be found, and operations that
+     *            are needed to implement the strategy. Some of the important input parameters are:
+     *            {@link TemplateLookupContext#getTemplateName()}, {@link TemplateLookupContext#getTemplateLocale()}.
+     *            The most important operations are {@link TemplateLookupContext#lookupWithAcquisitionStrategy(String)}
+     *            and {@link TemplateLookupContext#createNegativeLookupResult()}. (Note that you deliberately can't
+     *            use {@link TemplateLoader}-s directly to implement lookup.)
+     * 
+     * @return Usually the return value of {@link TemplateLookupContext#lookupWithAcquisitionStrategy(String)}, or
+     *         {@code TemplateLookupContext#createNegativeLookupResult()} if no matching template exists. Can't be
+     *         {@code null}.
+     */
+    public abstract <R extends TemplateLookupResult> R lookup(TemplateLookupContext<R> ctx) throws IOException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
new file mode 100644
index 0000000..57773f4
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Symbolizes a template name format, which defines the basic syntax of names through algorithms such as normalization.
+ */
+// TODO [FM3] Before it becomes a BC problem, shouldn't we add methods like splitting to directory name and file name?
+public abstract class TemplateNameFormat {
+
+    protected TemplateNameFormat() {
+       //  
+    }
+    
+    /**
+     * Implements {@link TemplateResolver#toRootBasedName(String, String)}; see more there.
+     */
+    public abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException;
+    
+    /**
+     * Implements {@link TemplateResolver#normalizeRootBasedName(String)}; see more there.
+     */
+    public abstract String normalizeRootBasedName(String name) throws MalformedTemplateNameException;
+
+    protected void checkNameHasNoNullCharacter(final String name) throws MalformedTemplateNameException {
+        if (name.indexOf(0) != -1) {
+            throw new MalformedTemplateNameException(name,
+                    "Null character (\\u0000) in the name; possible attack attempt");
+        }
+    }
+    
+    protected MalformedTemplateNameException newRootLeavingException(final String name) {
+        return new MalformedTemplateNameException(name, "Backing out from the root directory is not allowed");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
new file mode 100644
index 0000000..bf7280a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
@@ -0,0 +1,166 @@
+/*
+ * 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 java.util.Locale;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.ParseException;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateNotFoundException;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+
+/**
+ * This class was introduced to allow users to fully implement the template lookup, loading and caching logic,
+ * in case the standard mechanism ({@link DefaultTemplateResolver}) is not flexible enough. By implementing this class,
+ * you can take over the duty of the following {@link Configuration} settings, and it's up to the implementation if you
+ * delegate some of those duties back to the {@link Configuration} setting:
+ * 
+ * <ul>
+ * <li>{@link Configuration#getTemplateLoader() templateLoader}
+ * <li>{@link Configuration#getTemplateNameFormat() templateNameFormat}
+ * <li>{@link Configuration#getTemplateLookupStrategy() templateLookupStrategy}
+ * <li>{@link Configuration#getCacheStorage() cacheStorage}
+ * </ul>
+ * 
+ * @since 3.0.0
+ */
+//TODO DRAFT only [FM3]
+public abstract class TemplateResolver {
+
+    private final Configuration configuration;
+
+    protected TemplateResolver(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    /**
+     * Retrieves the parsed template with the given name (and according the specified further parameters), or returns a
+     * result that indicates that no such template exists. The result should come from a cache most of the time
+     * (avoiding I/O and template parsing), as this method is typically called frequently.
+     * 
+     * <p>
+     * All parameters must be non-{@code null}, except {@code customLookupCondition}. For the meaning of the parameters
+     * see {@link Configuration#getTemplate(String, Locale, Serializable, boolean)}.
+     *
+     * @return A {@link GetTemplateResult} object that contains the {@link Template}, or a
+     *         {@link GetTemplateResult} object that contains {@code null} as the {@link Template} and information
+     *         about the missing template. The return value itself is never {@code null}. Note that exceptions occurring
+     *         during template loading mustn't be treated as a missing template, they must cause an exception to be
+     *         thrown by this method instead of returning a {@link GetTemplateResult}. The idea is that having a
+     *         missing template is normal (not exceptional), because of how some lookup strategies work. That the
+     *         backing storage mechanism should indeed check that it's missing though, and not cover an error as such.
+     * 
+     * @throws MalformedTemplateNameException
+     *             If the {@code name} was malformed. This is certainly originally thrown by
+     *             {@link #normalizeRootBasedName(String)}; see more there.
+     * 
+     * @throws IOException
+     *             If reading the template has failed from a reason other than the template is missing. This method
+     *             should never be a {@link TemplateNotFoundException}, as that condition is indicated in the return
+     *             value.
+     */
+    // [FM3] This parameters will be removed: String encoding
+    public abstract GetTemplateResult getTemplate(String name, Locale locale, Serializable customLookupCondition)
+            throws MalformedTemplateNameException, ParseException, IOException;
+
+    /**
+     * Clears the cache of templates, to enforce re-loading templates when they are get next time; this is an optional
+     * operation.
+     * 
+     * <p>
+     * Note that if the {@link TemplateResolver} implementation uses {@link TemplateLoader}-s, it should also call
+     * {@link TemplateLoader#resetState()} on them.
+     * 
+     * <p>
+     * This method is thread-safe and can be called while the engine processes templates.
+     * 
+     * @throws UnsupportedOperationException If the {@link TemplateResolver} implementation doesn't support this
+     *        operation.
+     */
+    public abstract void clearTemplateCache() throws UnsupportedOperationException;
+
+    /**
+     * Removes a template from the template cache, hence forcing the re-loading of it when it's next time requested;
+     * this is an optional operation. This is to give the application finer control over cache updating than the
+     * {@link Configuration#getTemplateUpdateDelayMilliseconds() templateUpdateDelayMilliseconds} setting alone gives.
+     * 
+     * <p>
+     * For the meaning of the parameters, see {@link #getTemplate(String, Locale, Serializable)}
+     * 
+     * <p>
+     * This method is thread-safe and can be called while the engine processes templates.
+     * 
+     * @throws UnsupportedOperationException If the {@link TemplateResolver} implementation doesn't support this
+     *        operation.
+     */
+    public abstract void removeTemplateFromCache(String name, Locale locale, Serializable customLookupCondition)
+            throws IOException, UnsupportedOperationException;
+
+    /**
+     * Converts a name to a template root directory based name, so that it can be used to find a template without
+     * knowing what (like which template) has referred to it. The rules depend on the name format, but a typical example
+     * is converting "t.ftl" with base "sub/contex.ftl" to "sub/t.ftl".
+     * 
+     * <p>
+     * Some implementations, notably {@link DefaultTemplateResolver}, delegates this check to the
+     * {@link TemplateNameFormat} coming from the {@link Configuration}.
+     * 
+     * @param baseName
+     *            Maybe a file name, maybe a directory name. The meaning of file name VS directory name depends on the
+     *            name format, but typically, something like "foo/bar/" is a directory name, and something like
+     *            "foo/bar" is a file name, and thus in the last case the effective base is "foo/" (i.e., the directory
+     *            that contains the file). Not {@code null}.
+     * @param targetName
+     *            The name to convert. This usually comes from a template that refers to another template by name. It
+     *            can be a relative name, or an absolute name. (In typical name formats absolute names start with
+     *            {@code "/"} or maybe with an URL scheme, and all others are relative). Not {@code null}.
+     * 
+     * @return The path in template root directory relative format, or even an absolute name (where the root directory
+     *         is not the real root directory of the file system, but the imaginary directory that exists to store the
+     *         templates). The standard implementations shipped with FreeMarker always return a root relative path
+     *         (except if the name starts with an URI schema, in which case a full URI is returned).
+     */
+    public abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException;
+
+    /**
+     * Normalizes a template root directory based name (relative to the root or absolute), so that equivalent names
+     * become equivalent according {@link String#equals(Object)} too. The rules depend on the name format, but typical
+     * examples are "sub/../t.ftl" to "t.ftl", "sub/./t.ftl" to "sub/t.ftl" and "/t.ftl" to "t.ftl".
+     * 
+     * <p>
+     * Some implementations, notably {@link DefaultTemplateResolver}, delegates this check to the {@link TemplateNameFormat}
+     * coming from the {@link Configuration}. The standard {@link TemplateNameFormat} implementations shipped with
+     * FreeMarker always returns a root relative path (except if the name starts with an URI schema, in which case a
+     * full URI is returned), for example, "/foo.ftl" becomes to "foo.ftl".
+     * 
+     * @param name
+     *            The root based name. Not {@code null}.
+     * 
+     * @return The normalized root based name. Not {@code null}.
+     */
+    public abstract String normalizeRootBasedName(String name) throws MalformedTemplateNameException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateSourceMatcher.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateSourceMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateSourceMatcher.java
new file mode 100644
index 0000000..ca38c39
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateSourceMatcher.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * @since 2.3.24
+ */
+public abstract class TemplateSourceMatcher {
+    
+    abstract boolean matches(String sourceName, Object templateSource) throws IOException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/_CacheAPI.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/_CacheAPI.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/_CacheAPI.java
new file mode 100644
index 0000000..09b216f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/_CacheAPI.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.templateresolver;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
+ * access things inside this package that users shouldn't. 
+ */ 
+public final class _CacheAPI {
+
+    private _CacheAPI() {
+        // Not meant to be instantiated
+    }
+    
+    public static String toRootBasedName(TemplateNameFormat templateNameFormat, String baseName, String targetName)
+            throws MalformedTemplateNameException {
+        return templateNameFormat.toRootBasedName(baseName, targetName);
+    }
+
+    public static String normalizeRootBasedName(TemplateNameFormat templateNameFormat, String name)
+            throws MalformedTemplateNameException {
+        return templateNameFormat.normalizeRootBasedName(name);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java
new file mode 100644
index 0000000..417566f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoaderSession;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * A {@link TemplateLoader} that uses a {@link Map} with {@code byte[]} as its source of templates. This is similar to
+ * {@link StringTemplateLoader}, but uses {@code byte[]} instead of {@link String}; see more details there.
+ * 
+ * <p>Note that {@link ByteArrayTemplateLoader} can't be used with a distributed (cluster-wide) {@link CacheStorage},
+ * as it produces {@link TemplateLoadingSource}-s that deliberately throw exception on serialization (because the
+ * content is only accessible within a single JVM, and is also volatile).
+ */
+// TODO JUnit tests
+public class ByteArrayTemplateLoader implements TemplateLoader {
+    
+    private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
+    
+    private final long instanceId = INSTANCE_COUNTER.get();
+    private final AtomicLong templatesRevision = new AtomicLong();
+    private final ConcurrentMap<String, ContentHolder> templates = new ConcurrentHashMap<>();
+    
+    /**
+     * Puts a template into the template loader. The name can contain slashes to denote logical directory structure, but
+     * must not start with a slash. Each template will get an unique revision number, thus replacing a template will
+     * cause the template cache to reload it (when the update delay expires).
+     * 
+     * <p>This method is thread-safe.
+     * 
+     * @param name
+     *            the name of the template.
+     * @param content
+     *            the source code of the template.
+     */
+    public void putTemplate(String name, byte[] content) {
+        templates.put(
+                name,
+                new ContentHolder(content, new Source(instanceId, name), templatesRevision.incrementAndGet()));
+    }
+    
+    /**
+     * Removes the template with the specified name if it was added earlier.
+     * 
+     * <p>
+     * This method is thread-safe.
+     * 
+     * @param name
+     *            Exactly the key with which the template was added.
+     * 
+     * @return Whether a template was found with the given key (and hence was removed now)
+     */ 
+    public boolean removeTemplate(String name) {
+        return templates.remove(name) != null;
+    }
+    
+    @Override
+    public TemplateLoaderSession createSession() {
+        return null;
+    }
+
+    @Override
+    public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom,
+            Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException {
+        ContentHolder contentHolder = templates.get(name);
+        if (contentHolder == null) {
+            return TemplateLoadingResult.NOT_FOUND;
+        } else if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(contentHolder.source)
+                && Objects.equals(ifVersionDiffersFrom, contentHolder.version)) {
+            return TemplateLoadingResult.NOT_MODIFIED;
+        } else {
+            return new TemplateLoadingResult(
+                    contentHolder.source, contentHolder.version,
+                    new ByteArrayInputStream(contentHolder.content),
+                    null);
+        }
+    }
+
+    @Override
+    public void resetState() {
+        // Do nothing
+    }
+
+    /**
+     * Show class name and some details that are useful in template-not-found errors.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_TemplateLoaderUtils.getClassNameForToString(this));
+        sb.append("(Map { ");
+        int cnt = 0;
+        for (String name : templates.keySet()) {
+            cnt++;
+            if (cnt != 1) {
+                sb.append(", ");
+            }
+            if (cnt > 10) {
+                sb.append("...");
+                break;
+            }
+            sb.append(_StringUtil.jQuote(name));
+            sb.append("=...");
+        }
+        if (cnt != 0) {
+            sb.append(' ');
+        }
+        sb.append("})");
+        return sb.toString();
+    }
+
+    private static class ContentHolder {
+        private final byte[] content;
+        private final Source source;
+        private final long version;
+        
+        public ContentHolder(byte[] content, Source source, long version) {
+            this.content = content;
+            this.source = source;
+            this.version = version;
+        }
+        
+    }
+    
+    @SuppressWarnings("serial")
+    private static class Source implements TemplateLoadingSource {
+        
+        private final long instanceId;
+        private final String name;
+        
+        public Source(long instanceId, String name) {
+            this.instanceId = instanceId;
+            this.name = name;
+        }
+    
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (int) (instanceId ^ (instanceId >>> 32));
+            result = prime * result + ((name == null) ? 0 : name.hashCode());
+            return result;
+        }
+    
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            Source other = (Source) obj;
+            if (instanceId != other.instanceId) return false;
+            if (name == null) {
+                if (other.name != null) return false;
+            } else if (!name.equals(other.name)) {
+                return false;
+            }
+            return true;
+        }
+        
+        private void writeObject(ObjectOutputStream out) throws IOException {
+            throw new IOException(ByteArrayTemplateLoader.class.getName()
+                    + " sources can't be serialized, as they don't support clustering.");
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java
new file mode 100644
index 0000000..331307f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
+import org.apache.freemarker.core.util._NullArgumentException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * A {@link TemplateLoader} that can load templates from the "classpath". Naturally, it can load from jar files, or from
+ * anywhere where Java can load classes from. Internally, it uses {@link Class#getResource(String)} or
+ * {@link ClassLoader#getResource(String)} to load templates.
+ */
+// TODO
+public class ClassTemplateLoader extends URLTemplateLoader {
+    
+    private final Class<?> resourceLoaderClass;
+    private final ClassLoader classLoader;
+    private final String basePackagePath;
+
+    /**
+     * Creates a template loader that will use the {@link Class#getResource(String)} method of the specified class to
+     * load the resources, and the specified base package path (absolute or relative).
+     *
+     * <p>
+     * Examples:
+     * <ul>
+     * <li>Relative base path (will load from the {@code com.example.myapplication.templates} package):<br>
+     * {@code new ClassTemplateLoader(com.example.myapplication.SomeClass.class, "templates")}
+     * <li>Absolute base path:<br>
+     * {@code new ClassTemplateLoader(somepackage.SomeClass.class, "/com/example/myapplication/templates")}
+     * </ul>
+     *
+     * @param resourceLoaderClass
+     *            The class whose {@link Class#getResource(String)} method will be used to load the templates. Be sure
+     *            that you chose a class whose defining class-loader sees the templates. This parameter can't be
+     *            {@code null}.
+     * @param basePackagePath
+     *            The package that contains the templates, in path ({@code /}-separated) format. If it doesn't start
+     *            with a {@code /} then it's relative to the path (package) of the {@code resourceLoaderClass} class. If
+     *            it starts with {@code /} then it's relative to the root of the package hierarchy. Note that path
+     *            components should be separated by forward slashes independently of the separator character used by the
+     *            underlying operating system. This parameter can't be {@code null}.
+     * 
+     * @see #ClassTemplateLoader(ClassLoader, String)
+     */
+    public ClassTemplateLoader(Class<?> resourceLoaderClass, String basePackagePath) {
+        this(resourceLoaderClass, false, null, basePackagePath);
+    }
+
+    /**
+     * Similar to {@link #ClassTemplateLoader(Class, String)}, but instead of {@link Class#getResource(String)} it uses
+     * {@link ClassLoader#getResource(String)}. Because a {@link ClassLoader} isn't bound to any Java package, it
+     * doesn't mater if the {@code basePackagePath} starts with {@code /} or not, it will be always relative to the root
+     * of the package hierarchy
+     */
+    public ClassTemplateLoader(ClassLoader classLoader, String basePackagePath) {
+        this(null, true, classLoader, basePackagePath);
+    }
+
+    private ClassTemplateLoader(Class<?> resourceLoaderClass, boolean allowNullResourceLoaderClass,
+            ClassLoader classLoader, String basePackagePath) {
+        if (!allowNullResourceLoaderClass) {
+            _NullArgumentException.check("resourceLoaderClass", resourceLoaderClass);
+        }
+        _NullArgumentException.check("basePackagePath", basePackagePath);
+
+        // Either set a non-null resourceLoaderClass or a non-null classLoader, not both:
+        this.resourceLoaderClass = classLoader == null ? (resourceLoaderClass == null ? getClass()
+                : resourceLoaderClass) : null;
+        if (this.resourceLoaderClass == null && classLoader == null) {
+            throw new _NullArgumentException("classLoader");
+        }
+        this.classLoader = classLoader;
+
+        String canonBasePackagePath = canonicalizePrefix(basePackagePath);
+        if (this.classLoader != null && canonBasePackagePath.startsWith("/")) {
+            canonBasePackagePath = canonBasePackagePath.substring(1);
+        }
+        this.basePackagePath = canonBasePackagePath;
+    }
+
+    private static boolean isSchemeless(String fullPath) {
+        int i = 0;
+        int ln = fullPath.length();
+
+        // Skip a single initial /, as things like "/file:/..." might work:
+        if (i < ln && fullPath.charAt(i) == '/') i++;
+
+        // Check if there's no ":" earlier than a '/', as the URLClassLoader
+        // could interpret that as an URL scheme:
+        while (i < ln) {
+            char c = fullPath.charAt(i);
+            if (c == '/') return true;
+            if (c == ':') return false;
+            i++;
+        }
+        return true;
+    }
+
+    /**
+     * Show class name and some details that are useful in template-not-found errors.
+     */
+    @Override
+    public String toString() {
+        return _TemplateLoaderUtils.getClassNameForToString(this) + "("
+                + (resourceLoaderClass != null
+                        ? "resourceLoaderClass=" + resourceLoaderClass.getName()
+                        : "classLoader=" + _StringUtil.jQuote(classLoader))
+                + ", basePackagePath"
+                + "="
+                + _StringUtil.jQuote(basePackagePath)
+                + (resourceLoaderClass != null
+                        ? (basePackagePath.startsWith("/") ? "" : " /* relatively to resourceLoaderClass pkg */")
+                        : ""
+                )
+                + ")";
+    }
+
+    /**
+     * See the similar parameter of {@link #ClassTemplateLoader(Class, String)}; {@code null} when other mechanism is
+     * used to load the resources.
+     */
+    public Class<?> getResourceLoaderClass() {
+        return resourceLoaderClass;
+    }
+
+    /**
+     * See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; {@code null} when other mechanism
+     * is used to load the resources.
+     */
+    public ClassLoader getClassLoader() {
+        return classLoader;
+    }
+
+    /**
+     * See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; note that this is a normalized
+     * version of what was actually passed to the constructor.
+     */
+    public String getBasePackagePath() {
+        return basePackagePath;
+    }
+
+    @Override
+    protected URL getURL(String name) {
+        String fullPath = basePackagePath + name;
+    
+        // Block java.net.URLClassLoader exploits:
+        if (basePackagePath.equals("/") && !isSchemeless(fullPath)) {
+            return null;
+        }
+    
+        return resourceLoaderClass != null ? resourceLoaderClass.getResource(fullPath) : classLoader
+                .getResource(fullPath);
+    }
+
+    @Override
+    protected TemplateLoadingResult extractNegativeResult(URLConnection conn) throws IOException {
+        return null;
+    }
+
+}
\ 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/impl/DefaultTemplateLookupStrategy.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateLookupStrategy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateLookupStrategy.java
new file mode 100644
index 0000000..185f5b9
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateLookupStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.templateresolver.impl;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import org.apache.freemarker.core.templateresolver.TemplateLookupContext;
+import org.apache.freemarker.core.templateresolver.TemplateLookupResult;
+import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+
+/**
+ * <p>
+ * The default lookup strategy of FreeMarker.
+ * 
+ * <p>
+ * Through an example: Assuming localized lookup is enabled and that a template is requested for the name
+ * {@code example.ftl} and {@code Locale("es", "ES", "Traditional_WIN")}, it will try the following template names,
+ * in this order: {@code "foo_en_AU_Traditional_WIN.ftl"}, {@code "foo_en_AU_Traditional.ftl"},
+ * {@code "foo_en_AU.ftl"}, {@code "foo_en.ftl"}, {@code "foo.ftl"}. It stops at the first variation where it finds
+ * a template. (If the template name contains "*" steps, finding the template for the attempted localized variation
+ * happens with the template acquisition mechanism.) If localized lookup is disabled, it won't try to add any locale
+ * strings, so it just looks for {@code "foo.ftl"}.
+ * 
+ * <p>
+ * The generation of the localized name variation with the default lookup strategy, happens like this: It removes
+ * the file extension (the part starting with the <em>last</em> dot), then appends {@link Locale#toString()} after
+ * it, and puts back the extension. Then it starts to remove the parts from the end of the locale, considering
+ * {@code "_"} as the separator between the parts. It won't remove parts that are not part of the locale string
+ * (like if the requested template name is {@code foo_bar.ftl}, it won't remove the {@code "_bar"}).
+ */
+public class DefaultTemplateLookupStrategy extends TemplateLookupStrategy {
+    
+    public static final DefaultTemplateLookupStrategy INSTANCE = new DefaultTemplateLookupStrategy();
+    
+    private DefaultTemplateLookupStrategy() {
+        //
+    }
+    
+    @Override
+    public  <R extends TemplateLookupResult> R lookup(TemplateLookupContext<R> ctx) throws IOException {
+        return ctx.lookupWithLocalizedThenAcquisitionStrategy(ctx.getTemplateName(), ctx.getTemplateLocale());
+    }
+    
+}
\ 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/impl/DefaultTemplateNameFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java
new file mode 100644
index 0000000..69fa390
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormat.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.templateresolver.impl;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.TemplateNotFoundException;
+import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * The default template name format only when {@link Configuration#getIncompatibleImprovements()
+ * incompatible_improvements} is set to 2.4.0 (or higher). This is not the out-of-the-box default format of FreeMarker
+ * 2.4.x, because the default {@code incompatible_improvements} is still 2.3.0 there.
+ * 
+ * <p>
+ * Differences to the {@link DefaultTemplateNameFormatFM2} format:
+ * 
+ * <ul>
+ * 
+ * <li>The scheme and the path need not be separated with {@code "://"} anymore, only with {@code ":"}. This makes
+ * template names like {@code "classpath:foo.ftl"} interpreted as an absolute name with scheme {@code "classpath"}
+ * and absolute path "foo.ftl". The scheme name before the {@code ":"} can't contain {@code "/"}, or else it's
+ * treated as a malformed name. The scheme part can be separated either with {@code "://"} or just {@code ":"} from
+ * the path. Hence, {@code myschme:/x} is normalized to {@code myschme:x}, while {@code myschme:///x} is normalized
+ * to {@code myschme://x}, but {@code myschme://x} or {@code myschme:/x} aren't changed by normalization. It's up
+ * the {@link TemplateLoader} to which the normalized names are passed to decide which of these scheme separation
+ * conventions are valid (maybe both).</li>
+ * 
+ * <li>{@code ":"} is not allowed in template names, except as the scheme separator (see previous point).
+ * 
+ * <li>Malformed paths throw {@link MalformedTemplateNameException} instead of acting like if the template wasn't
+ * found.
+ * 
+ * <li>{@code "\"} (backslash) is not allowed in template names, and causes {@link MalformedTemplateNameException}.
+ * With {@link DefaultTemplateNameFormatFM2} you would certainly end up with a {@link TemplateNotFoundException} (or
+ * worse, it would work, but steps like {@code ".."} wouldn't be normalized by FreeMarker).
+ * 
+ * <li>Template names might end with {@code /}, like {@code "foo/"}, and the presence or lack of the terminating
+ * {@code /} is seen as significant. While their actual interpretation is up to the {@link TemplateLoader},
+ * operations that manipulate template names assume that the last step refers to a "directory" as opposed to a
+ * "file" exactly if the terminating {@code /} is present. Except, the empty name is assumed to refer to the root
+ * "directory" (despite that it doesn't end with {@code /}).
+ *
+ * <li>{@code //} is normalized to {@code /}, except of course if it's in the scheme name terminator. Like
+ * {@code foo//bar///baaz.ftl} is normalized to {@code foo/bar/baaz.ftl}. (In general, 0 long step names aren't
+ * possible anymore.)</li>
+ * 
+ * <li>The {@code ".."} bugs of the legacy normalizer are oms: {@code ".."} steps has removed the preceding
+ * {@code "."} or {@code "*"} or scheme steps, not treating them specially as they should be. Now these work as
+ * expected. Examples: {@code "a/./../c"} has become to {@code "a/c"}, now it will be {@code "c"}; {@code "a/b/*}
+ * {@code /../c"} has become to {@code "a/b/c"}, now it will be {@code "a/*}{@code /c"}; {@code "scheme://.."} has
+ * become to {@code "scheme:/"}, now it will be {@code null} ({@link TemplateNotFoundException}) for backing out of
+ * the root directory.</li>
+ * 
+ * <li>As now directory paths has to be handled as well, it recognizes terminating, leading, and lonely {@code ".."}
+ * and {@code "."} steps. For example, {@code "foo/bar/.."} now becomes to {@code "foo/"}</li>
+ * 
+ * <li>Multiple consecutive {@code *} steps are normalized to one</li>
+ * 
+ * </ul>
+ */
+public final class DefaultTemplateNameFormat extends TemplateNameFormat {
+    
+    public static DefaultTemplateNameFormat INSTANCE = new DefaultTemplateNameFormat();
+    
+    private DefaultTemplateNameFormat() {
+        //
+    }
+    
+    @Override
+    public String toRootBasedName(String baseName, String targetName) {
+        if (findSchemeSectionEnd(targetName) != 0) {
+            return targetName;
+        } else if (targetName.startsWith("/")) {  // targetName is an absolute path
+            final String targetNameAsRelative = targetName.substring(1);
+            final int schemeSectionEnd = findSchemeSectionEnd(baseName);
+            if (schemeSectionEnd == 0) {
+                return targetNameAsRelative;
+            } else {
+                // Prepend the scheme of baseName:
+                return baseName.substring(0, schemeSectionEnd) + targetNameAsRelative;
+            }
+        } else {  // targetName is a relative path
+            if (!baseName.endsWith("/")) {
+                // Not a directory name => get containing directory name
+                int baseEnd = baseName.lastIndexOf("/") + 1;
+                if (baseEnd == 0) {
+                    // For something like "classpath:t.ftl", must not remove the scheme part:
+                    baseEnd = findSchemeSectionEnd(baseName);
+                }
+                baseName = baseName.substring(0, baseEnd);
+            }
+            return baseName + targetName;
+        }
+    }
+
+    @Override
+    public String normalizeRootBasedName(final String name) throws MalformedTemplateNameException {
+        // Disallow 0 for security reasons.
+        checkNameHasNoNullCharacter(name);
+
+        if (name.indexOf('\\') != -1) {
+            throw new MalformedTemplateNameException(
+                    name,
+                    "Backslash (\"\\\") is not allowed in template names. Use slash (\"/\") instead.");
+        }
+        
+        // Split name to a scheme and a path:
+        final String scheme;
+        String path;
+        {
+            int schemeSectionEnd = findSchemeSectionEnd(name);
+            if (schemeSectionEnd == 0) {
+                scheme = null;
+                path = name;
+            } else {
+                scheme = name.substring(0, schemeSectionEnd);
+                path = name.substring(schemeSectionEnd);
+            }
+        }
+        
+        if (path.indexOf(':') != -1) {
+            throw new MalformedTemplateNameException(name,
+                    "The ':' character can only be used after the scheme name (if there's any), "
+                    + "not in the path part");
+        }
+        
+        path = removeRedundantSlashes(path);
+        // path now doesn't start with "/"
+        
+        path = removeDotSteps(path);
+        
+        path = resolveDotDotSteps(path, name);
+
+        path = removeRedundantStarSteps(path);
+        
+        return scheme == null ? path : scheme + path;
+    }
+
+    private int findSchemeSectionEnd(String name) {
+        int schemeColonIdx = name.indexOf(":");
+        if (schemeColonIdx == -1 || name.lastIndexOf('/', schemeColonIdx - 1) != -1) {
+            return 0;
+        } else {
+            // If there's a following "//", it's treated as the part of the scheme section:
+            if (schemeColonIdx + 2 < name.length()
+                    && name.charAt(schemeColonIdx + 1) == '/' && name.charAt(schemeColonIdx + 2) == '/') {
+                return schemeColonIdx + 3;
+            } else {
+                return schemeColonIdx + 1;
+            }
+        }
+    }
+
+    private String removeRedundantSlashes(String path) {
+        String prevName;
+        do {
+            prevName = path;
+            path = _StringUtil.replace(path, "//", "/");
+        } while (prevName != path);
+        return path.startsWith("/") ? path.substring(1) : path;
+    }
+
+    private String removeDotSteps(String path) {
+        int nextFromIdx = path.length() - 1;
+        findDotSteps: while (true) {
+            final int dotIdx = path.lastIndexOf('.', nextFromIdx);
+            if (dotIdx < 0) {
+                return path;
+            }
+            nextFromIdx = dotIdx - 1;
+            
+            if (dotIdx != 0 && path.charAt(dotIdx - 1) != '/') {
+                // False alarm
+                continue findDotSteps;
+            }
+            
+            final boolean slashRight;
+            if (dotIdx + 1 == path.length()) {
+                slashRight = false;
+            } else if (path.charAt(dotIdx + 1) == '/') {
+                slashRight = true;
+            } else {
+                // False alarm
+                continue findDotSteps;
+            }
+            
+            if (slashRight) { // "foo/./bar" or "./bar" 
+                path = path.substring(0, dotIdx) + path.substring(dotIdx + 2);
+            } else { // "foo/." or "."
+                path = path.substring(0, path.length() - 1);
+            }
+        }
+    }
+
+    /**
+     * @param name The original name, needed for exception error messages.
+     */
+    private String resolveDotDotSteps(String path, final String name) throws MalformedTemplateNameException {
+        int nextFromIdx = 0;
+        findDotDotSteps: while (true) {
+            final int dotDotIdx = path.indexOf("..", nextFromIdx);
+            if (dotDotIdx < 0) {
+                return path;
+            }
+
+            if (dotDotIdx == 0) {
+                throw newRootLeavingException(name);
+            } else if (path.charAt(dotDotIdx - 1) != '/') {
+                // False alarm
+                nextFromIdx = dotDotIdx + 3;
+                continue findDotDotSteps;
+            }
+            // Here we know that it has a preceding "/".
+            
+            final boolean slashRight;
+            if (dotDotIdx + 2 == path.length()) {
+                slashRight = false;
+            } else if (path.charAt(dotDotIdx + 2) == '/') {
+                slashRight = true;
+            } else {
+                // False alarm
+                nextFromIdx = dotDotIdx + 3;
+                continue findDotDotSteps;
+            }
+            
+            int previousSlashIdx;
+            boolean skippedStarStep = false;
+            {
+                int searchSlashBacwardsFrom = dotDotIdx - 2; // before the "/.."
+                scanBackwardsForSlash: while (true) {
+                    if (searchSlashBacwardsFrom == -1) {
+                        throw newRootLeavingException(name);
+                    }
+                    previousSlashIdx = path.lastIndexOf('/', searchSlashBacwardsFrom);
+                    if (previousSlashIdx == -1) {
+                        if (searchSlashBacwardsFrom == 0 && path.charAt(0) == '*') {
+                            // "*/.."
+                            throw newRootLeavingException(name);
+                        }
+                        break scanBackwardsForSlash;
+                    }
+                    if (path.charAt(previousSlashIdx + 1) == '*' && path.charAt(previousSlashIdx + 2) == '/') {
+                        skippedStarStep = true;
+                        searchSlashBacwardsFrom = previousSlashIdx - 1; 
+                    } else {
+                        break scanBackwardsForSlash;
+                    }
+                }
+            }
+            
+            // Note: previousSlashIdx is possibly -1
+            // Removed part in {}: "a/{b/*/../}c" or "a/{b/*/..}"
+            path = path.substring(0, previousSlashIdx + 1)
+                    + (skippedStarStep ? "*/" : "")
+                    + path.substring(dotDotIdx + (slashRight ? 3 : 2));
+            nextFromIdx = previousSlashIdx + 1;
+        }
+    }
+
+    private String removeRedundantStarSteps(String path) {
+        String prevName;
+        removeDoubleStarSteps: do {
+            int supiciousIdx = path.indexOf("*/*");
+            if (supiciousIdx == -1) {
+                break removeDoubleStarSteps;
+            }
+    
+            prevName = path;
+            
+            // Is it delimited on both sided by "/" or by the string boundaires? 
+            if ((supiciousIdx == 0 || path.charAt(supiciousIdx - 1) == '/')
+                    && (supiciousIdx + 3 == path.length() || path.charAt(supiciousIdx + 3) == '/')) {
+                path = path.substring(0, supiciousIdx) + path.substring(supiciousIdx + 2); 
+            }
+        } while (prevName != path);
+        
+        // An initial "*" step is redundant:
+        if (path.startsWith("*")) {
+            if (path.length() == 1) {
+                path = "";
+            } else if (path.charAt(1) == '/') {
+                path = path.substring(2); 
+            }
+            // else: it's wasn't a "*" step.
+        }
+        
+        return path;
+    }
+    
+}
\ 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/impl/DefaultTemplateNameFormatFM2.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java
new file mode 100644
index 0000000..c5db8e5
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateNameFormatFM2.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.templateresolver.impl;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
+
+/**
+ * The default template name format when {@link Configuration#getIncompatibleImprovements() incompatible_improvements}
+ * is below 2.4.0. As of FreeMarker 2.4.0, the default {@code incompatible_improvements} is still {@code 2.3.0}, and it
+ * will certainly remain so for a very long time. In new projects it's highly recommended to use
+ * {@link DefaultTemplateNameFormat#INSTANCE} instead.
+ * 
+ * @deprecated [FM3] Remove
+ */
+@Deprecated
+public final class DefaultTemplateNameFormatFM2 extends TemplateNameFormat {
+    
+    public static final DefaultTemplateNameFormatFM2 INSTANCE = new DefaultTemplateNameFormatFM2();
+    
+    private DefaultTemplateNameFormatFM2() {
+        //
+    }
+    
+    @Override
+    public String toRootBasedName(String baseName, String targetName) {
+        if (targetName.indexOf("://") > 0) {
+            return targetName;
+        } else if (targetName.startsWith("/")) {
+            int schemeSepIdx = baseName.indexOf("://");
+            if (schemeSepIdx > 0) {
+                return baseName.substring(0, schemeSepIdx + 2) + targetName;
+            } else {
+                return targetName.substring(1);
+            }
+        } else {
+            if (!baseName.endsWith("/")) {
+                baseName = baseName.substring(0, baseName.lastIndexOf("/") + 1);
+            }
+            return baseName + targetName;
+        }
+    }
+
+    @Override
+    public String normalizeRootBasedName(final String name) throws MalformedTemplateNameException {
+        // Disallow 0 for security reasons.
+        checkNameHasNoNullCharacter(name);
+        
+        // The legacy algorithm haven't considered schemes, so the name is in effect a path.
+        // Also, note that `path` will be repeatedly replaced below, while `name` is final.
+        String path = name;
+        
+        for (; ; ) {
+            int parentDirPathLoc = path.indexOf("/../");
+            if (parentDirPathLoc == 0) {
+                // If it starts with /../, then it reaches outside the template
+                // root.
+                throw newRootLeavingException(name);
+            }
+            if (parentDirPathLoc == -1) {
+                if (path.startsWith("../")) {
+                    throw newRootLeavingException(name);
+                }
+                break;
+            }
+            int previousSlashLoc = path.lastIndexOf('/', parentDirPathLoc - 1);
+            path = path.substring(0, previousSlashLoc + 1) +
+                   path.substring(parentDirPathLoc + "/../".length());
+        }
+        for (; ; ) {
+            int currentDirPathLoc = path.indexOf("/./");
+            if (currentDirPathLoc == -1) {
+                if (path.startsWith("./")) {
+                    path = path.substring("./".length());
+                }
+                break;
+            }
+            path = path.substring(0, currentDirPathLoc) +
+                   path.substring(currentDirPathLoc + "/./".length() - 1);
+        }
+        // Editing can leave us with a leading slash; strip it.
+        if (path.length() > 1 && path.charAt(0) == '/') {
+            path = path.substring(1);
+        }
+        return path;
+    }
+    
+}
\ No newline at end of file


Mime
View raw message