Skip to content

Commit d1d398b

Browse files
author
Sam Pullara
committed
add deferring mustache factory to the main
1 parent f333f00 commit d1d398b

File tree

6 files changed

+185
-12
lines changed

6 files changed

+185
-12
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.github.mustachejava;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.StringWriter;
6+
import java.io.Writer;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.concurrent.Callable;
10+
import java.util.concurrent.Future;
11+
import java.util.concurrent.atomic.AtomicLong;
12+
13+
import com.github.mustachejava.codes.PartialCode;
14+
import com.github.mustachejava.util.GuardException;
15+
import com.github.mustachejava.util.Wrapper;
16+
17+
/**
18+
* This allows you to automatically defer evaluation of partials. By default
19+
* it generates HTML but you can override that behavior.
20+
*/
21+
public class DeferringMustacheFactory extends DefaultMustacheFactory {
22+
23+
public static final Object DEFERRED = new Object();
24+
25+
public DeferringMustacheFactory() {
26+
}
27+
28+
public DeferringMustacheFactory(String resourceRoot) {
29+
super(resourceRoot);
30+
}
31+
32+
public DeferringMustacheFactory(File fileRoot) {
33+
super(fileRoot);
34+
}
35+
36+
private static class Deferral {
37+
final long id;
38+
final Future<Object> future;
39+
40+
Deferral(long id, Future<Object> future) {
41+
this.id = id;
42+
this.future = future;
43+
}
44+
}
45+
46+
public static class DeferredCallable implements Callable<String> {
47+
48+
private List<Deferral> deferrals = new ArrayList<Deferral>();
49+
50+
public void add(Deferral deferral) {
51+
deferrals.add(deferral);
52+
}
53+
54+
@Override
55+
public String call() throws Exception {
56+
StringBuilder sb = new StringBuilder();
57+
for (Deferral deferral : deferrals) {
58+
Object o = deferral.future.get();
59+
if (o != null) {
60+
writeDeferral(sb, deferral, o);
61+
}
62+
}
63+
return sb.toString();
64+
}
65+
}
66+
67+
@Override
68+
public MustacheVisitor createMustacheVisitor() {
69+
final AtomicLong id = new AtomicLong(0);
70+
return new DefaultMustacheVisitor(this) {
71+
@Override
72+
public void partial(TemplateContext templateContext, final String variable) {
73+
TemplateContext partialTC = new TemplateContext("{{", "}}", templateContext.file(),
74+
templateContext.line());
75+
final Long divid = id.incrementAndGet();
76+
list.add(new PartialCode(partialTC, cf, variable) {
77+
Wrapper deferredWrapper;
78+
79+
@Override
80+
protected Writer partialExecute(Writer writer, final Object[] scopes) {
81+
final Object object = get(variable, scopes);
82+
final DeferredCallable deferredCallable = getDeferred(scopes);
83+
if (object == DEFERRED && deferredCallable != null) {
84+
try {
85+
writeTarget(writer, divid);
86+
} catch (IOException e) {
87+
throw new MustacheException("Failed to write", e);
88+
}
89+
deferredCallable.add(
90+
new Deferral(divid, getExecutorService().submit(new Callable<Object>() {
91+
@Override
92+
public Object call() {
93+
try {
94+
StringWriter writer = new StringWriter();
95+
execute(writer, object, scopes).close();
96+
return writer.toString();
97+
} catch (IOException e) {
98+
throw new MustacheException("Failed to writer", e);
99+
}
100+
}
101+
})));
102+
return writer;
103+
} else {
104+
return super.partialExecute(writer, scopes);
105+
}
106+
}
107+
108+
private DeferredCallable getDeferred(Object[] scopes) {
109+
try {
110+
if (deferredWrapper == null) {
111+
deferredWrapper = getObjectHandler().find("deferred", scopes);
112+
}
113+
return (DeferredCallable) deferredWrapper.call(scopes);
114+
} catch (GuardException e) {
115+
deferredWrapper = null;
116+
return getDeferred(scopes);
117+
}
118+
}
119+
});
120+
}
121+
};
122+
}
123+
124+
protected void writeTarget(Writer writer, Long divid) throws IOException {
125+
writer.append("<div id=\"");
126+
writer.append(divid.toString());
127+
writer.append("\"></div>\n");
128+
}
129+
130+
protected static void writeDeferral(StringBuilder sb, Deferral deferral, Object o) {
131+
sb.append("<script>document.getElementById(\"");
132+
sb.append(deferral.id);
133+
sb.append("\").innerHTML=\"");
134+
sb.append(o.toString().replace("<", "&lt;").replace("\"", "\\\"").replace("\n", "\\n"));
135+
sb.append("\";</script>");
136+
}
137+
}

compiler/src/main/java/com/github/mustachejava/codes/DefaultCode.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,7 @@ public Object get(String name, Object[] scopes) {
9797

9898
private Object get_recurse(String name, Object[] scopes, int depth) {
9999
if (depth > 10) {
100-
StringBuilder sb = new StringBuilder();
101-
sb.append(name);
102-
for (Object scope : scopes) {
103-
sb.append(":");
104-
sb.append(scope);
105-
if (scope != null) {
106-
sb.append("<");
107-
sb.append(scope.getClass());
108-
sb.append(">");
109-
}
110-
}
111-
throw new AssertionError("Guard recursion: " + sb);
100+
return recursionError(name, scopes);
112101
}
113102
if (returnThis) {
114103
return scopes[scopes.length - 1];
@@ -126,6 +115,21 @@ private Object get_recurse(String name, Object[] scopes, int depth) {
126115
}
127116
}
128117

118+
private Object recursionError(String name, Object[] scopes) {
119+
StringBuilder sb = new StringBuilder();
120+
sb.append(name);
121+
for (Object scope : scopes) {
122+
sb.append(":");
123+
sb.append(scope);
124+
if (scope != null) {
125+
sb.append("<");
126+
sb.append(scope.getClass());
127+
sb.append(">");
128+
}
129+
}
130+
throw new AssertionError("Guard recursion: " + sb);
131+
}
132+
129133
private boolean getWrapper(String name, Object[] scopes) {
130134
boolean notfound = false;
131135
wrapper = oh.find(name, scopes);

compiler/src/test/java/com/github/mustachejava/InterpreterTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,20 @@ public String call() throws Exception {
355355
}
356356
}
357357

358+
public void testDeferred() throws IOException {
359+
DefaultMustacheFactory mf = new DeferringMustacheFactory(root);
360+
mf.setExecutorService(Executors.newCachedThreadPool());
361+
Object context = new Object() {
362+
String title = "Deferred";
363+
Object deferred = new DeferringMustacheFactory.DeferredCallable();
364+
Object deferredpartial = DeferringMustacheFactory.DEFERRED;
365+
};
366+
Mustache m = mf.compile("deferred.html");
367+
StringWriter sw = new StringWriter();
368+
m.execute(sw, context).close();
369+
assertEquals(getContents(root, "deferred.txt"), sw.toString());
370+
}
371+
358372
private MustacheFactory init() {
359373
DefaultMustacheFactory cf = new DefaultMustacheFactory(root);
360374
return cf;

src/test/resources/deferred.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<head><title>{{title}}</title></head>
3+
<body>
4+
{{>deferredpartial}}
5+
{{{deferred}}}
6+
</body>
7+
</html>

src/test/resources/deferred.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<head><title>Deferred</title></head>
3+
<body>
4+
<div id="1"></div>
5+
<script>document.getElementById("1").innerHTML="I am calculated\n\"later\" and divs\nare written out &lt;\nnow\n";</script>
6+
</body>
7+
</html>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
I am calculated
2+
"later" and divs
3+
are written out <
4+
now

0 commit comments

Comments
 (0)