Freemarker源码分析(2)cache.TemplateLoader及其子类

2021SC@SDUSC

cache.TemplateLoader及其子类

总览图
cache.TemplateLoader及其子类总览图

1.接口TemplateLoader

代码


    public interface TemplateLoader {
    
    
	

    public Object findTemplateSource(String name)
    throws IOException;
        
    public long getLastModified(Object templateSource);
    

    public Reader getReader(Object templateSource, String encoding) throws IOException;
    

    public void closeTemplateSource(Object templateSource) throws IOException;
    
}


作用:模板加载器的接口

2.URLTemplateloader

代码

public abstract class URLTemplateLoader implements TemplateLoader {
    
    
    
    private Boolean urlConnectionUsesCaches;
    
    @Override
    public Object findTemplateSource(String name)
    throws IOException {
    
    
        URL url = getURL(name);
        return url == null ? null : new URLTemplateSource(url, getURLConnectionUsesCaches());
    }
    
    @Override
    public long getLastModified(Object templateSource) {
    
    
        return ((URLTemplateSource) templateSource).lastModified();
    }
    
    @Override
    public Reader getReader(Object templateSource, String encoding)
    throws IOException {
    
    
        return new InputStreamReader(
                ((URLTemplateSource) templateSource).getInputStream(),
                encoding);
    }
    
    @Override
    public void closeTemplateSource(Object templateSource)
    throws IOException {
    
    
        ((URLTemplateSource) templateSource).close();
    }

    public Boolean getURLConnectionUsesCaches() {
    
    
        return urlConnectionUsesCaches;
    }

    public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
    
    
        this.urlConnectionUsesCaches = urlConnectionUsesCaches;
    }


    protected abstract URL getURL(String name);

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

作用:抽象模板加载器,它可以加载位置可以用URL描述的模板。这个超类只适用于仅获取URL就能立即判断资源是否存在的情况,而不适用于需要检查响应头才能知道的情况。子类只需要重写getURL(String)方法。

3.ClassTemplateLoader

代码

public class ClassTemplateLoader extends URLTemplateLoader {
    
    
    
    private final Class<?> resourceLoaderClass;
    private final ClassLoader classLoader;
    private final String basePackagePath;

    @Deprecated
    public ClassTemplateLoader() {
    
    
        this(null, true, null, "/");
    }

    @Deprecated
    public ClassTemplateLoader(Class<?> resourceLoaderClass) {
    
    
        this(resourceLoaderClass, "");
    }

    public ClassTemplateLoader(Class<?> resourceLoaderClass, String basePackagePath) {
    
    
        this(resourceLoaderClass, false, null, basePackagePath);
    }

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

    @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);
    }

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


    @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 */")
                        : ""
                )
                + ")";
    }

    public Class getResourceLoaderClass() {
    
    
        return resourceLoaderClass;
    }

    public ClassLoader getClassLoader() {
    
    
        return classLoader;
    }

    public String getBasePackagePath() {
    
    
        return basePackagePath;
    }

}

作用:一个可以从类路径加载模板的TemplateLoader的子类。它既可以在jar包中加载,也可以通过调用此类进行加载。

4.WebappTemplateLoader

代码

public class WebappTemplateLoader implements TemplateLoader {
    
    

    private static final Logger LOG = Logger.getLogger("freemarker.cache");

    private final ServletContext servletContext;
    private final String subdirPath;

    private Boolean urlConnectionUsesCaches;

    private boolean attemptFileAccess = true;

    public WebappTemplateLoader(ServletContext servletContext) {
    
    
        this(servletContext, "/");
    }

    public WebappTemplateLoader(ServletContext servletContext, String subdirPath) {
    
    
        NullArgumentException.check("servletContext", servletContext);
        NullArgumentException.check("subdirPath", subdirPath);

        subdirPath = subdirPath.replace('\\', '/');
        if (!subdirPath.endsWith("/")) {
    
    
            subdirPath += "/";
        }
        if (!subdirPath.startsWith("/")) {
    
    
            subdirPath = "/" + subdirPath;
        }
        this.subdirPath = subdirPath;
        this.servletContext = servletContext;
    }

    @Override
    public Object findTemplateSource(String name) throws IOException {
    
    
        String fullPath = subdirPath + name;

        if (attemptFileAccess) {
    
    
            // First try to open as plain file (to bypass servlet container resource caches).
            try {
    
    
                String realPath = servletContext.getRealPath(fullPath);
                if (realPath != null) {
    
    
                    File file = new File(realPath);
                    if (file.canRead() && file.isFile()) {
    
    
                        return file;
                    }
                }
            } catch (SecurityException e) {
    
    
                ;// ignore
            }
        }

        // If it fails, try to open it with servletContext.getResource.
        URL url = null;
        try {
    
    
            url = servletContext.getResource(fullPath);
        } catch (MalformedURLException e) {
    
    
            LOG.warn("Could not retrieve resource " + StringUtil.jQuoteNoXSS(fullPath),
                    e);
            return null;
        }
        return url == null ? null : new URLTemplateSource(url, getURLConnectionUsesCaches());
    }

    @Override
    public long getLastModified(Object templateSource) {
    
    
        if (templateSource instanceof File) {
    
    
            return ((File) templateSource).lastModified();
        } else {
    
    
            return ((URLTemplateSource) templateSource).lastModified();
        }
    }

    @Override
    public Reader getReader(Object templateSource, String encoding)
            throws IOException {
    
    
        if (templateSource instanceof File) {
    
    
            return new InputStreamReader(
                    new FileInputStream((File) templateSource),
                    encoding);
        } else {
    
    
            return new InputStreamReader(
                    ((URLTemplateSource) templateSource).getInputStream(),
                    encoding);
        }
    }

    @Override
    public void closeTemplateSource(Object templateSource) throws IOException {
    
    
        if (templateSource instanceof File) {
    
    
            // Do nothing.
        } else {
    
    
            ((URLTemplateSource) templateSource).close();
        }
    }

    public Boolean getURLConnectionUsesCaches() {
    
    
        return urlConnectionUsesCaches;
    }

    public void setURLConnectionUsesCaches(Boolean urlConnectionUsesCaches) {
    
    
        this.urlConnectionUsesCaches = urlConnectionUsesCaches;
    }

    @Override
    public String toString() {
    
    
        return TemplateLoaderUtils.getClassNameForToString(this)
                + "(subdirPath=" + StringUtil.jQuote(subdirPath)
                + ", servletContext={contextPath=" + StringUtil.jQuote(getContextPath())
                + ", displayName=" + StringUtil.jQuote(servletContext.getServletContextName()) + "})";
    }

    private String getContextPath() {
    
    
        try {
    
    
            Method m = servletContext.getClass().getMethod("getContextPath", CollectionUtils.EMPTY_CLASS_ARRAY);
            return (String) m.invoke(servletContext, CollectionUtils.EMPTY_OBJECT_ARRAY);
        } catch (Throwable e) {
    
    
            return "[can't query before Serlvet 2.5]";
        }
    }

    public boolean getAttemptFileAccess() {
    
    
        return attemptFileAccess;
    }

    public void setAttemptFileAccess(boolean attemptLoadingFromFile) {
    
    
        this.attemptFileAccess = attemptLoadingFromFile;
    }

}

作用:TemplateLoader的子类,以通过ServletContext.getResource(String)可以到达的stream作为模板来源

5.StringTemplateLoader

代码

public class StringTemplateLoader implements TemplateLoader {
    
    
    
    private final Map<String, StringTemplateSource> templates = new HashMap<>();

    public void putTemplate(String name, String templateContent) {
    
    
        putTemplate(name, templateContent, System.currentTimeMillis());
    }

    public void putTemplate(String name, String templateContent, long lastModified) {
    
    
        templates.put(name, new StringTemplateSource(name, templateContent, lastModified));
    }

    public boolean removeTemplate(String name) {
    
    
        return templates.remove(name) != null;
    }
    
    @Override
    public void closeTemplateSource(Object templateSource) {
    
    
    }
    
    @Override
    public Object findTemplateSource(String name) {
    
    
        return templates.get(name);
    }
    
    @Override
    public long getLastModified(Object templateSource) {
    
    
        return ((StringTemplateSource) templateSource).lastModified;
    }
    
    @Override
    public Reader getReader(Object templateSource, String encoding) {
    
    
        return new StringReader(((StringTemplateSource) templateSource).templateContent);
    }
    
    private static class StringTemplateSource {
    
    
        private final String name;
        private final String templateContent;
        private final long lastModified;
        
        StringTemplateSource(String name, String templateContent, long lastModified) {
    
    
            if (name == null) {
    
    
                throw new IllegalArgumentException("name == null");
            }
            if (templateContent == null) {
    
    
                throw new IllegalArgumentException("source == null");
            }
            if (lastModified < -1L) {
    
    
                throw new IllegalArgumentException("lastModified < -1L");
            }
            this.name = name;
            this.templateContent = templateContent;
            this.lastModified = lastModified;
        }
        
        @Override
        public int hashCode() {
    
    
            final int prime = 31;
            int result = 1;
            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;
            StringTemplateSource other = (StringTemplateSource) obj;
            if (name == null) {
    
    
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }


        @Override
        public String toString() {
    
    
            return name;
        }
        
    }

    @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();
    }
    
}

作用:使用字符串创建模板加载器

6.ByteArrayTemplateLoader

代码

public class ByteArrayTemplateLoader implements TemplateLoader {
    
    

    private final Map<String, ByteArrayTemplateSource> templates = new HashMap<>();
ds a template to this template loader; see {
    
    @link StringTemplateLoader#putTemplate(String, String)} for more.
     */
    public void putTemplate(String name, byte[] templateContent) {
    
    
        putTemplate(name, templateContent, System.currentTimeMillis());
    }

    public void putTemplate(String name, byte[] templateContent, long lastModified) {
    
    
        templates.put(name, new ByteArrayTemplateSource(name, templateContent, lastModified));
    }
    

    public boolean removeTemplate(String name) {
    
    
        return templates.remove(name) != null;
    }
    
    @Override
    public void closeTemplateSource(Object templateSource) {
    
    
    }
    
    @Override
    public Object findTemplateSource(String name) {
    
    
        return templates.get(name);
    }
    
    @Override
    public long getLastModified(Object templateSource) {
    
    
        return ((ByteArrayTemplateSource) templateSource).lastModified;
    }
    
    @Override
    public Reader getReader(Object templateSource, String encoding) throws UnsupportedEncodingException {
    
    
        return new InputStreamReader(
                new ByteArrayInputStream(((ByteArrayTemplateSource) templateSource).templateContent),
                encoding);
    }
    
    private static class ByteArrayTemplateSource {
    
    
        private final String name;
        private final byte[] templateContent;
        private final long lastModified;
        
        ByteArrayTemplateSource(String name, byte[] templateContent, long lastModified) {
    
    
            if (name == null) {
    
    
                throw new IllegalArgumentException("name == null");
            }
            if (templateContent == null) {
    
    
                throw new IllegalArgumentException("templateContent == null");
            }
            if (lastModified < -1L) {
    
    
                throw new IllegalArgumentException("lastModified < -1L");
            }
            this.name = name;
            this.templateContent = templateContent;
            this.lastModified = lastModified;
        }

        @Override
        public int hashCode() {
    
    
            final int prime = 31;
            int result = 1;
            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;
            ByteArrayTemplateSource other = (ByteArrayTemplateSource) obj;
            if (name == null) {
    
    
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }
        
    }
    
    @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();
    }
    
}

作用:与StringTemplateLoader类似,使用byte[]创建模板加载器

7.FileTemplateLoader

public class FileTemplateLoader implements TemplateLoader {
    
    

    public static String SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM
            = "org.freemarker.emulateCaseSensitiveFileSystem";
    private static final boolean EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT;
    static {
    
    
        final String s = SecurityUtilities.getSystemProperty(SYSTEM_PROPERTY_NAME_EMULATE_CASE_SENSITIVE_FILE_SYSTEM,
                "false");
        boolean emuCaseSensFS;
        try {
    
    
            emuCaseSensFS = StringUtil.getYesNo(s);
        } catch (Exception e) {
    
    
            emuCaseSensFS = false;
        }
        EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT = emuCaseSensFS;
    }

    private static final int CASE_CHECH_CACHE_HARD_SIZE = 50;
    private static final int CASE_CHECK_CACHE__SOFT_SIZE = 1000;
    private static final boolean SEP_IS_SLASH = File.separatorChar == '/';
    
    private static final Logger LOG = Logger.getLogger("freemarker.cache");
    
    public final File baseDir;
    private final String canonicalBasePath;
    private boolean emulateCaseSensitiveFileSystem;
    private MruCacheStorage correctCasePaths;

    @Deprecated
    public FileTemplateLoader() throws IOException {
    
    
        this(new File(SecurityUtilities.getSystemProperty("user.dir")));
    }

    public FileTemplateLoader(final File baseDir) throws IOException {
    
    
        this(baseDir, false);
    }

    public FileTemplateLoader(final File baseDir, final boolean disableCanonicalPathCheck) throws IOException {
    
    
        try {
    
    
            Object[] retval = AccessController.doPrivileged(new PrivilegedExceptionAction<Object[]>() {
    
    
                @Override
                public Object[] run() throws IOException {
    
    
                    if (!baseDir.exists()) {
    
    
                        throw new FileNotFoundException(baseDir + " does not exist.");
                    }
                    if (!baseDir.isDirectory()) {
    
    
                        throw new IOException(baseDir + " is not a directory.");
                    }
                    Object[] retval = new Object[2];
                    if (disableCanonicalPathCheck) {
    
    
                        retval[0] = baseDir;
                        retval[1] = null;
                    } else {
    
    
                        retval[0] = baseDir.getCanonicalFile();
                        String basePath = ((File) retval[0]).getPath();
                        // Most canonical paths don't end with File.separator,
                        // but some does. Like, "C:\" VS "C:\templates".
                        if (!basePath.endsWith(File.separator)) {
    
    
                            basePath += File.separatorChar;
                        }
                        retval[1] = basePath;
                    }
                    return retval;
                }
            });
            this.baseDir = (File) retval[0];
            this.canonicalBasePath = (String) retval[1];
            
            setEmulateCaseSensitiveFileSystem(getEmulateCaseSensitiveFileSystemDefault());
        } catch (PrivilegedActionException e) {
    
    
            throw (IOException) e.getException();
        }
    }
    
    @Override
    public Object findTemplateSource(final String name) throws IOException {
    
    
        try {
    
    
            return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
    
    
                @Override
                public File run() throws IOException {
    
    
                    File source = new File(baseDir, SEP_IS_SLASH ? name : 
                        name.replace('/', File.separatorChar));
                    if (!source.isFile()) {
    
    
                        return null;
                    }
                    // Security check for inadvertently returning something 
                    // outside the template directory when linking is not 
                    // allowed.
                    if (canonicalBasePath != null) {
    
    
                        String normalized = source.getCanonicalPath();
                        if (!normalized.startsWith(canonicalBasePath)) {
    
    
                            throw new SecurityException(source.getAbsolutePath() 
                                    + " resolves to " + normalized + " which "
                                    + " doesn't start with " + canonicalBasePath);
                        }
                    }
                    
                    if (emulateCaseSensitiveFileSystem && !isNameCaseCorrect(source)) {
    
    
                        return null;
                    }
                    
                    return source;
                }
            });
        } catch (PrivilegedActionException e) {
    
    
            throw (IOException) e.getException();
        }
    }
    
    @Override
    public long getLastModified(final Object templateSource) {
    
    
        return (AccessController.doPrivileged(new PrivilegedAction<Long>() {
    
    
            @Override
            public Long run() {
    
    
                return Long.valueOf(((File) templateSource).lastModified());
            }
        })).longValue();
    }
    
    @Override
    public Reader getReader(final Object templateSource, final String encoding) throws IOException {
    
    
        try {
    
    
            return AccessController.doPrivileged(new PrivilegedExceptionAction<Reader>() {
    
    
                @Override
                public Reader run() throws IOException {
    
    
                    if (!(templateSource instanceof File)) {
    
    
                        throw new IllegalArgumentException(
                                "templateSource wasn't a File, but a: " + 
                                templateSource.getClass().getName());
                    }
                    return new InputStreamReader(new FileInputStream((File) templateSource), encoding);
                }
            });
        } catch (PrivilegedActionException e) {
    
    
            throw (IOException) e.getException();
        }
    }

    private boolean isNameCaseCorrect(File source) throws IOException {
    
    
        final String sourcePath = source.getPath();
        synchronized (correctCasePaths) {
    
    
            if (correctCasePaths.get(sourcePath) != null) {
    
    
                return true;
            }
        }
        
        final File parentDir = source.getParentFile();
        if (parentDir != null) {
    
    
            if (!baseDir.equals(parentDir) && !isNameCaseCorrect(parentDir)) {
    
    
                return false;
            }
            
            final String[] listing = parentDir.list();
            if (listing != null) {
    
    
                final String fileName = source.getName();
                
                boolean identicalNameFound = false;
                for (int i = 0; !identicalNameFound && i < listing.length; i++) {
    
    
                    if (fileName.equals(listing[i])) {
    
    
                        identicalNameFound = true;
                    }
                }
        
                if (!identicalNameFound) {
    
    
                    // If we find a similarly named file that only differs in case, then this is a file-not-found.
                    for (int i = 0; i < listing.length; i++) {
    
    
                        final String listingEntry = listing[i];
                        if (fileName.equalsIgnoreCase(listingEntry)) {
    
    
                            if (LOG.isDebugEnabled()) {
    
    
                                LOG.debug("Emulating file-not-found because of letter case differences to the "
                                        + "real file, for: " + sourcePath);
                            }
                            return false;
                        }
                    }
                }
            }
        }

        synchronized (correctCasePaths) {
    
    
            correctCasePaths.put(sourcePath, Boolean.TRUE);        
        }
        return true;
    }

    @Override
    public void closeTemplateSource(Object templateSource) {
    
    
        // Do nothing.
    }

    public File getBaseDirectory() {
    
    
        return baseDir;
    }

    public void setEmulateCaseSensitiveFileSystem(boolean nameCaseChecked) {
    
    
        // Ensure that the cache exists exactly when needed:
        if (nameCaseChecked) {
    
    
            if (correctCasePaths == null) {
    
    
                correctCasePaths = new MruCacheStorage(CASE_CHECH_CACHE_HARD_SIZE, CASE_CHECK_CACHE__SOFT_SIZE);
            }
        } else {
    
    
            correctCasePaths = null;
        }
        
        this.emulateCaseSensitiveFileSystem = nameCaseChecked;
    }

    public boolean getEmulateCaseSensitiveFileSystem() {
    
    
        return emulateCaseSensitiveFileSystem;
    }

    protected boolean getEmulateCaseSensitiveFileSystemDefault() {
    
    
        return EMULATE_CASE_SENSITIVE_FILE_SYSTEM_DEFAULT;
    }

    @Override
    public String toString() {
    
    
        // We don't StringUtil.jQuote paths here, because on Windows there will be \\-s then that some may find
        // confusing.
        return TemplateLoaderUtils.getClassNameForToString(this) + "("
                + "baseDir=\"" + baseDir + "\""
                + (canonicalBasePath != null ? ", canonicalBasePath=\"" + canonicalBasePath + "\"" : "")
                + (emulateCaseSensitiveFileSystem ? ", emulateCaseSensitiveFileSystem=true" : "")
                + ")";
    }
    
}

作用:使用特定文件夹下的文件生成模板加载器。生成之前会进行安全性检测

8.MultiTemplateLoader

代码

public class MultiTemplateLoader implements StatefulTemplateLoader {
    
    

    private final TemplateLoader[] templateLoaders;
    private final Map<String, TemplateLoader> lastTemplateLoaderForName
            = new ConcurrentHashMap<>();
    
    private boolean sticky = true;

    public MultiTemplateLoader(TemplateLoader[] templateLoaders) {
    
    
        NullArgumentException.check("templateLoaders", templateLoaders);
        this.templateLoaders = templateLoaders.clone();
    }

    @Override
    public Object findTemplateSource(String name)
            throws IOException {
    
    
        TemplateLoader lastTemplateLoader = null;
        if (sticky) {
    
    
            // Use soft affinity - give the loader that last found this
            // resource a chance to find it again first.
            lastTemplateLoader = lastTemplateLoaderForName.get(name);
            if (lastTemplateLoader != null) {
    
    
                Object source = lastTemplateLoader.findTemplateSource(name);
                if (source != null) {
    
    
                    return new MultiSource(source, lastTemplateLoader);
                }
            }
        }

        // If there is no affine loader, or it could not find the resource
        // again, try all loaders in order of appearance. If any manages
        // to find the resource, then associate it as the new affine loader
        // for this resource.
        for (TemplateLoader templateLoader : templateLoaders) {
    
    
            if (lastTemplateLoader != templateLoader) {
    
    
                Object source = templateLoader.findTemplateSource(name);
                if (source != null) {
    
    
                    if (sticky) {
    
    
                        lastTemplateLoaderForName.put(name, templateLoader);
                    }
                    return new MultiSource(source, templateLoader);
                }
            }
        }

        if (sticky) {
    
    
            lastTemplateLoaderForName.remove(name);
        }
        // Resource not found
        return null;
    }

    @Override
    public long getLastModified(Object templateSource) {
    
    
        return ((MultiSource) templateSource).getLastModified();
    }

    @Override
    public Reader getReader(Object templateSource, String encoding)
            throws IOException {
    
    
        return ((MultiSource) templateSource).getReader(encoding);
    }

    @Override
    public void closeTemplateSource(Object templateSource)
            throws IOException {
    
    
        ((MultiSource) templateSource).close();
    }

    @Override
    public void resetState() {
    
    
        lastTemplateLoaderForName.clear();
        for (TemplateLoader loader : templateLoaders) {
    
    
            if (loader instanceof StatefulTemplateLoader) {
    
    
                ((StatefulTemplateLoader) loader).resetState();
            }
        }
    }

    static final class MultiSource {
    
    

        private final Object source;
        private final TemplateLoader loader;

        MultiSource(Object source, TemplateLoader loader) {
    
    
            this.source = source;
            this.loader = loader;
        }

        long getLastModified() {
    
    
            return loader.getLastModified(source);
        }

        Reader getReader(String encoding)
                throws IOException {
    
    
            return loader.getReader(source, encoding);
        }

        void close()
                throws IOException {
    
    
            loader.closeTemplateSource(source);
        }

        Object getWrappedSource() {
    
    
            return source;
        }

        @Override
        public boolean equals(Object o) {
    
    
            if (o instanceof MultiSource) {
    
    
                MultiSource m = (MultiSource) o;
                return m.loader.equals(loader) && m.source.equals(source);
            }
            return false;
        }

        @Override
        public int hashCode() {
    
    
            return loader.hashCode() + 31 * source.hashCode();
        }

        @Override
        public String toString() {
    
    
            return source.toString();
        }
    }

    @Override
    public String toString() {
    
    
        StringBuilder sb = new StringBuilder();
        sb.append("MultiTemplateLoader(");
        for (int i = 0; i < templateLoaders.length; i++) {
    
    
            if (i != 0) {
    
    
                sb.append(", ");
            }
            sb.append("loader").append(i + 1).append(" = ").append(templateLoaders[i]);
        }
        sb.append(")");
        return sb.toString();
    }

    public int getTemplateLoaderCount() {
    
    
        return templateLoaders.length;
    }

    public TemplateLoader getTemplateLoader(int index) {
    
    
        return templateLoaders[index];
    }
lean isSticky() {
    
    
        return sticky;
    }

    public void setSticky(boolean sticky) {
    
    
        this.sticky = sticky;
    }


}

作用:使用其他的模板生成器生成模板

注:Freemarker代码来自FreeMarker 中文官方参考手册

新手写的代码分析,文章若有错误还请指出

猜你喜欢

转载自blog.csdn.net/qq_43518847/article/details/120815801