Skip to content

Commit 3a8dc43

Browse files
author
Sam Pullara
committed
Merge branch 'jruby'
2 parents 5f2de5c + 6051318 commit 3a8dc43

File tree

11 files changed

+449
-3
lines changed

11 files changed

+449
-3
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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import java.util.Map;
88
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.logging.Level;
910
import java.util.logging.Logger;
1011

1112
import com.github.mustachejava.Code;
@@ -91,6 +92,13 @@ public void setCodes(Code[] newcodes) {
9192
* @return The value of the field or method
9293
*/
9394
public Object get(String name, Object[] scopes) {
95+
return get_recurse(name, scopes, 0);
96+
}
97+
98+
private Object get_recurse(String name, Object[] scopes, int depth) {
99+
if (depth > 10) {
100+
return recursionError(name, scopes);
101+
}
94102
if (returnThis) {
95103
return scopes[scopes.length - 1];
96104
}
@@ -103,8 +111,23 @@ public Object get(String name, Object[] scopes) {
103111
return oh.coerce(wrapper.call(scopes));
104112
} catch (GuardException e) {
105113
wrapper = null;
106-
return get(name, scopes);
114+
return get_recurse(name, scopes, depth + 1);
115+
}
116+
}
117+
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+
}
107129
}
130+
throw new AssertionError("Guard recursion: " + sb);
108131
}
109132

110133
private boolean getWrapper(String name, Object[] scopes) {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.github.mustachejava.jruby;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.List;
5+
import javax.annotation.Nullable;
6+
7+
import com.google.common.base.Predicate;
8+
9+
import com.github.mustachejava.reflect.ReflectionObjectHandler;
10+
import com.github.mustachejava.util.Wrapper;
11+
import org.jruby.RubyBoolean;
12+
import org.jruby.RubyHash;
13+
import org.jruby.RubyObject;
14+
import org.jruby.RubySymbol;
15+
16+
public class JRubyObjectHandler extends ReflectionObjectHandler {
17+
18+
private static final Method CALL_METHOD;
19+
20+
static {
21+
try {
22+
CALL_METHOD = RubyHash.class.getMethod("callMethod", String.class);
23+
} catch (NoSuchMethodException e) {
24+
throw new AssertionError(e);
25+
}
26+
}
27+
28+
@Override
29+
public Object coerce(Object object) {
30+
if (object instanceof RubyBoolean) {
31+
RubyBoolean rb = (RubyBoolean) object;
32+
return rb.toJava(Boolean.class);
33+
}
34+
return object;
35+
}
36+
37+
@Override
38+
protected Wrapper findWrapper(final int scopeIndex, final Wrapper[] wrappers, final List<Predicate<Object[]>> guards, final Object scope, final String name) {
39+
Wrapper wrapper = super.findWrapper(scopeIndex, wrappers, guards, scope, name);
40+
if (wrapper == null) {
41+
if (scope instanceof RubyHash) {
42+
RubyHash hash = (RubyHash) scope;
43+
final RubySymbol rs = RubySymbol.newSymbol(hash.getRuntime(), name);
44+
if (hash.get(rs) != null) {
45+
guards.add(new Predicate<Object[]>() {
46+
@Override
47+
public boolean apply(@Nullable Object[] input) {
48+
assert input != null;
49+
return ((RubyHash)input[scopeIndex]).containsKey(rs);
50+
}
51+
});
52+
return createWrapper(scopeIndex, wrappers, guards, MAP_METHOD, new Object[]{rs});
53+
}
54+
}
55+
if (scope instanceof RubyObject) {
56+
RubyObject ro = (RubyObject) scope;
57+
if (ro.respondsTo(name)) {
58+
guards.add(new Predicate<Object[]>() {
59+
@Override
60+
public boolean apply(@Nullable Object[] objects) {
61+
RubyObject scope = (RubyObject) objects[scopeIndex];
62+
return scope.respondsTo(name);
63+
}
64+
});
65+
return createWrapper(scopeIndex, wrappers, guards, CALL_METHOD, new Object[]{ name });
66+
} else {
67+
guards.add(new Predicate<Object[]>() {
68+
@Override
69+
public boolean apply(@Nullable Object[] objects) {
70+
RubyObject scope = (RubyObject) objects[scopeIndex];
71+
return !scope.respondsTo(name);
72+
}
73+
});
74+
}
75+
}
76+
}
77+
return wrapper;
78+
}
79+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,20 @@ public String call() throws Exception {
396396
}
397397
}
398398

399+
public void testDeferred() throws IOException {
400+
DefaultMustacheFactory mf = new DeferringMustacheFactory(root);
401+
mf.setExecutorService(Executors.newCachedThreadPool());
402+
Object context = new Object() {
403+
String title = "Deferred";
404+
Object deferred = new DeferringMustacheFactory.DeferredCallable();
405+
Object deferredpartial = DeferringMustacheFactory.DEFERRED;
406+
};
407+
Mustache m = mf.compile("deferred.html");
408+
StringWriter sw = new StringWriter();
409+
m.execute(sw, context).close();
410+
assertEquals(getContents(root, "deferred.txt"), sw.toString());
411+
}
412+
399413
private MustacheFactory init() {
400414
DefaultMustacheFactory cf = new DefaultMustacheFactory(root);
401415
return cf;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.github.mustachejava;
2+
3+
import com.github.mustachejava.jruby.JRubyObjectHandler;
4+
import org.jruby.embed.ScriptingContainer;
5+
import org.junit.Test;
6+
7+
import java.io.IOException;
8+
import java.io.StringReader;
9+
import java.io.StringWriter;
10+
import java.io.Writer;
11+
12+
import static junit.framework.Assert.assertEquals;
13+
14+
public class JRubyTest {
15+
@Test
16+
public void testHash() throws IOException {
17+
ScriptingContainer sc = new ScriptingContainer();
18+
Object context = sc.runScriptlet("{:test=>'fred'}");
19+
DefaultMustacheFactory mf = new DefaultMustacheFactory();
20+
mf.setObjectHandler(new JRubyObjectHandler());
21+
Mustache m = mf.compile(new StringReader("{{test}}"), "test");
22+
Writer writer = new StringWriter();
23+
writer = m.execute(writer, context);
24+
writer.close();
25+
assertEquals("fred", writer.toString());
26+
}
27+
28+
@Test
29+
public void testObject() throws IOException {
30+
ScriptingContainer sc = new ScriptingContainer();
31+
Object context = sc.runScriptlet("class Test\ndef test()\n \"fred\"\nend\nend\nTest.new\n");
32+
DefaultMustacheFactory mf = new DefaultMustacheFactory();
33+
mf.setObjectHandler(new JRubyObjectHandler());
34+
Mustache m = mf.compile(new StringReader("{{test}}"), "test");
35+
Writer writer = new StringWriter();
36+
writer = m.execute(writer, context);
37+
writer.close();
38+
assertEquals("fred", writer.toString());
39+
}
40+
41+
@Test
42+
public void testArray() throws IOException {
43+
ScriptingContainer sc = new ScriptingContainer();
44+
Object context = sc.runScriptlet("class Test\ndef test()\n [\"fred\",\"fred\"]\nend\nend\nTest.new\n");
45+
DefaultMustacheFactory mf = new DefaultMustacheFactory();
46+
mf.setObjectHandler(new JRubyObjectHandler());
47+
Mustache m = mf.compile(new StringReader("{{#test}}{{.}}{{/test}}"), "test");
48+
Writer writer = new StringWriter();
49+
writer = m.execute(writer, context);
50+
writer.close();
51+
assertEquals("fredfred", writer.toString());
52+
}
53+
}

compiler/src/test/java/com/github/mustachejavabenchmarks/BenchmarkTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ public void testComplex() throws MustacheException, IOException {
4848
{
4949
DefaultMustacheFactory cf = new DefaultMustacheFactory();
5050
Mustache m = cf.compile("complex.html");
51-
complextest(m, new ComplexObject()).toString();
51+
ComplexObject complexObject = new ComplexObject();
52+
complextest(m, complexObject).toString();
5253
long start = System.currentTimeMillis();
5354
int total = 0;
5455
while (true) {
55-
complextest(m, new ComplexObject());
56+
complextest(m, complexObject);
5657
total++;
5758
if (System.currentTimeMillis() - start > TIME) break;
5859
}

0 commit comments

Comments
 (0)