1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.scripting.velocity;
17
18 import java.io.IOException;
19 import java.io.StringWriter;
20 import java.io.Writer;
21 import java.util.Iterator;
22
23 import org.apache.velocity.context.ChainedInternalContextAdapter;
24 import org.apache.velocity.context.InternalContextAdapter;
25 import org.apache.velocity.exception.TemplateInitException;
26 import org.apache.velocity.exception.VelocityException;
27 import org.apache.velocity.runtime.RuntimeServices;
28 import org.apache.velocity.runtime.directive.Directive;
29 import org.apache.velocity.runtime.directive.Scope;
30 import org.apache.velocity.runtime.directive.StopCommand;
31 import org.apache.velocity.runtime.parser.node.ASTReference;
32 import org.apache.velocity.runtime.parser.node.ASTStringLiteral;
33 import org.apache.velocity.runtime.parser.node.Node;
34 import org.apache.velocity.runtime.parser.node.StandardParserTreeConstants;
35 import org.apache.velocity.util.introspection.Info;
36
37
38
39
40 public class RepeatDirective extends Directive {
41
42 protected static final int MAX_IN_CLAUSE_SIZE = 1000;
43
44
45 private String var;
46 private String open = "";
47 private String close = "";
48 private String separator = "";
49 protected Info uberInfo;
50
51 @Override
52 public String getName() {
53 return "repeat";
54 }
55
56 @Override
57 public void init(RuntimeServices rs, InternalContextAdapter context, Node node) {
58 super.init(rs, context, node);
59 final int n = node.jjtGetNumChildren() - 1;
60 for (int i = 1; i < n; i++) {
61 Node child = node.jjtGetChild(i);
62 if (i == 1) {
63 if (child.getType() == StandardParserTreeConstants.JJTREFERENCE) {
64 this.var = ((ASTReference) child).getRootString();
65 } else {
66 throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn());
67 }
68 } else if (child.getType() == StandardParserTreeConstants.JJTSTRINGLITERAL) {
69 String value = (String) ((ASTStringLiteral) child).value(context);
70 switch (i) {
71 case 2:
72 this.separator = value;
73 break;
74 case 3:
75 this.open = value;
76 break;
77 case 4:
78 this.close = value;
79 break;
80 default:
81 break;
82 }
83 } else {
84 throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn());
85 }
86 }
87 this.uberInfo = new Info(this.getTemplateName(), getLine(), getColumn());
88 }
89
90 @Override
91 public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException {
92
93 Object listObject = node.jjtGetChild(0).value(context);
94
95 if (listObject == null) {
96 return false;
97 }
98
99 Iterator<?> i = null;
100
101 try {
102 i = this.rsvc.getUberspect().getIterator(listObject, this.uberInfo);
103 } catch (RuntimeException e) {
104 throw e;
105 } catch (Exception ee) {
106 String msg = "Error getting iterator for #repeat at " + this.uberInfo;
107 this.rsvc.getLog().error(msg, ee);
108 throw new VelocityException(msg, ee);
109 }
110
111 if (i == null) {
112 throw new VelocityException("Invalid collection");
113 }
114
115 int counter = 0;
116 boolean maxNbrLoopsExceeded = false;
117 Object o = context.get(this.var);
118
119 ParameterMappingCollector collector = (ParameterMappingCollector) context
120 .get(SQLScriptSource.MAPPING_COLLECTOR_KEY);
121 String savedItemKey = collector.getItemKey();
122 collector.setItemKey(this.var);
123 RepeatScope foreach = new RepeatScope(this, context.get(getName()), this.var);
124 context.put(getName(), foreach);
125
126 NullHolderContext nullHolderContext = null;
127 StringWriter buffer = new StringWriter();
128 while (!maxNbrLoopsExceeded && i.hasNext()) {
129 Object value = i.next();
130 put(context, this.var, value);
131 foreach.index++;
132 foreach.hasNext = i.hasNext();
133
134 try {
135 if (value == null) {
136 if (nullHolderContext == null) {
137 nullHolderContext = new NullHolderContext(this.var, context);
138 }
139 node.jjtGetChild(node.jjtGetNumChildren() - 1).render(nullHolderContext, buffer);
140 } else {
141 node.jjtGetChild(node.jjtGetNumChildren() - 1).render(context, buffer);
142 }
143 } catch (StopCommand stop) {
144 if (stop.isFor(this)) {
145 break;
146 }
147 clean(context, o, collector, savedItemKey);
148
149
150 buffer.close();
151 throw stop;
152 }
153
154 counter++;
155 maxNbrLoopsExceeded = counter >= MAX_IN_CLAUSE_SIZE;
156
157 if (i.hasNext() && !maxNbrLoopsExceeded) {
158 buffer.append(this.separator);
159 }
160
161 }
162 String content = buffer.toString().trim();
163 if (!"".equals(content)) {
164 writer.append(this.open);
165 writer.append(content);
166 writer.append(this.close);
167 }
168 clean(context, o, collector, savedItemKey);
169 return true;
170
171 }
172
173 protected void clean(InternalContextAdapter context, Object o, ParameterMappingCollector collector,
174 String savedItemKey) {
175 if (o != null) {
176 context.put(this.var, o);
177 } else {
178 context.remove(this.var);
179 }
180 collector.setItemKey(savedItemKey);
181 postRender(context);
182 }
183
184 protected void put(InternalContextAdapter context, String key, Object value) {
185 context.put(key, value);
186 }
187
188 @Override
189 public int getType() {
190 return BLOCK;
191 }
192
193 public static class RepeatScope extends Scope {
194
195 protected int index = -1;
196 protected boolean hasNext = false;
197 protected final String var;
198
199 public RepeatScope(Object newOwner, Object replaces, String newVar) {
200 super(newOwner, replaces);
201 this.var = newVar;
202 }
203
204 public int getIndex() {
205 return this.index;
206 }
207
208 public int getCount() {
209 return this.index + 1;
210 }
211
212 public boolean hasNext() {
213 return getHasNext();
214 }
215
216 public boolean getHasNext() {
217 return this.hasNext;
218 }
219
220 public boolean isFirst() {
221 return this.index < 1;
222 }
223
224 public boolean getFirst() {
225 return isFirst();
226 }
227
228 public boolean isLast() {
229 return !this.hasNext;
230 }
231
232 public boolean getLast() {
233 return isLast();
234 }
235
236 public String getVar() {
237 return this.var;
238 }
239
240 }
241
242 protected static class NullHolderContext extends ChainedInternalContextAdapter {
243
244 private String loopVariableKey = "";
245 private boolean active = true;
246
247 protected NullHolderContext(String key, InternalContextAdapter context) {
248 super(context);
249 if (key != null) {
250 this.loopVariableKey = key;
251 }
252 }
253
254 @Override
255 public Object get(String key) {
256 return (this.active && this.loopVariableKey.equals(key)) ? null : super.get(key);
257 }
258
259 @Override
260 public Object put(String key, Object value) {
261 if (this.loopVariableKey.equals(key) && (value == null)) {
262 this.active = true;
263 }
264
265 return super.put(key, value);
266 }
267
268 @Override
269 public Object remove(String key) {
270 if (this.loopVariableKey.equals(key)) {
271 this.active = false;
272 }
273 return super.remove(key);
274 }
275 }
276
277 @Override
278 public boolean isScopeProvided() {
279 return true;
280 }
281
282 }