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/02/17 01:53:40 UTC
[4/6] incubator-freemarker git commit: Continued subpackage content
rearrangement...
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java
deleted file mode 100644
index 0f75912..0000000
--- a/src/main/java/org/apache/freemarker/core/templateresolver/SoftCacheStorage.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.templateresolver;
-
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-/**
- * Soft cache storage is a cache storage that uses {@link SoftReference} objects to hold the objects it was passed,
- * therefore allows the garbage collector to purge the cache when it determines that it wants to free up memory. This
- * class is thread-safe to the extent that its underlying map is. The parameterless constructor uses a thread-safe map
- * since 2.3.24 or Java 5.
- *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
- */
-public class SoftCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
- private static final Method atomicRemove = getAtomicRemoveMethod();
-
- private final ReferenceQueue queue = new ReferenceQueue();
- private final Map map;
- private final boolean concurrent;
-
- /**
- * Creates an instance that uses a {@link ConcurrentMap} internally.
- */
- public SoftCacheStorage() {
- this(new ConcurrentHashMap());
- }
-
- /**
- * Returns true if the underlying Map is a {@code ConcurrentMap}.
- */
- public boolean isConcurrent() {
- return concurrent;
- }
-
- public SoftCacheStorage(Map backingMap) {
- map = backingMap;
- this.concurrent = map instanceof ConcurrentMap;
- }
-
- public Object get(Object key) {
- processQueue();
- Reference ref = (Reference) map.get(key);
- return ref == null ? null : ref.get();
- }
-
- public void put(Object key, Object value) {
- processQueue();
- map.put(key, new SoftValueReference(key, value, queue));
- }
-
- public void remove(Object key) {
- processQueue();
- map.remove(key);
- }
-
- public void clear() {
- map.clear();
- processQueue();
- }
-
- /**
- * Returns a close approximation of the number of cache entries.
- *
- * @since 2.3.21
- */
- public int getSize() {
- processQueue();
- return map.size();
- }
-
- private void processQueue() {
- for (; ; ) {
- SoftValueReference ref = (SoftValueReference) queue.poll();
- if (ref == null) {
- return;
- }
- Object key = ref.getKey();
- if (concurrent) {
- try {
- atomicRemove.invoke(map, new Object[] { key, ref });
- } catch (IllegalAccessException e) {
- throw new UndeclaredThrowableException(e);
- } catch (InvocationTargetException e) {
- throw new UndeclaredThrowableException(e);
- }
- } else if (map.get(key) == ref) {
- map.remove(key);
- }
- }
- }
-
- private static final class SoftValueReference extends SoftReference {
- private final Object key;
-
- SoftValueReference(Object key, Object value, ReferenceQueue queue) {
- super(value, queue);
- this.key = key;
- }
-
- Object getKey() {
- return key;
- }
- }
-
- private static Method getAtomicRemoveMethod() {
- try {
- return Class.forName("java.util.concurrent.ConcurrentMap").getMethod("remove", new Class[] { Object.class, Object.class });
- } catch (ClassNotFoundException e) {
- return null;
- } catch (NoSuchMethodException e) {
- throw new UndeclaredThrowableException(e);
- }
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java
deleted file mode 100644
index 5d0d378..0000000
--- a/src/main/java/org/apache/freemarker/core/templateresolver/StringTemplateLoader.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.templateresolver;
-
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.io.StringReader;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.freemarker.core.util.StringUtil;
-
-/**
- * A {@link TemplateLoader} that uses a {@link Map} with {@code String} as its source of templates. This is similar to
- * {@link StringTemplateLoader}, but uses {@code String} instead of {@link String}; see more details there.
- *
- * <p>Note that {@link StringTemplateLoader} can't be used with a distributed (cluster-wide) {@link CacheStorage},
- * as it produces {@link TemplateLoadingSource}-s that deliberately throw exception on serialization (because the
- * content is only accessible within a single JVM, and is also volatile).
- */
-// TODO JUnit tests
-public class StringTemplateLoader implements TemplateLoader {
-
- private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
-
- private final long instanceId = INSTANCE_COUNTER.get();
- private final AtomicLong templatesRevision = new AtomicLong();
- private final ConcurrentMap<String, ContentHolder> templates = new ConcurrentHashMap<>();
-
- /**
- * Puts a template into the template loader. The name can contain slashes to denote logical directory structure, but
- * must not start with a slash. Each template will get an unique revision number, thus replacing a template will
- * cause the template cache to reload it (when the update delay expires).
- *
- * <p>This method is thread-safe.
- *
- * @param name
- * the name of the template.
- * @param content
- * the source code of the template.
- */
- public void putTemplate(String name, String content) {
- templates.put(
- name,
- new ContentHolder(content, new Source(instanceId, name), templatesRevision.incrementAndGet()));
- }
-
- /**
- * Removes the template with the specified name if it was added earlier.
- *
- * <p>
- * This method is thread-safe.
- *
- * @param name
- * Exactly the key with which the template was added.
- *
- * @return Whether a template was found with the given key (and hence was removed now)
- */
- public boolean removeTemplate(String name) {
- return templates.remove(name) != null;
- }
-
- @Override
- public TemplateLoaderSession createSession() {
- return null;
- }
-
- @Override
- public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom,
- Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException {
- ContentHolder contentHolder = templates.get(name);
- if (contentHolder == null) {
- return TemplateLoadingResult.NOT_FOUND;
- } else if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(contentHolder.source)
- && Objects.equals(ifVersionDiffersFrom, contentHolder.version)) {
- return TemplateLoadingResult.NOT_MODIFIED;
- } else {
- return new TemplateLoadingResult(
- contentHolder.source, contentHolder.version,
- new StringReader(contentHolder.content),
- null);
- }
- }
-
- @Override
- public void resetState() {
- // Do nothing
- }
-
- /**
- * Show class name and some details that are useful in template-not-found errors.
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(_TemplateLoaderUtils.getClassNameForToString(this));
- sb.append("(Map { ");
- int cnt = 0;
- for (String name : templates.keySet()) {
- cnt++;
- if (cnt != 1) {
- sb.append(", ");
- }
- if (cnt > 10) {
- sb.append("...");
- break;
- }
- sb.append(StringUtil.jQuote(name));
- sb.append("=...");
- }
- if (cnt != 0) {
- sb.append(' ');
- }
- sb.append("})");
- return sb.toString();
- }
-
- private static class ContentHolder {
- private final String content;
- private final Source source;
- private final long version;
-
- public ContentHolder(String content, Source source, long version) {
- this.content = content;
- this.source = source;
- this.version = version;
- }
-
- }
-
- @SuppressWarnings("serial")
- private static class Source implements TemplateLoadingSource {
-
- private final long instanceId;
- private final String name;
-
- public Source(long instanceId, String name) {
- this.instanceId = instanceId;
- this.name = name;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + (int) (instanceId ^ (instanceId >>> 32));
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- Source other = (Source) obj;
- if (instanceId != other.instanceId) return false;
- if (name == null) {
- if (other.name != null) return false;
- } else if (!name.equals(other.name)) {
- return false;
- }
- return true;
- }
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- throw new IOException(StringTemplateLoader.class.getName()
- + " sources can't be serialized, as they don't support clustering.");
- }
-
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java b/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java
deleted file mode 100644
index ad5079f..0000000
--- a/src/main/java/org/apache/freemarker/core/templateresolver/StrongCacheStorage.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.templateresolver;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Strong cache storage is a cache storage that simply wraps a {@link Map}. It holds a strong reference to all objects
- * it was passed, therefore prevents the cache from being purged during garbage collection. This class is always
- * thread-safe since 2.3.24, before that if we are running on Java 5 or later.
- *
- * @see org.apache.freemarker.core.Configuration#setCacheStorage(CacheStorage)
- */
-public class StrongCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
-
- private final Map map = new ConcurrentHashMap();
-
- /**
- * Always returns {@code true}.
- */
- public boolean isConcurrent() {
- return true;
- }
-
- public Object get(Object key) {
- return map.get(key);
- }
-
- public void put(Object key, Object value) {
- map.put(key, value);
- }
-
- public void remove(Object key) {
- map.remove(key);
- }
-
- /**
- * Returns a close approximation of the number of cache entries.
- *
- * @since 2.3.21
- */
- public int getSize() {
- return map.size();
- }
-
- public void clear() {
- map.clear();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
index f484224..2a58de7 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoader.java
@@ -23,6 +23,7 @@ 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
@@ -61,7 +62,7 @@ public interface TemplateLoader {
*
* @param name
* The name (template root directory relative path) of the template, already localized and normalized by
- * the {@link org.apache.freemarker.core.templateresolver.DefaultTemplateResolver cache}. It is completely up to the loader implementation to
+ * 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
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
index 9fd99ff..262dcce 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoaderSession.java
@@ -22,6 +22,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
+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
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
index 426e050..94844a4 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingResult.java
@@ -27,6 +27,7 @@ import java.util.Date;
import org.apache.freemarker.core.Configuration;
import org.apache.freemarker.core.ast.TemplateConfiguration;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
import org.apache.freemarker.core.util.NullArgumentException;
/**
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
index 42b5398..2af5721 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLoadingSource.java
@@ -21,6 +21,9 @@ 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,
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
index 304d9b6..a37e043 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
@@ -24,6 +24,7 @@ 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)}.
@@ -31,13 +32,11 @@ import org.apache.freemarker.core.Configuration;
*
* @since 2.3.22
*/
-public abstract class TemplateLookupContext {
+public abstract class TemplateLookupContext<R extends TemplateLookupResult> {
private final String templateName;
private final Locale templateLocale;
private final Object customLookupCondition;
- private final TemplateLoadingSource cachedResultSource;
- private final Serializable cachedResultVersion;
/**
* Finds the template source based on its <em>normalized</em> name; handles {@code *} steps (so called acquisition),
@@ -56,7 +55,7 @@ public abstract class TemplateLookupContext {
* not for {@link TemplateLoader}-s). Hence discarding a positive result and looking for another can
* generate substantial extra I/O.
*/
- public abstract TemplateLookupResult lookupWithAcquisitionStrategy(String templateName) throws IOException;
+ 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
@@ -64,19 +63,16 @@ public abstract class TemplateLookupContext {
* 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 TemplateLookupStrategy#DEFAULT_2_3_0}.
+ * {@link DefaultTemplateLookupStrategy#INSTANCE}.
*/
- public abstract TemplateLookupResult lookupWithLocalizedThenAcquisitionStrategy(String templateName,
+ public abstract R lookupWithLocalizedThenAcquisitionStrategy(String templateName,
Locale templateLocale) throws IOException;
/** Default visibility to prevent extending the class from outside this package. */
- TemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition,
- TemplateLoadingSource cachedResultSource, Serializable cachedResultVersion) {
+ protected TemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition) {
this.templateName = templateName;
this.templateLocale = templateLocale;
this.customLookupCondition = customLookupCondition;
- this.cachedResultSource = cachedResultSource;
- this.cachedResultVersion = cachedResultVersion;
}
/**
@@ -111,16 +107,6 @@ public abstract class TemplateLookupContext {
* {@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 TemplateLookupResult createNegativeLookupResult() {
- return TemplateLookupResult.createNegativeResult();
- }
-
- TemplateLoadingSource getCachedResultSource() {
- return cachedResultSource;
- }
-
- Serializable getCachedResultVersion() {
- return cachedResultVersion;
- }
+ public abstract R createNegativeLookupResult();
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java
index c6a4aa5..4917878 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupResult.java
@@ -20,30 +20,23 @@
package org.apache.freemarker.core.templateresolver;
import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.util.NullArgumentException;
+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()}; you can't create instances of this directly.
+ * {@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 {
- /** Used internally to get a not-found result (currently just a static singleton). */
- static TemplateLookupResult createNegativeResult() {
- return NegativeTemplateLookupResult.INSTANCE;
- }
-
- /** Used internally to create the appropriate kind of result from the parameters. */
- static TemplateLookupResult from(String templateSourceName, TemplateLoadingResult templateLoaderResult) {
- return templateLoaderResult.getStatus() != TemplateLoadingResultStatus.NOT_FOUND
- ? new PositiveTemplateLookupResult(templateSourceName, templateLoaderResult)
- : createNegativeResult();
- }
-
- private TemplateLookupResult() {
+ protected TemplateLookupResult() {
// nop
}
@@ -57,77 +50,5 @@ public abstract class TemplateLookupResult {
* Tells if the lookup has found a matching template.
*/
public abstract boolean isPositive();
-
- /**
- * Used internally to extract the {@link TemplateLoadingResult}; {@code null} if {@link #isPositive()} is
- * {@code false}.
- */
- abstract TemplateLoadingResult getTemplateLoaderResult();
-
- private static final class PositiveTemplateLookupResult extends TemplateLookupResult {
-
- private final String templateSourceName;
- private final TemplateLoadingResult templateLoaderResult;
-
- /**
- * @param templateSourceName
- * The name of the matching template found. This is not necessarily the same as the template name
- * with which the template was originally requested. For example, one may gets a template for the
- * {@code "foo.ftl"} name, but due to localized lookup the template is actually loaded from
- * {@code "foo_de.ftl"}. Then this parameter must be {@code "foo_de.ftl"}, not {@code "foo.ftl"}. Not
- * {@code null}.
- *
- * @param templateLoaderResult
- * See {@link TemplateLoader#load} to understand what that means. Not
- * {@code null}.
- */
- private PositiveTemplateLookupResult(String templateSourceName, TemplateLoadingResult templateLoaderResult) {
- NullArgumentException.check("templateName", templateSourceName);
- NullArgumentException.check("templateLoaderResult", templateLoaderResult);
-
- this.templateSourceName = templateSourceName;
- this.templateLoaderResult = templateLoaderResult;
- }
-
- @Override
- public String getTemplateSourceName() {
- return templateSourceName;
- }
-
- @Override
- TemplateLoadingResult getTemplateLoaderResult() {
- return templateLoaderResult;
- }
-
- @Override
- public boolean isPositive() {
- return true;
- }
- }
-
- private static final class NegativeTemplateLookupResult extends TemplateLookupResult {
-
- private static final NegativeTemplateLookupResult INSTANCE = new NegativeTemplateLookupResult();
-
- private NegativeTemplateLookupResult() {
- // nop
- }
-
- @Override
- public String getTemplateSourceName() {
- return null;
- }
-
- @Override
- TemplateLoadingResult getTemplateLoaderResult() {
- return null;
- }
-
- @Override
- public boolean isPositive() {
- return false;
- }
-
- }
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
index e993140..a1c9577 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupStrategy.java
@@ -20,7 +20,6 @@
package org.apache.freemarker.core.templateresolver;
import java.io.IOException;
-import java.util.Locale;
import org.apache.freemarker.core.Configuration;
import org.apache.freemarker.core.Template;
@@ -58,28 +57,6 @@ import org.apache.freemarker.core.Template;
public abstract class 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 static final TemplateLookupStrategy DEFAULT_2_3_0 = new Default020300();
-
- /**
* 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.
@@ -96,20 +73,6 @@ public abstract class TemplateLookupStrategy {
* {@code TemplateLookupContext#createNegativeLookupResult()} if no matching template exists. Can't be
* {@code null}.
*/
- public abstract TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException;
-
- private static class Default020300 extends TemplateLookupStrategy {
-
- @Override
- public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
- return ctx.lookupWithLocalizedThenAcquisitionStrategy(ctx.getTemplateName(), ctx.getTemplateLocale());
- }
-
- @Override
- public String toString() {
- return "TemplateLookupStrategy.DEFAULT_2_3_0";
- }
-
- }
+ public abstract <R extends TemplateLookupResult> R lookup(TemplateLookupContext<R> ctx) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
index 5b68241..ebcbe9a 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateNameFormat.java
@@ -19,11 +19,6 @@
package org.apache.freemarker.core.templateresolver;
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.TemplateNotFoundException;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.util.StringUtil;
-
/**
* Symbolized template name format. The API of this class isn't exposed as it's too immature, so custom
* template name formats aren't possible yet.
@@ -32,389 +27,28 @@ import org.apache.freemarker.core.util.StringUtil;
*/
public abstract class TemplateNameFormat {
- private TemplateNameFormat() {
- // Currently can't be instantiated from outside
+ protected TemplateNameFormat() {
+ //
}
/**
- * The default template name format when {@link Configuration#Configuration(Version) 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 #DEFAULT_2_4_0} instead.
- */
- public static final TemplateNameFormat DEFAULT_2_3_0 = new Default020300();
-
- /**
- * The default template name format only when {@link Configuration#Configuration(Version) 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 #DEFAULT_2_3_0} 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 #DEFAULT_2_3_0} 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 fixed: {@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 static final TemplateNameFormat DEFAULT_2_4_0 = new Default020400();
-
- /**
* Implements {@link TemplateResolver#toRootBasedName(String, String)}; see more there.
*/
- abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException;
+ public abstract String toRootBasedName(String baseName, String targetName) throws MalformedTemplateNameException;
/**
* Implements {@link TemplateResolver#normalizeRootBasedName(String)}; see more there.
*/
- abstract String normalizeRootBasedName(String name) throws MalformedTemplateNameException;
-
- private static final class Default020300 extends TemplateNameFormat {
- @Override
- 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
- 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;
- }
-
- @Override
- public String toString() {
- return "TemplateNameFormat.DEFAULT_2_3_0";
- }
-
- }
-
- private static final class Default020400 extends TemplateNameFormat {
- @Override
- 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
- 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;
- }
-
- @Override
- public String toString() {
- return "TemplateNameFormat.DEFAULT_2_4_0";
- }
- }
+ public abstract String normalizeRootBasedName(String name) throws MalformedTemplateNameException;
- private static void checkNameHasNoNullCharacter(final 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");
}
}
- private static MalformedTemplateNameException newRootLeavingException(final String name) {
+ 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/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
index 6253354..800b1b8 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
@@ -25,6 +25,7 @@ import org.apache.freemarker.core.Configuration;
import org.apache.freemarker.core.Template;
import org.apache.freemarker.core.TemplateNotFoundException;
import org.apache.freemarker.core.ast.ParseException;
+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,
@@ -96,10 +97,10 @@ public abstract class TemplateResolver {
* <p>
* This method is thread-safe and can be called while the engine processes templates.
*
- * @throw {@link UnsupportedOperationException} If the {@link TemplateResolver} implementation doesn't support this
+ * @throws UnsupportedOperationException If the {@link TemplateResolver} implementation doesn't support this
* operation.
*/
- public abstract void clearTemplateCache();
+ 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;
@@ -112,11 +113,11 @@ public abstract class TemplateResolver {
* <p>
* This method is thread-safe and can be called while the engine processes templates.
*
- * @throw {@link UnsupportedOperationException} If the {@link TemplateResolver} implementation doesn't support this
+ * @throws UnsupportedOperationException If the {@link TemplateResolver} implementation doesn't support this
* operation.
*/
public abstract void removeTemplateFromCache(String name, Locale locale, String encoding, boolean parse)
- throws IOException;
+ throws IOException, UnsupportedOperationException;;
/**
* Converts a name to a template root directory based name, so that it can be used to find a template without
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java
deleted file mode 100644
index 70cb733..0000000
--- a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoader.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.templateresolver;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.net.JarURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Objects;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core._CoreLogs;
-import org.slf4j.Logger;
-
-/**
- * This is an abstract template loader that can load templates whose location can be described by an URL. Subclasses
- * only need to override the {@link #getURL(String)}, {@link #extractNegativeResult(URLConnection)}, and perhaps the
- * {@link #prepareConnection(URLConnection)} method.
- */
-// TODO JUnit test (including implementing a HTTP-based template loader to test the new protected methods)
-public abstract class URLTemplateLoader implements TemplateLoader {
-
- private static final Logger LOG = _CoreLogs.TEMPLATE_RESOLVER;
-
- private Boolean urlConnectionUsesCaches = false;
-
- /**
- * Getter pair of {@link #setURLConnectionUsesCaches(Boolean)}.
- *
- * @since 2.3.21
- */
- public Boolean getURLConnectionUsesCaches() {
- return urlConnectionUsesCaches;
- }
-
- /**
- * Sets if {@link URLConnection#setUseCaches(boolean)} will be called, and with what value. By default this is
- * {@code false}, because FreeMarker has its own template cache with its own update delay setting
- * ({@link Configuration#setTemplateUpdateDelay(int)}). If this is set to {@code null},
- * {@link URLConnection#setUseCaches(boolean)} won't be called.
- */
- public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
- this.urlConnectionUsesCaches = urlConnectionUsesCaches;
- }
-
- @Override
- public TemplateLoaderSession createSession() {
- return null;
- }
-
- @Override
- public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom,
- Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException {
- URL url = getURL(name);
- if (url == null) {
- return TemplateLoadingResult.NOT_FOUND;
- }
-
- URLConnection conn = url.openConnection();
- Boolean urlConnectionUsesCaches = getURLConnectionUsesCaches();
- if (urlConnectionUsesCaches != null) {
- conn.setUseCaches(urlConnectionUsesCaches);
- }
-
- prepareConnection(conn);
- conn.connect();
-
- InputStream inputStream = null;
- Long version;
- URLTemplateLoadingSource source;
- try {
- TemplateLoadingResult negativeResult = extractNegativeResult(conn);
- if (negativeResult != null) {
- return negativeResult;
- }
-
- // To prevent clustering issues, getLastModified(fallbackToJarLMD=false)
- long lmd = getLastModified(conn, false);
- version = lmd != -1 ? lmd : null;
-
- source = new URLTemplateLoadingSource(url);
-
- if (ifSourceDiffersFrom != null && ifSourceDiffersFrom.equals(source)
- && Objects.equals(ifVersionDiffersFrom, version)) {
- return TemplateLoadingResult.NOT_MODIFIED;
- }
-
- inputStream = conn.getInputStream();
- } catch (Throwable e) {
- try {
- if (inputStream == null) {
- // There's no URLConnection.close(), so we do this hack. In case of HttpURLConnection we could call
- // disconnect(), but that's perhaps too aggressive.
- conn.getInputStream().close();
- }
- } catch (IOException e2) {
- LOG.debug("Failed to close connection inputStream", e2);
- }
- throw e;
- }
- return new TemplateLoadingResult(source, version, inputStream, null);
- }
-
- @Override
- public void resetState() {
- // Do nothing
- }
-
- /**
- * {@link URLConnection#getLastModified()} with JDK bug workarounds. Because of JDK-6956385, for files inside a jar,
- * it returns the last modification time of the jar itself, rather than the last modification time of the file
- * inside the jar.
- *
- * @param fallbackToJarLMD
- * Tells if the file is in side jar, then we should return the last modification time of the jar itself,
- * or -1 (to work around JDK-6956385).
- */
- public static long getLastModified(URLConnection conn, boolean fallbackToJarLMD) throws IOException {
- if (conn instanceof JarURLConnection) {
- // There is a bug in sun's jar url connection that causes file handle leaks when calling getLastModified()
- // (see https://bugs.openjdk.java.net/browse/JDK-6956385).
- // Since the time stamps of jar file contents can't vary independent from the jar file timestamp, just use
- // the jar file timestamp
- if (fallbackToJarLMD) {
- URL jarURL = ((JarURLConnection) conn).getJarFileURL();
- if (jarURL.getProtocol().equals("file")) {
- // Return the last modified time of the underlying file - saves some opening and closing
- return new File(jarURL.getFile()).lastModified();
- } else {
- // Use the URL mechanism
- URLConnection jarConn = null;
- try {
- jarConn = jarURL.openConnection();
- return jarConn.getLastModified();
- } finally {
- try {
- if (jarConn != null) {
- jarConn.getInputStream().close();
- }
- } catch (IOException e) {
- LOG.warn("Failed to close URL connection for: {}", conn, e);
- }
- }
- }
- } else {
- return -1;
- }
- } else {
- return conn.getLastModified();
- }
- }
-
- /**
- * Given a template name (plus potential locale decorations) retrieves an URL that points the template source.
- *
- * @param name
- * the name of the sought template (including the locale decorations, or other decorations the
- * {@link TemplateLookupStrategy} uses).
- *
- * @return An URL that points to the template source, or null if it can be determined that the template source does
- * not exist. For many implementations the existence of the template can't be decided at this point, and you
- * rely on {@link #extractNegativeResult(URLConnection)} instead.
- */
- protected abstract URL getURL(String name);
-
- /**
- * Called before the resource if read, checks if we can immediately return a {@link TemplateLoadingResult#NOT_FOUND}
- * or {@link TemplateLoadingResult#NOT_MODIFIED}, or throw an {@link IOException}. For example, for a HTTP-based
- * storage, the HTTP response status 404 could result in {@link TemplateLoadingResult#NOT_FOUND}, 304 in
- * {@link TemplateLoadingResult#NOT_MODIFIED}, and some others, like 500 in throwing an {@link IOException}.
- *
- * <p>Some
- * implementations rely on {@link #getURL(String)} returning {@code null} when a template is missing, in which case
- * this method is certainly not applicable.
- */
- protected abstract TemplateLoadingResult extractNegativeResult(URLConnection conn) throws IOException;
-
- /**
- * Called before anything that causes the connection to actually build up. This is where
- * {@link URLConnection#setIfModifiedSince(long)} and such can be called if someone overrides this.
- * The default implementation in {@link URLTemplateLoader} does nothing.
- */
- protected void prepareConnection(URLConnection conn) {
- // Does nothing
- }
-
- /**
- * Can be used by subclasses to canonicalize URL path prefixes.
- * @param prefix the path prefix to canonicalize
- * @return the canonicalized prefix. All backslashes are replaced with
- * forward slashes, and a trailing slash is appended if the original
- * prefix wasn't empty and didn't already end with a slash.
- */
- protected static String canonicalizePrefix(String prefix) {
- // make it foolproof
- prefix = prefix.replace('\\', '/');
- // ensure there's a trailing slash
- if (prefix.length() > 0 && !prefix.endsWith("/")) {
- prefix += "/";
- }
- return prefix;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java b/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java
deleted file mode 100644
index 99fbc25..0000000
--- a/src/main/java/org/apache/freemarker/core/templateresolver/URLTemplateLoadingSource.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.freemarker.core.templateresolver;
-
-import java.net.URL;
-
-import org.apache.freemarker.core.util.NullArgumentException;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-@SuppressWarnings("serial")
-public class URLTemplateLoadingSource implements TemplateLoadingSource {
-
- private final URL url;
-
- public URLTemplateLoadingSource(URL url) {
- NullArgumentException.check("url", url);
- this.url = url;
- }
-
- public URL getUrl() {
- return url;
- }
-
- @Override
- public int hashCode() {
- return url.hashCode();
- }
-
- @Override
- @SuppressFBWarnings("EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS")
- public boolean equals(Object obj) {
- return url.equals(obj);
- }
-
- @Override
- public String toString() {
- return url.toString();
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java b/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java
deleted file mode 100644
index 9c7e8a8..0000000
--- a/src/main/java/org/apache/freemarker/core/templateresolver/_TemplateLoaderUtils.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.templateresolver;
-
-import org.apache.freemarker.core.Configuration;
-
-/**
- * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
- * access things inside this package that users shouldn't.
- */
-public final class _TemplateLoaderUtils {
-
- private _TemplateLoaderUtils() {
- // Not meant to be instantiated
- }
-
- public static String getClassNameForToString(TemplateLoader templateLoader) {
- final Class<? extends TemplateLoader> tlClass = templateLoader.getClass();
- final Package tlPackage = tlClass.getPackage();
- return tlPackage == Configuration.class.getPackage() || tlPackage == TemplateLoader.class.getPackage()
- ? tlClass.getSimpleName() : tlClass.getName();
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/ByteArrayTemplateLoader.java
new file mode 100644
index 0000000..f77de7c
--- /dev/null
+++ b/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/b0e08ccb/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/ClassTemplateLoader.java
new file mode 100644
index 0000000..9cd8ead
--- /dev/null
+++ b/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 ? this.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