Skip to content

Commit f233e83

Browse files
committed
Merge branch 'master' of github.com:spullara/mustache.java
2 parents d730bb7 + c477dec commit f233e83

7 files changed

Lines changed: 213 additions & 33 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Maven dependency information (ie. for most common cases you will just need the `
4343
<dependency>
4444
<groupId>com.github.spullara.mustache.java</groupId>
4545
<artifactId>compiler</artifactId>
46-
<version>0.8.7</version>
46+
<version>0.8.8</version>
4747
</dependency>
4848
```
4949

compiler/src/main/java/com/github/mustachejava/Code.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.github.mustachejava;
22

33
import java.io.Writer;
4+
import java.util.Set;
45

56
/**
67
* Code objects that are executed in order to evaluate the template
@@ -21,4 +22,6 @@ public interface Code {
2122
void init();
2223

2324
Object clone();
25+
26+
Object clone(Set<Code> seen);
2427
}

compiler/src/main/java/com/github/mustachejava/FallbackMustacheFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
* @author gw0 [http://gw.tnode.com/] <[email protected]>
1111
*/
12-
public class FallbackMustacheFactory extends DefaultMustacheFactory {
12+
public class FallbackMustacheFactory extends DefaultMustacheFactory {
1313

1414
/** List of fallback resource roots to search through. */
1515
private Object[] resourceRoots;

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import java.io.IOException;
66
import java.io.Writer;
7+
import java.util.HashSet;
8+
import java.util.Set;
79

810
/**
911
* Simplest possible code implementaion with some default shared behavior
@@ -21,18 +23,30 @@ public class DefaultCode implements Code, Cloneable {
2123
protected final Binding binding;
2224

2325
public Object clone() {
26+
Set<Code> seen = new HashSet<Code>();
27+
seen.add(this);
28+
return clone(seen);
29+
}
30+
31+
public Object clone(Set<Code> seen) {
2432
try {
2533
DefaultCode code = (DefaultCode) super.clone();
2634
Code[] codes = code.getCodes();
2735
if (codes != null) {
2836
codes = codes.clone();
2937
for (int i = 0; codes != null && i < codes.length; i++) {
30-
codes[i] = (Code) codes[i].clone();
38+
if (seen.add(codes[i])) {
39+
codes[i] = (Code) codes[i].clone(seen);
40+
seen.remove(codes[i]);
41+
}
3142
}
3243
code.setCodes(codes);
3344
}
3445
if (mustache != null) {
35-
code.mustache = (Mustache) mustache.clone();
46+
if (seen.add(mustache)) {
47+
code.mustache = (Mustache) mustache.clone(seen);
48+
seen.remove(mustache);
49+
}
3650
}
3751
return code;
3852
} catch (CloneNotSupportedException e) {

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

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
import java.io.Writer;
66
import java.util.HashMap;
7+
import java.util.HashSet;
78
import java.util.Map;
9+
import java.util.Set;
810

911
/**
1012
* Extending a template through in-place replacement of the overridden codes.
@@ -22,25 +24,28 @@ public ExtendCode(TemplateContext tc, DefaultMustacheFactory mf, Mustache codes,
2224
this.mf = mf;
2325
}
2426

25-
private Code[] replaceCodes(Code[] supercodes, Map<String, ExtendNameCode> replaceMap) {
27+
private Code[] replaceCodes(Code[] supercodes, Map<String, ExtendNameCode> replaceMap, Set<Code> seen) {
2628
Code[] newcodes = supercodes.clone();
2729
for (int i = 0; i < supercodes.length; i++) {
2830
Code code = supercodes[i];
29-
if (code instanceof ExtendNameCode) {
30-
ExtendNameCode enc = (ExtendNameCode) code;
31-
ExtendNameCode extendReplaceCode = replaceMap.get(enc.getName());
32-
if (extendReplaceCode != null) {
33-
ExtendNameCode newcode = (ExtendNameCode) (newcodes[i] = (Code) extendReplaceCode.clone());
34-
// We need to set the appended text of the new code to that of the old code
35-
newcode.appended = enc.appended;
31+
if (seen.add(code)) {
32+
if (code instanceof ExtendNameCode) {
33+
ExtendNameCode enc = (ExtendNameCode) code;
34+
ExtendNameCode extendReplaceCode = replaceMap.get(enc.getName());
35+
if (extendReplaceCode != null) {
36+
ExtendNameCode newcode = (ExtendNameCode) (newcodes[i] = (Code) extendReplaceCode.clone());
37+
// We need to set the appended text of the new code to that of the old code
38+
newcode.appended = enc.appended;
39+
} else {
40+
enc.setCodes(replaceCodes(enc.getCodes(), replaceMap, seen));
41+
}
3642
} else {
37-
enc.setCodes(replaceCodes(enc.getCodes(), replaceMap));
38-
}
39-
} else {
40-
Code[] codes = code.getCodes();
41-
if (codes != null) {
42-
code.setCodes(replaceCodes(codes, replaceMap));
43+
Code[] codes = code.getCodes();
44+
if (codes != null) {
45+
code.setCodes(replaceCodes(codes, replaceMap, seen));
46+
}
4347
}
48+
seen.remove(code);
4449
}
4550
}
4651
return newcodes;
@@ -72,7 +77,9 @@ public synchronized void init() {
7277
partial = (Mustache) original.clone();
7378
Code[] supercodes = partial.getCodes();
7479
// recursively replace named sections with replacements
75-
partial.setCodes(replaceCodes(supercodes, replaceMap));
80+
HashSet<Code> seen = new HashSet<Code>();
81+
seen.add(partial);
82+
partial.setCodes(replaceCodes(supercodes, replaceMap, seen));
7683
}
7784

7885
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package com.github.mustachejava;
2+
3+
import com.github.mustachejavabenchmarks.BenchmarkTest;
4+
import org.junit.Test;
5+
6+
import java.io.IOException;
7+
import java.io.StringReader;
8+
import java.io.StringWriter;
9+
import java.io.Writer;
10+
import java.security.SecureRandom;
11+
import java.util.Random;
12+
import java.util.concurrent.Callable;
13+
import java.util.concurrent.ExecutorService;
14+
import java.util.concurrent.Executors;
15+
import java.util.concurrent.Semaphore;
16+
import java.util.concurrent.atomic.AtomicInteger;
17+
18+
import static com.github.mustachejavabenchmarks.BenchmarkTest.skip;
19+
import static junit.framework.Assert.assertEquals;
20+
21+
/**
22+
* Inspired by an unconfirmed bug report.
23+
*/
24+
public class ConcurrencyTest {
25+
26+
static Random r = new SecureRandom();
27+
28+
private static class TestObject {
29+
final int a;
30+
final int b;
31+
final int c;
32+
33+
int aa() throws InterruptedException {
34+
Thread.sleep(r.nextInt(10));
35+
return a;
36+
}
37+
38+
int bb() throws InterruptedException {
39+
Thread.sleep(r.nextInt(10));
40+
return b;
41+
}
42+
43+
int cc() throws InterruptedException {
44+
Thread.sleep(r.nextInt(10));
45+
return c;
46+
}
47+
48+
Callable<Integer> calla() throws InterruptedException {
49+
return new Callable<Integer>() {
50+
@Override
51+
public Integer call() throws Exception {
52+
Thread.sleep(r.nextInt(10));
53+
return a;
54+
}
55+
};
56+
}
57+
58+
Callable<Integer> callb() throws InterruptedException {
59+
return new Callable<Integer>() {
60+
@Override
61+
public Integer call() throws Exception {
62+
Thread.sleep(r.nextInt(10));
63+
return b;
64+
}
65+
};
66+
}
67+
68+
Callable<Integer> callc() throws InterruptedException {
69+
return new Callable<Integer>() {
70+
@Override
71+
public Integer call() throws Exception {
72+
Thread.sleep(r.nextInt(10));
73+
return c;
74+
}
75+
};
76+
}
77+
78+
private TestObject(int a, int b, int c) {
79+
this.a = a;
80+
this.b = b;
81+
this.c = c;
82+
}
83+
}
84+
85+
// Alternate render
86+
static String render(TestObject to) {
87+
return to.a + ":" + to.b + ":" + to.c;
88+
}
89+
90+
@Test
91+
public void testConcurrentExecution() throws InterruptedException {
92+
if (skip()) return;
93+
String template = "{{aa}}:{{bb}}:{{cc}}";
94+
final Mustache test = new DefaultMustacheFactory().compile(new StringReader(template), "test");
95+
ExecutorService es = Executors.newCachedThreadPool();
96+
final AtomicInteger total = new AtomicInteger();
97+
final Semaphore semaphore = new Semaphore(100);
98+
for (int i = 0; i < 100000; i++) {
99+
semaphore.acquire();
100+
es.submit(new Runnable() {
101+
@Override
102+
public void run() {
103+
try {
104+
TestObject testObject = new TestObject(r.nextInt(), r.nextInt(), r.nextInt());
105+
StringWriter sw = new StringWriter();
106+
test.execute(sw, testObject).close();
107+
if (!render(testObject).equals(sw.toString())) {
108+
total.incrementAndGet();
109+
}
110+
} catch (IOException e) {
111+
// Can't fail
112+
e.printStackTrace();
113+
System.exit(1);
114+
} finally {
115+
semaphore.release();
116+
}
117+
}
118+
});
119+
}
120+
// Wait for them all to complete
121+
semaphore.acquire(100);
122+
assertEquals(0, total.intValue());
123+
}
124+
125+
@Test
126+
public void testConcurrentExecutionWithConcurrentTemplate() throws InterruptedException {
127+
if (skip()) return;
128+
String template = "{{calla}}:{{callb}}:{{callc}}";
129+
ExecutorService es = Executors.newCachedThreadPool();
130+
DefaultMustacheFactory dmf = new DefaultMustacheFactory();
131+
dmf.setExecutorService(es);
132+
final Mustache test = dmf.compile(new StringReader(template), "test");
133+
final AtomicInteger total = new AtomicInteger();
134+
final Semaphore semaphore = new Semaphore(100);
135+
for (int i = 0; i < 100000; i++) {
136+
semaphore.acquire();
137+
es.submit(new Runnable() {
138+
@Override
139+
public void run() {
140+
try {
141+
TestObject testObject = new TestObject(r.nextInt(), r.nextInt(), r.nextInt());
142+
StringWriter sw = new StringWriter();
143+
test.execute(sw, testObject).close();
144+
if (!render(testObject).equals(sw.toString())) {
145+
total.incrementAndGet();
146+
}
147+
} catch (IOException e) {
148+
// Can't fail
149+
e.printStackTrace();
150+
System.exit(1);
151+
} finally {
152+
semaphore.release();
153+
}
154+
}
155+
});
156+
}
157+
// Wait for them all to complete
158+
semaphore.acquire(100);
159+
assertEquals(0, total.intValue());
160+
}
161+
}

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,15 @@ public void testRecurision() throws IOException {
6666
}
6767

6868
public void testRecursionWithInheritance() throws IOException {
69-
try {
70-
MustacheFactory c = createMustacheFactory();
71-
Mustache m = c.compile("recursion_with_inheritance.html");
72-
StringWriter sw = new StringWriter();
73-
m.execute(sw, new Object() {
74-
Object value = new Object() {
75-
boolean value = false;
76-
};
77-
});
78-
assertEquals(getContents(root, "recursion.txt"), sw.toString());
79-
fail("This has been fixed!");
80-
} catch (StackOverflowError soe) {
81-
// It isn't clear how to successfully do this yet.
82-
}
69+
MustacheFactory c = createMustacheFactory();
70+
Mustache m = c.compile("recursion_with_inheritance.html");
71+
StringWriter sw = new StringWriter();
72+
m.execute(sw, new Object() {
73+
Object value = new Object() {
74+
boolean value = false;
75+
};
76+
});
77+
assertEquals(getContents(root, "recursion.txt"), sw.toString());
8378
}
8479

8580
public void testSimplePragma() throws MustacheException, IOException, ExecutionException, InterruptedException {

0 commit comments

Comments
 (0)