package com.github.mustachejava;
import com.github.mustachejava.codes.
PartialCode;
import com.github.mustachejava.util.
GuardException;
import com.github.mustachejava.util.
InternalArrayList;
import com.github.mustachejava.util.
Wrapper;
import java.io.
File;
import java.io.
IOException;
import java.io.
StringWriter;
import java.io.
Writer;
import java.util.
ArrayList;
import java.util.
List;
import java.util.concurrent.
Callable;
import java.util.concurrent.
Future;
import java.util.concurrent.atomic.
AtomicLong;
/**
* This allows you to automatically defer evaluation of partials. By default
* it generates HTML but you can override that behavior.
*/
public class
DeferringMustacheFactory extends
DefaultMustacheFactory {
public static final
Object DEFERRED = new
Object();
public
DeferringMustacheFactory() {
}
public
DeferringMustacheFactory(
String resourceRoot) {
super(
resourceRoot);
}
public
DeferringMustacheFactory(
File fileRoot) {
super(
fileRoot);
}
private static class
Deferral {
final long
id;
final
Future<
Object>
future;
Deferral(long
id,
Future<
Object>
future) {
this.
id =
id;
this.
future =
future;
}
}
public static class
DeferredCallable implements
Callable<
String> {
private
List<
Deferral>
deferrals = new
ArrayList<>();
public void
add(
Deferral deferral) {
deferrals.
add(
deferral);
}
@
Override
public
String call() throws
Exception {
StringBuilder sb = new
StringBuilder();
for (
Deferral deferral :
deferrals) {
Object o =
deferral.
future.
get();
if (
o != null) {
writeDeferral(
sb,
deferral,
o);
}
}
return
sb.
toString();
}
}
@
Override
public
MustacheVisitor createMustacheVisitor() {
final
AtomicLong id = new
AtomicLong(0);
return new
DefaultMustacheVisitor(this) {
@
Override
public void
partial(
TemplateContext tc, final
String variable) {
TemplateContext partialTC = new
TemplateContext("{{", "}}",
tc.
file(),
tc.
line(),
tc.
startOfLine());
final
Long divid =
id.
incrementAndGet();
list.
add(new
PartialCode(
partialTC,
df,
variable) {
Wrapper deferredWrapper;
@
Override
public
Writer execute(
Writer writer, final
List<
Object>
scopes) {
final
Object object =
get(
scopes);
final
DeferredCallable deferredCallable =
getDeferred(
scopes);
if (
object ==
DEFERRED &&
deferredCallable != null) {
try {
writeTarget(
writer,
divid);
writer.
append(
appended);
} catch (
IOException e) {
throw new
MustacheException("Failed to write",
e);
}
// Make a copy of the scopes so we don't change them
List<
Object>
scopesCopy = new
InternalArrayList<>(
scopes);
deferredCallable.
add(
new
Deferral(
divid,
getExecutorService().
submit(() -> {
try {
StringWriter sw = new
StringWriter();
partial.
execute(
sw,
scopesCopy).
close();
return
sw.
toString();
} catch (
IOException e) {
throw new
MustacheException("Failed to writer",
e);
}
})));
return
writer;
} else {
return
appendText(
partial.
execute(
writer,
scopes));
}
}
private
DeferredCallable getDeferred(
List<
Object>
scopes) {
try {
if (
deferredWrapper == null) {
deferredWrapper =
getObjectHandler().
find("deferred",
scopes);
}
return (
DeferredCallable)
deferredWrapper.
call(
scopes);
} catch (
GuardException e) {
deferredWrapper = null;
return
getDeferred(
scopes);
}
}
});
}
};
}
protected void
writeTarget(
Writer writer,
Long divid) throws
IOException {
writer.
append("<div id=\"");
writer.
append(
divid.
toString());
writer.
append("\"></div>");
}
protected static void
writeDeferral(
StringBuilder sb,
Deferral deferral,
Object o) {
sb.
append("<script>document.getElementById(\"");
sb.
append(
deferral.
id);
sb.
append("\").innerHTML=\"");
sb.
append(
o.
toString().
replace("<", "<").
replace("\"", "\\\"").
replace("\n", "\\n"));
sb.
append("\";</script>");
}
}