package com.github.mustachejava;
import com.github.mustachejava.codes.
DefaultMustache;
import com.github.mustachejava.reflect.
ReflectionObjectHandler;
import com.github.mustachejava.resolver.
DefaultResolver;
import java.io.
File;
import java.io.
Reader;
import java.io.
StringReader;
import java.io.
Writer;
import java.util.
HashMap;
import java.util.
Map;
import java.util.concurrent.
ConcurrentHashMap;
import java.util.concurrent.
ExecutorService;
import java.util.function.
Function;
import static com.github.mustachejava.util.
HtmlEscaper.escape;
/**
* Simplest possible code factory
*/
public class
DefaultMustacheFactory implements
MustacheFactory {
/**
* Create the default cache for mustache compilations. This is basically
* required by the specification to handle recursive templates.
*/
protected final
ConcurrentHashMap<
String,
Mustache>
mustacheCache =
createMustacheCache();
/**
* This is the default object handler.
*/
protected
ObjectHandler oh = new
ReflectionObjectHandler();
/**
* This parser should work with any MustacheFactory
*/
protected final
MustacheParser mc = new
MustacheParser(this);
/**
* New templates that are generated at runtime are cached here. The template key
* includes the text of the template and the context so we get proper error
* messages and debugging information.
*/
protected final
ConcurrentHashMap<
FragmentKey,
Mustache>
templateCache =
createLambdaCache();
protected int
recursionLimit = 100;
private final
MustacheResolver mustacheResolver;
protected
ExecutorService es;
public
DefaultMustacheFactory() {
this.
mustacheResolver = new
DefaultResolver();
}
public
DefaultMustacheFactory(
MustacheResolver mustacheResolver) {
this.
mustacheResolver =
mustacheResolver;
}
/**
* Use the classpath to resolve mustache templates.
*
* @param resourceRoot the location in the resources where templates are stored
*/
public
DefaultMustacheFactory(
String resourceRoot) {
this.
mustacheResolver = new
DefaultResolver(
resourceRoot);
}
/**
* Use the file system to resolve mustache templates.
*
* @param fileRoot the root of the file system where templates are stored
*/
public
DefaultMustacheFactory(
File fileRoot) {
this.
mustacheResolver = new
DefaultResolver(
fileRoot);
}
/**
* Using the directory, namd and extension, resolve a partial to a name.
*
* @param dir
* @param name
* @param extension
* @return
*/
public
String resolvePartialPath(
String dir,
String name,
String extension) {
String filePath =
name;
// Do not prepend directory if it is already defined
if (!
name.
startsWith("/")) {
filePath =
dir +
filePath;
}
// Do not append extension if it is already defined
if (!
name.
endsWith(
extension)) {
filePath =
filePath +
extension;
}
String path = new
File(
filePath).
getPath();
return
ensureForwardSlash(
path);
}
private static
String ensureForwardSlash(
String path) {
return
path.
replace('\\', '/');
}
@
Override
public
MustacheVisitor createMustacheVisitor() {
return new
DefaultMustacheVisitor(this);
}
@
Override
public
Reader getReader(
String resourceName) {
Reader reader =
mustacheResolver.
getReader(
resourceName);
if (
reader == null) {
throw new
MustacheNotFoundException(
resourceName);
}
return
reader;
}
@
Override
public void
encode(
String value,
Writer writer) {
escape(
value,
writer);
}
@
Override
public
ObjectHandler getObjectHandler() {
return
oh;
}
/**
* You can override the default object handler post construction.
*
* @param oh The object handler to use.
*/
public void
setObjectHandler(
ObjectHandler oh) {
this.
oh =
oh;
}
/**
* There is an ExecutorService that is used when executing parallel
* operations when a Callable is returned from a mustache value or iterable.
*
* @return the executor service
*/
public
ExecutorService getExecutorService() {
return
es;
}
/**
* If you need to specify your own executor service you can.
*
* @param es The executor service to use for Future evaluation
*/
public void
setExecutorService(
ExecutorService es) {
this.
es =
es;
}
public
Mustache getFragment(
FragmentKey templateKey) {
Mustache mustache =
templateCache.
computeIfAbsent(
templateKey,
getFragmentCacheFunction());
mustache.
init();
return
mustache;
}
protected
Function<
FragmentKey,
Mustache>
getFragmentCacheFunction() {
return (
fragmentKey) -> {
StringReader reader = new
StringReader(
fragmentKey.
templateText);
TemplateContext tc =
fragmentKey.
tc;
return
mc.
compile(
reader,
tc.
file(),
tc.
startChars(),
tc.
endChars(),
tc.
startOfLine());
};
}
@
Override
public
Mustache compile(
String name) {
Mustache mustache =
mustacheCache.
computeIfAbsent(
name,
getMustacheCacheFunction());
mustache.
init();
return
mustache;
}
@
Override
public
Mustache compile(
Reader reader,
String name) {
return
compile(
reader,
name, "{{", "}}");
}
// Template functions need this to comply with the specification
public
Mustache compile(
Reader reader,
String file,
String sm,
String em) {
Mustache compile =
mc.
compile(
reader,
file,
sm,
em);
compile.
init();
partialCache.
remove();
return
compile;
}
@
Override
public
String translate(
String from) {
return
from;
}
/**
* Override this method to apply any filtering to text that will appear
* verbatim in the output template.
*
* @param appended The text to be appended to the output
* @param startOfLine Are we at the start of the line?
* @return the filtered string
*/
public
String filterText(
String appended, boolean
startOfLine) {
return
appended;
}
/**
* Maximum recursion limit for partials.
*
* @param recursionLimit the number of recursions we will attempt before failing
*/
public void
setRecursionLimit(int
recursionLimit) {
this.
recursionLimit =
recursionLimit;
}
public int
getRecursionLimit() {
return
recursionLimit;
}
private final
ThreadLocal<
Map<
String,
Mustache>>
partialCache = new
ThreadLocal<
Map<
String,
Mustache>>() {
@
Override
protected
Map<
String,
Mustache>
initialValue() {
return new
HashMap<>();
}
};
/**
* In order to handle recursion, we need a temporary thread local cache during compilation
* that is ultimately thrown away after the top level partial is complete.
*
* @param s the name of the partial to compile
* @return the compiled partial
*/
public
Mustache compilePartial(
String s) {
final
Map<
String,
Mustache>
cache =
partialCache.
get();
final
Mustache cached =
cache.
get(
s);
if (
cached != null) {
// Our implementation supports this but I
// don't think it makes sense in the interface
if (
cached instanceof
DefaultMustache) {
((
DefaultMustache)
cached).
setRecursive();
}
return
cached;
}
try {
final
Mustache mustache =
mc.
compile(
s);
cache.
put(
s,
mustache);
mustache.
init();
return
mustache;
} finally {
cache.
remove(
s);
}
}
protected
Function<
String,
Mustache>
getMustacheCacheFunction() {
return
mc::compile;
}
protected
ConcurrentHashMap<
String,
Mustache>
createMustacheCache() {
return new
ConcurrentHashMap<>();
}
protected
ConcurrentHashMap<
FragmentKey,
Mustache>
createLambdaCache() {
return new
ConcurrentHashMap<>();
}
}