2021SC@SDUSC
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 中文官方参考手册
新手写的代码分析,文章若有错误还请指出