Freemarker源码分析(5)cache包中其他的类

2021SC@SDUSC

Freemarker源码分析(5)cache包中其他的类

Freemarker源码分析(5)cache包中其他的类

总览
Freemarker源码分析(5)cache包中其他的类

_CacheAPI类

代码

public final class _CacheAPI {

    private _CacheAPI() {
        // 不必实例化
    }
    
    public static String toRootBasedName(TemplateNameFormat templateNameFormat, String baseName, String targetName)
            throws MalformedTemplateNameException {
        return templateNameFormat.toRootBasedName(baseName, targetName);
    }

    public static String normalizeRootBasedName(TemplateNameFormat templateNameFormat, String name)
            throws MalformedTemplateNameException {
        return templateNameFormat.normalizeRootBasedName(name);
    }

    public static String rootBasedNameToAbsoluteName(TemplateNameFormat templateNameFormat, String rootBasedName)
            throws MalformedTemplateNameException {
        return templateNameFormat.rootBasedNameToAbsoluteName(rootBasedName);
    }
    
}

作用:这个类是为了解决Java中缺少模块系统的问题,也就是说,其他FreeMarker包可以访问这个包中用户不应该访问的内容。(此类仅供内部使用)

TemplateCache类

代码

public class TemplateCache {
    
    public static final long DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS = 5000L;
    
    private static final String ASTERISKSTR = "*";
    private static final char ASTERISK = '*';
    private static final char SLASH = '/';
    private static final String LOCALE_PART_SEPARATOR = "_";
    private static final Logger LOG = Logger.getLogger("freemarker.cache");

    private final TemplateLoader templateLoader;

    private final CacheStorage storage;
    private final TemplateLookupStrategy templateLookupStrategy;
    private final TemplateNameFormat templateNameFormat;
    private final TemplateConfigurationFactory templateConfigurations;
    
    private final boolean isStorageConcurrent;
    private long updateDelay = DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS;
    private boolean localizedLookup = true;

    private Configuration config;

    @Deprecated
    public TemplateCache() {
        this(_TemplateAPI.createDefaultTemplateLoader(Configuration.VERSION_2_3_0));
    }

    @Deprecated
    public TemplateCache(TemplateLoader templateLoader) {
        this(templateLoader, (Configuration) null);
    }

    @Deprecated
    public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage) {
        this(templateLoader, cacheStorage, null);
    }

    public TemplateCache(TemplateLoader templateLoader, Configuration config) {
        this(templateLoader, _TemplateAPI.createDefaultCacheStorage(Configuration.VERSION_2_3_0), config);
    }

    public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage, Configuration config) {
        this(templateLoader, cacheStorage,
                _TemplateAPI.getDefaultTemplateLookupStrategy(Configuration.VERSION_2_3_0),
                _TemplateAPI.getDefaultTemplateNameFormat(Configuration.VERSION_2_3_0),
                config);
    }

    public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage,
            TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,
            Configuration config) {
        this(templateLoader, cacheStorage, templateLookupStrategy, templateNameFormat, null, config);
    }

    public TemplateCache(TemplateLoader templateLoader, CacheStorage cacheStorage,
            TemplateLookupStrategy templateLookupStrategy, TemplateNameFormat templateNameFormat,
            TemplateConfigurationFactory templateConfigurations,
            Configuration config) {
        this.templateLoader = templateLoader;
        
        NullArgumentException.check("cacheStorage", cacheStorage);
        this.storage = cacheStorage;
        isStorageConcurrent = cacheStorage instanceof ConcurrentCacheStorage &&
                ((ConcurrentCacheStorage) cacheStorage).isConcurrent();
        
        NullArgumentException.check("templateLookupStrategy", templateLookupStrategy);
        this.templateLookupStrategy = templateLookupStrategy;

        NullArgumentException.check("templateNameFormat", templateNameFormat);
        this.templateNameFormat = templateNameFormat;

        // Can be null
        this.templateConfigurations = templateConfigurations;
        
        this.config = config;
    }

    @Deprecated
    public void setConfiguration(Configuration config) {
        this.config = config;
        clear();
    }

    public TemplateLoader getTemplateLoader() {
        return templateLoader;
    }
    
    public CacheStorage getCacheStorage() {
        return storage;
    }
    
    /**
     * @since 2.3.22
     */
    public TemplateLookupStrategy getTemplateLookupStrategy() {
        return templateLookupStrategy;
    }
    
    public TemplateNameFormat getTemplateNameFormat() {
        return templateNameFormat;
    }

    public TemplateConfigurationFactory getTemplateConfigurations() {
        return templateConfigurations;
    }

    public MaybeMissingTemplate getTemplate(String name, Locale locale, Object customLookupCondition,
            String encoding, boolean parseAsFTL)
    throws IOException {
        NullArgumentException.check("name", name);
        NullArgumentException.check("locale", locale);
        NullArgumentException.check("encoding", encoding);
        
        try {
            name = templateNameFormat.normalizeRootBasedName(name);
        } catch (MalformedTemplateNameException e) {
            // If we don't have to emulate backward compatible behavior, then just rethrow it: 
            if (templateNameFormat != TemplateNameFormat.DEFAULT_2_3_0
                    || config.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_4_0) {
                throw e;
            }
            return new MaybeMissingTemplate(null, e);
        }
        
        if (templateLoader == null) {
            return new MaybeMissingTemplate(name, "The TemplateLoader was null.");
        }
        
        Template template = getTemplateInternal(name, locale, customLookupCondition, encoding, parseAsFTL);
        return template != null ? new MaybeMissingTemplate(template) : new MaybeMissingTemplate(name, (String) null);
    }    

    @Deprecated
    public Template getTemplate(String name, Locale locale, String encoding, boolean parseAsFTL)
    throws IOException {
        return getTemplate(name, locale, null, encoding, parseAsFTL).getTemplate();
    }
    
    @Deprecated
    protected static TemplateLoader createLegacyDefaultTemplateLoader() {
        return _TemplateAPI.createDefaultTemplateLoader(Configuration.VERSION_2_3_0);        
    }
    
    private Template getTemplateInternal(
            final String name, final Locale locale, final Object customLookupCondition,
            final String encoding, final boolean parseAsFTL)
    throws IOException {
        final boolean debug = LOG.isDebugEnabled();
        final String debugName = debug
                ? buildDebugName(name, locale, customLookupCondition, encoding, parseAsFTL)
                : null;
        final TemplateKey tk = new TemplateKey(name, locale, customLookupCondition, encoding, parseAsFTL);
        
        CachedTemplate cachedTemplate;
        if (isStorageConcurrent) {
            cachedTemplate = (CachedTemplate) storage.get(tk);
        } else {
            synchronized (storage) {
                cachedTemplate = (CachedTemplate) storage.get(tk);
            }
        }
        
        final long now = System.currentTimeMillis();
        
        long lastModified = -1L;
        boolean rethrown = false;
        TemplateLookupResult newLookupResult = null;
        try {
            if (cachedTemplate != null) {
                if (now - cachedTemplate.lastChecked < updateDelay) {
                    if (debug) {
                        LOG.debug(debugName + " cached copy not yet stale; using cached.");
                    }
                    // Can be null, indicating a cached negative lookup
                    Object t = cachedTemplate.templateOrException;
                    if (t instanceof Template || t == null) {
                        return (Template) t;
                    } else if (t instanceof RuntimeException) {
                        throwLoadFailedException((RuntimeException) t);
                    } else if (t instanceof IOException) {
                        rethrown = true;
                        throwLoadFailedException((IOException) t);
                    }
                    throw new BugException("t is " + t.getClass().getName());
                }
                
                cachedTemplate = cachedTemplate.cloneCachedTemplate();
                cachedTemplate.lastChecked = now;

                newLookupResult = lookupTemplate(name, locale, customLookupCondition);

                // Template source was removed
                if (!newLookupResult.isPositive()) {
                    if (debug) {
                        LOG.debug(debugName + " no source found.");
                    } 
                    storeNegativeLookup(tk, cachedTemplate, null);
                    return null;
                }

                final Object newLookupResultSource = newLookupResult.getTemplateSource();
                lastModified = templateLoader.getLastModified(newLookupResultSource);
                boolean lastModifiedNotChanged = lastModified == cachedTemplate.lastModified;
                boolean sourceEquals = newLookupResultSource.equals(cachedTemplate.source);
                if (lastModifiedNotChanged && sourceEquals) {
                    if (debug) {
                        LOG.debug(debugName + ": using cached since " + newLookupResultSource + " hasn't changed.");
                    }
                    storeCached(tk, cachedTemplate);
                    return (Template) cachedTemplate.templateOrException;
                } else if (debug) {
                    if (!sourceEquals) {
                        LOG.debug("Updating source because: " + 
                            "sourceEquals=" + sourceEquals + 
                            ", newlyFoundSource=" + StringUtil.jQuoteNoXSS(newLookupResultSource) + 
                            ", cached.source=" + StringUtil.jQuoteNoXSS(cachedTemplate.source));
                    } else if (!lastModifiedNotChanged) {
                        LOG.debug("Updating source because: " + 
                            "lastModifiedNotChanged=" + lastModifiedNotChanged + 
                            ", cached.lastModified=" + cachedTemplate.lastModified + 
                            " != source.lastModified=" + lastModified);
                    }
                }
            } else {
                if (debug) {
                    LOG.debug("Couldn't find template in cache for " + debugName + "; will try to load it.");
                }
                
                cachedTemplate = new CachedTemplate();
                cachedTemplate.lastChecked = now;
                
                newLookupResult = lookupTemplate(name, locale, customLookupCondition);
                
                if (!newLookupResult.isPositive()) {
                    storeNegativeLookup(tk, cachedTemplate, null);
                    return null;
                }
                
                cachedTemplate.lastModified = lastModified = Long.MIN_VALUE;
            }

            Object source = newLookupResult.getTemplateSource();
            cachedTemplate.source = source;
            
            if (debug) {
                LOG.debug("Loading template for " + debugName + " from " + StringUtil.jQuoteNoXSS(source));
            }
            
            lastModified = lastModified == Long.MIN_VALUE ? templateLoader.getLastModified(source) : lastModified;            
            Template template = loadTemplate(
                    templateLoader, source,
                    name, newLookupResult.getTemplateSourceName(), locale, customLookupCondition,
                    encoding, parseAsFTL);
            cachedTemplate.templateOrException = template;
            cachedTemplate.lastModified = lastModified;
            storeCached(tk, cachedTemplate);
            return template;
        } catch (RuntimeException e) {
            if (cachedTemplate != null) {
                storeNegativeLookup(tk, cachedTemplate, e);
            }
            throw e;
        } catch (IOException e) {
            if (!rethrown) {
                storeNegativeLookup(tk, cachedTemplate, e);
            }
            throw e;
        } finally {
            if (newLookupResult != null && newLookupResult.isPositive()) {
                templateLoader.closeTemplateSource(newLookupResult.getTemplateSource());
            }
        }
    }

    private static final Method INIT_CAUSE = getInitCauseMethod();
    
    private static final Method getInitCauseMethod() {
        try {
            return Throwable.class.getMethod("initCause", new Class[] { Throwable.class });
        } catch (NoSuchMethodException e) {
            return null;
        }
    }
    
    private IOException newIOException(String message, Throwable cause) {
        if (cause == null) {
            return new IOException(message);
        }
        
        IOException ioe;
        if (INIT_CAUSE != null) {
            ioe = new IOException(message);
            try {
                INIT_CAUSE.invoke(ioe, cause);
            } catch (RuntimeException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new UndeclaredThrowableException(ex);
            }
        } else {
            ioe = new IOException(message + "\nCaused by: " + cause.getClass().getName() + 
            ": " + cause.getMessage());
        }
        return ioe;
    }
    
    private void throwLoadFailedException(Throwable e) throws IOException {
        throw newIOException("There was an error loading the " +
                "template on an earlier attempt; see cause exception.", e);
    }

    private void storeNegativeLookup(TemplateKey tk, 
            CachedTemplate cachedTemplate, Exception e) {
        cachedTemplate.templateOrException = e;
        cachedTemplate.source = null;
        cachedTemplate.lastModified = 0L;
        storeCached(tk, cachedTemplate);
    }

    private void storeCached(TemplateKey tk, CachedTemplate cachedTemplate) {
        if (isStorageConcurrent) {
            storage.put(tk, cachedTemplate);
        } else {
            synchronized (storage) {
                storage.put(tk, cachedTemplate);
            }
        }
    }

    private Template loadTemplate(
            final TemplateLoader templateLoader, final Object source,
            final String name, final String sourceName, Locale locale, final Object customLookupCondition,
            String initialEncoding, final boolean parseAsFTL) throws IOException {
        final TemplateConfiguration tc;
        try {
            tc = templateConfigurations != null ? templateConfigurations.get(sourceName, source) : null;
        } catch (TemplateConfigurationFactoryException e) {
            throw newIOException("Error while getting TemplateConfiguration; see cause exception.", e);
        }
        if (tc != null) {
            // TC.{encoding,locale} is stronger than the cfg.getTemplate arguments by design.
            if (tc.isEncodingSet()) {
                initialEncoding = tc.getEncoding();
            }
            if (tc.isLocaleSet()) {
                locale = tc.getLocale();
            }
        }
        
        Template template;
        {
            if (parseAsFTL) {
                try {
                    try (Reader reader = templateLoader.getReader(source, initialEncoding)) {
                        template = new Template(name, sourceName, reader, config, tc, initialEncoding);
                    }
                } catch (Template.WrongEncodingException wee) {
                    String actualEncoding = wee.getTemplateSpecifiedEncoding();
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Initial encoding \"" + initialEncoding + "\" was incorrect, re-reading with \""
                                + actualEncoding + "\". Template: " + sourceName);
                    }

                    try (Reader reader = templateLoader.getReader(source, actualEncoding)) {
                        template = new Template(name, sourceName, reader, config, tc, actualEncoding);
                    }
                }
            } else {
                final StringWriter sw = new StringWriter();
                final char[] buf = new char[4096];
                try (Reader reader = templateLoader.getReader(source, initialEncoding)) {
                    fetchChars:
                    while (true) {
                        int charsRead = reader.read(buf);
                        if (charsRead > 0) {
                            sw.write(buf, 0, charsRead);
                        } else if (charsRead < 0) {
                            break fetchChars;
                        }
                    }
                }
                template = Template.getPlainTextTemplate(name, sourceName, sw.toString(), config);
                template.setEncoding(initialEncoding);
            }
        }

        if (tc != null) {
            tc.apply(template);
        }
        
        template.setLocale(locale);
        template.setCustomLookupCondition(customLookupCondition);
        return template;
    }

    public long getDelay() {
        synchronized (this) {
            return updateDelay;
        }
    }

    public void setDelay(long delay) {
        // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not.
        synchronized (this) {
            this.updateDelay = delay;
        }
    }

    public boolean getLocalizedLookup() {
        // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not.
        synchronized (this) {
            return localizedLookup;
        }
    }

    public void setLocalizedLookup(boolean localizedLookup) {
        // synchronized was moved here so that we don't advertise that it's thread-safe, as it's not.
        synchronized (this) {
            if (this.localizedLookup != localizedLookup) {
                this.localizedLookup = localizedLookup;
                clear();
            }
        }
    }

    public void clear() {
        synchronized (storage) {
            storage.clear();
            if (templateLoader instanceof StatefulTemplateLoader) {
                ((StatefulTemplateLoader) templateLoader).resetState();
            }
        }
    }

    /**
     * Same as {@link #removeTemplate(String, Locale, Object, String, boolean)} with {@code null}
     * {@code customLookupCondition}.
     */
    public void removeTemplate(
            String name, Locale locale, String encoding, boolean parse) throws IOException {
        removeTemplate(name, locale, null, encoding, parse);
    }
    
    public void removeTemplate(
            String name, Locale locale, Object customLookupCondition, String encoding, boolean parse)
    throws IOException {
        if (name == null) {
            throw new IllegalArgumentException("Argument \"name\" can't be null");
        }
        if (locale == null) {
            throw new IllegalArgumentException("Argument \"locale\" can't be null");
        }
        if (encoding == null) {
            throw new IllegalArgumentException("Argument \"encoding\" can't be null");
        }
        name = templateNameFormat.normalizeRootBasedName(name);
        if (name != null && templateLoader != null) {
            boolean debug = LOG.isDebugEnabled();
            String debugName = debug
                    ? buildDebugName(name, locale, customLookupCondition, encoding, parse)
                    : null;
            TemplateKey tk = new TemplateKey(name, locale, customLookupCondition, encoding, parse);
            
            if (isStorageConcurrent) {
                storage.remove(tk);
            } else {
                synchronized (storage) {
                    storage.remove(tk);
                }
            }
            if (debug) {
                LOG.debug(debugName + " was removed from the cache, if it was there");
            }
        }
    }

    private String buildDebugName(String name, Locale locale, Object customLookupCondition, String encoding,
            boolean parse) {
        return StringUtil.jQuoteNoXSS(name) + "("
                + StringUtil.jQuoteNoXSS(locale)
                + (customLookupCondition != null ? ", cond=" + StringUtil.jQuoteNoXSS(customLookupCondition) : "")
                + ", " + encoding
                + (parse ? ", parsed)" : ", unparsed]");
    }    

    @Deprecated
    public static String getFullTemplatePath(Environment env, String baseName, String targetName) {
        try {
            return env.toFullTemplateName(baseName, targetName);
        } catch (MalformedTemplateNameException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    private TemplateLookupResult lookupTemplate(String name, Locale locale, Object customLookupCondition)
            throws IOException {
        final TemplateLookupResult lookupResult = templateLookupStrategy.lookup(
                new TemplateCacheTemplateLookupContext(name, locale, customLookupCondition));
        if (lookupResult == null) {
            throw new NullPointerException("Lookup result shouldn't be null");
        }
        return lookupResult;
    }

    private TemplateLookupResult lookupTemplateWithAcquisitionStrategy(String path) throws IOException {
        int asterisk = path.indexOf(ASTERISK);
        // Shortcut in case there is no acquisition
        if (asterisk == -1) {
            return TemplateLookupResult.from(path, findTemplateSource(path));
        }
        StringTokenizer tok = new StringTokenizer(path, "/");
        int lastAsterisk = -1;
        List tokpath = new ArrayList();
        while (tok.hasMoreTokens()) {
            String pathToken = tok.nextToken();
            if (pathToken.equals(ASTERISKSTR)) {
                if (lastAsterisk != -1) {
                    tokpath.remove(lastAsterisk);
                }
                lastAsterisk = tokpath.size();
            }
            tokpath.add(pathToken);
        }
        if (lastAsterisk == -1) {  // if there was no real "*" step after all
            return TemplateLookupResult.from(path, findTemplateSource(path));
        }
        String basePath = concatPath(tokpath, 0, lastAsterisk);
        String resourcePath = concatPath(tokpath, lastAsterisk + 1, tokpath.size());
        if (resourcePath.endsWith("/")) {
            resourcePath = resourcePath.substring(0, resourcePath.length() - 1);
        }
        StringBuilder buf = new StringBuilder(path.length()).append(basePath);
        int l = basePath.length();
        for (; ; ) {
            String fullPath = buf.append(resourcePath).toString();
            Object templateSource = findTemplateSource(fullPath);
            if (templateSource != null) {
                return TemplateLookupResult.from(fullPath, templateSource);
            }
            if (l == 0) {
                return TemplateLookupResult.createNegativeResult();
            }
            l = basePath.lastIndexOf(SLASH, l - 2) + 1;
            buf.setLength(l);
        }
    }

    private Object findTemplateSource(String path) throws IOException {
        final Object result = templateLoader.findTemplateSource(path);
        if (LOG.isDebugEnabled()) {
            LOG.debug("TemplateLoader.findTemplateSource(" +  StringUtil.jQuote(path) + "): "
                    + (result == null ? "Not found" : "Found"));
        }
        return modifyForConfIcI(result);
    }

    private Object modifyForConfIcI(Object templateSource) {
        if (templateSource == null) return null;
        
        if (config.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
            return templateSource;
        }
        
        if (templateSource instanceof URLTemplateSource) {
            URLTemplateSource urlTemplateSource = (URLTemplateSource) templateSource;
            if (urlTemplateSource.getUseCaches() == null) {  // It was left unset
                urlTemplateSource.setUseCaches(false);
            }
        } else if (templateSource instanceof MultiSource) {
            modifyForConfIcI(((MultiSource) templateSource).getWrappedSource());
        }
        return templateSource;
    }

    private String concatPath(List path, int from, int to) {
        StringBuilder buf = new StringBuilder((to - from) * 16);
        for (int i = from; i < to; ++i) {
            buf.append(path.get(i)).append('/');
        }
        return buf.toString();
    }
    
    private static final class TemplateKey {
        private final String name;
        private final Locale locale;
        private final Object customLookupCondition;
        private final String encoding;
        private final boolean parse;

        TemplateKey(String name, Locale locale, Object customLookupCondition, String encoding, boolean parse) {
            this.name = name;
            this.locale = locale;
            this.customLookupCondition = customLookupCondition;
            this.encoding = encoding;
            this.parse = parse;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof TemplateKey) {
                TemplateKey tk = (TemplateKey) o;
                return
                    parse == tk.parse &&
                    name.equals(tk.name) &&
                    locale.equals(tk.locale) &&
                    nullSafeEquals(customLookupCondition, tk.customLookupCondition) &&
                    encoding.equals(tk.encoding);
            }
            return false;
        }

        private boolean nullSafeEquals(Object o1, Object o2) {
            return o1 != null
                ? (o2 != null ? o1.equals(o2) : false)
                : o2 == null;
        }

        @Override
        public int hashCode() {
            return
                name.hashCode() ^
                locale.hashCode() ^
                encoding.hashCode() ^
                (customLookupCondition != null ? customLookupCondition.hashCode() : 0) ^
                Boolean.valueOf(!parse).hashCode();
        }
    }

    private static final class CachedTemplate implements Cloneable, Serializable {
        private static final long serialVersionUID = 1L;

        Object templateOrException;
        Object source;
        long lastChecked;
        long lastModified;
        
        public CachedTemplate cloneCachedTemplate() {
            try {
                return (CachedTemplate) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new UndeclaredThrowableException(e);
            }
        }
    }
    
    private class TemplateCacheTemplateLookupContext extends TemplateLookupContext {

        TemplateCacheTemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition) {
            super(templateName, localizedLookup ? templateLocale : null, customLookupCondition);
        }

        @Override
        public TemplateLookupResult lookupWithAcquisitionStrategy(String name) throws IOException {
            // Only one of the possible ways of making a name non-normalized, but is the easiest mistake to do:
            if (name.startsWith("/")) {
                throw new IllegalArgumentException("Non-normalized name, starts with \"/\": " + name);
            }
            
            return TemplateCache.this.lookupTemplateWithAcquisitionStrategy(name);
        }

        @Override
        public TemplateLookupResult lookupWithLocalizedThenAcquisitionStrategy(final String templateName,
                final Locale templateLocale) throws IOException {
            
                if (templateLocale == null) {
                    return lookupWithAcquisitionStrategy(templateName);
                }
                
                int lastDot = templateName.lastIndexOf('.');
                String prefix = lastDot == -1 ? templateName : templateName.substring(0, lastDot);
                String suffix = lastDot == -1 ? "" : templateName.substring(lastDot);
                String localeName = LOCALE_PART_SEPARATOR + templateLocale.toString();
                StringBuilder buf = new StringBuilder(templateName.length() + localeName.length());
                buf.append(prefix);
                tryLocaleNameVariations: while (true) {
                    buf.setLength(prefix.length());
                    String path = buf.append(localeName).append(suffix).toString();
                    TemplateLookupResult lookupResult = lookupWithAcquisitionStrategy(path);
                    if (lookupResult.isPositive()) {
                        return lookupResult;
                    }
                    
                    int lastUnderscore = localeName.lastIndexOf('_');
                    if (lastUnderscore == -1) {
                        break tryLocaleNameVariations;
                    }
                    localeName = localeName.substring(0, lastUnderscore);
                }
                return createNegativeLookupResult();
        }
        
    }
    
 
    public final static class MaybeMissingTemplate {
        
        private final Template template;
        private final String missingTemplateNormalizedName;
        private final String missingTemplateReason;
        private final MalformedTemplateNameException missingTemplateCauseException;
        
        private MaybeMissingTemplate(Template template) {
            this.template = template;
            this.missingTemplateNormalizedName = null;
            this.missingTemplateReason = null;
            this.missingTemplateCauseException = null;
        }
        
        private MaybeMissingTemplate(String normalizedName, MalformedTemplateNameException missingTemplateCauseException) {
            this.template = null;
            this.missingTemplateNormalizedName = normalizedName;
            this.missingTemplateReason = null;
            this.missingTemplateCauseException = missingTemplateCauseException;
        }
        
        private MaybeMissingTemplate(String normalizedName, String missingTemplateReason) {
            this.template = null;
            this.missingTemplateNormalizedName = normalizedName;
            this.missingTemplateReason = missingTemplateReason;
            this.missingTemplateCauseException = null;
        }
        
        public Template getTemplate() {
            return template;
        }

        public String getMissingTemplateReason() {
            return missingTemplateReason != null
                    ? missingTemplateReason
                    : (missingTemplateCauseException != null
                            ? missingTemplateCauseException.getMalformednessDescription()
                            : null);
        }
        
        public String getMissingTemplateNormalizedName() {
            return missingTemplateNormalizedName;
        }
        
    }
    
}

作用:执行模板的缓存和按需加载。实际的模板“文件”加载被委托给一个TemplateLoader对象,你可以在构造函数中指定它。缓存的某些方面委托给CacheStorage对象,你也可以在构造函数中指定。

URLTemplateSource类

代码

class URLTemplateSource {
    private final URL url;
    private URLConnection conn;
    private InputStream inputStream;
    private Boolean useCaches;

    URLTemplateSource(URL url, Boolean useCaches) throws IOException {
        this.url = url;
        this.conn = url.openConnection();
        this.useCaches = useCaches;
        if (useCaches != null) {
            conn.setUseCaches(useCaches.booleanValue());
        }
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof URLTemplateSource) {
            return url.equals(((URLTemplateSource) o).url);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return url.hashCode();
    }

    @Override
    public String toString() {
        return url.toString();
    }
    
    long lastModified() {
        if (conn instanceof JarURLConnection) {
          URL jarURL = ((JarURLConnection) conn).getJarFileURL();
          if (jarURL.getProtocol().equals("file")) {
            return new File(jarURL.getFile()).lastModified();
          } else {
            // Use the URL mechanism
            URLConnection jarConn = null;
            try {
              jarConn = jarURL.openConnection();
              return jarConn.getLastModified();
            } catch (IOException e) {
              return -1;
            } finally {
              try {
                if (jarConn != null) jarConn.getInputStream().close();
              } catch (IOException e) { }
            }
          }
        } else {
          long lastModified = conn.getLastModified();
          if (lastModified == -1L && url.getProtocol().equals("file")) {
              return new File(url.getFile()).lastModified();
          } else {
              return lastModified;
          }
        }
    }

    InputStream getInputStream() throws IOException {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
            }
            this.conn = url.openConnection();
        }
        inputStream = conn.getInputStream();
        return inputStream;
    }

    void close() throws IOException {
        try {
          if (inputStream != null) {
              inputStream.close();
          } else {
              conn.getInputStream().close();
          }
        } finally {
          inputStream = null;
          conn = null;
        }
    }

    Boolean getUseCaches() {
        return useCaches;
    }

    void setUseCaches(boolean useCaches) {
        if (this.conn != null) {
            conn.setUseCaches(useCaches);
            this.useCaches = Boolean.valueOf(useCaches);
        }
    }
    
}

作用:包装一个URL对象,并实现一个典型模板源所需的方法。

TemplateLookupStrategy类

代码

public abstract class TemplateLookupStrategy {

    public static final TemplateLookupStrategy DEFAULT_2_3_0 = new Default020300();
    
    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";
        }
        
    }
    
}

作用:为请求模板的模板名找到TemplateLoader-level(存储级)模板源(如在Configuration.getTemplate(String)中)。这通常意味着尝试各种TemplateLoader对象级别的模板名称(也就是所谓的源名称;参见Template.getSourceName())是从请求的名称推导出来的。

TemplateLookupContext类

代码

public abstract class TemplateLookupContext {
    
    private final String templateName;
    private final Locale templateLocale;
    private final Object customLookupCondition;

    public abstract TemplateLookupResult lookupWithAcquisitionStrategy(String templateName) throws IOException;

    public abstract TemplateLookupResult lookupWithLocalizedThenAcquisitionStrategy(String templateName,
            Locale templateLocale) throws IOException;
    
    TemplateLookupContext(String templateName, Locale templateLocale, Object customLookupCondition) {
        this.templateName = templateName;
        this.templateLocale = templateLocale;
        this.customLookupCondition = customLookupCondition;
    }

    public String getTemplateName() {
        return templateName;
    }

    public Locale getTemplateLocale() {
        return templateLocale;
    }

    public Object getCustomLookupCondition() {
        return customLookupCondition;
    }

    public TemplateLookupResult createNegativeLookupResult() {
        return TemplateLookupResult.createNegativeResult();
    }
    
}

作用:用作TemplateLookupStrategy.lookup(TemplateLookupContext)的参数。不能创建它的实例,只能从FreeMarker接收它们。

TemplateLookupResult类

代码

public abstract class TemplateLookupResult {

    static TemplateLookupResult createNegativeResult() {
        return NegativeTemplateLookupResult.INSTANCE;
    }
    
    /** Used internally to create the appropriate kind of result from the parameters. */
    static TemplateLookupResult from(String templateSourceName, Object templateSource) {
        return templateSource != null
                ? new PositiveTemplateLookupResult(templateSourceName, templateSource)
                : createNegativeResult();
    }
    
    private TemplateLookupResult() {
        // nop
    }

    public abstract String getTemplateSourceName();

    public abstract boolean isPositive();

    abstract Object getTemplateSource();

    private static final class PositiveTemplateLookupResult extends TemplateLookupResult {

        private final String templateSourceName;
        private final Object templateSource;

        private PositiveTemplateLookupResult(String templateSourceName, Object templateSource) {
            NullArgumentException.check("templateName", templateSourceName);
            NullArgumentException.check("templateSource", templateSource);

            if (templateSource instanceof TemplateLookupResult) {
                throw new IllegalArgumentException();
            }

            this.templateSourceName = templateSourceName;
            this.templateSource = templateSource;
        }

        @Override
        public String getTemplateSourceName() {
            return templateSourceName;
        }

        @Override
        Object getTemplateSource() {
            return templateSource;
        }

        @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
        Object getTemplateSource() {
            return null;
        }

        @Override
        public boolean isPositive() {
            return false;
        }
        
    }
    
}

作用:TemplateLookupStrategy.lookup(TemplateLookupContext)和类似的查找方法的返回值,通常由TemplateLookupContext.lookupWithAcquisitionStrategy(String)或者TemplateLookupContext.createNegativeLookupResult()获取此类的对象,不能直接创建shi它的实例。

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

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

上一篇:SpringBoot2的自动配置原理


下一篇:springboot-05-web静态资源导入