1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.components;
23
24 import java.io.Writer;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Arrays;
28
29 import org.apache.struts2.views.annotations.StrutsTag;
30 import org.apache.struts2.views.annotations.StrutsTagAttribute;
31 import org.apache.struts2.util.MakeIterator;
32 import org.apache.struts2.views.jsp.IteratorStatus;
33
34 import com.opensymphony.xwork2.util.ValueStack;
35 import com.opensymphony.xwork2.util.logging.Logger;
36 import com.opensymphony.xwork2.util.logging.LoggerFactory;
37
38 /***
39 * <!-- START SNIPPET: javadoc -->
40 *
41 * <p>Iterator will iterate over a value. An iterable value can be any of: java.util.Collection, java.util.Iterator,
42 * java.util.Enumeration, java.util.Map, or an array.</p> <p/> <!-- END SNIPPET: javadoc -->
43 *
44 * <!-- START SNIPPET: params -->
45 *
46 * <ul>
47 *
48 * <li>status (String) - if specified, an instance of IteratorStatus will be pushed into stack upon each iteration</li>
49 *
50 * <li>value (Object) - the source to iterate over, must be iteratable, else the object itself will be put into a
51 * newly created List (see MakeIterator#convert(Object)</li>
52 *
53 * <li>id (String) - if specified the current iteration object will be place with this id in Struts stack's context
54 * scope</li>
55 *
56 * <li>begin (Integer) - if specified the iteration will start on that index</li>
57 *
58 * <li>end (Integer) - if specified the iteration will end on that index(inclusive)</li>
59 *
60 * <li>step (Integer) - if specified the iteration index will be increased by this value on each iteration. It can be a negative
61 * value, in which case 'begin' must be greater than 'end'</li>
62 *
63 * </ul>
64 *
65 * <!-- END SNIPPET: params -->
66 *
67 * <!-- START SNIPPET: example1description -->
68 *
69 * <p>The following example retrieves the value of the getDays() method of the current object on the value stack and
70 * uses it to iterate over. The <s:property/> tag prints out the current value of the iterator.</p>
71 *
72 * <!-- END SNIPPET: example1description -->
73 *
74 * <pre>
75 * <!-- START SNIPPET: example1code -->
76 * <s:iterator value="days">
77 * <p>day is: <s:property/></p>
78 * </s:iterator>
79 * <!-- END SNIPPET: example1code -->
80 * </pre>
81 *
82 *
83 * <!-- START SNIPPET: example2description -->
84 *
85 * <p>The following example uses a {@link Bean} tag and places it into the ActionContext. The iterator tag will retrieve
86 * that object from the ActionContext and then calls its getDays() method as above. The status attribute is also used to
87 * create an {@link IteratorStatus} object, which in this example, its odd() method is used to alternate row
88 * colours:</p>
89 *
90 * <!-- END SNIPPET: example2description -->
91 *
92 *
93 * <pre>
94 * <!-- START SNIPPET: example2code -->
95 *
96 * <s:bean name="org.apache.struts2.example.IteratorExample" var="it">
97 * <s:param name="day" value="'foo'"/>
98 * <s:param name="day" value="'bar'"/>
99 * </s:bean>
100 * <p/>
101 * <table border="0" cellspacing="0" cellpadding="1">
102 * <tr>
103 * <th>Days of the week</th>
104 * </tr>
105 * <p/>
106 * <s:iterator value="#it.days" status="rowstatus">
107 * <tr>
108 * <s:if test="#rowstatus.odd == true">
109 * <td style="background: grey"><s:property/></td>
110 * </s:if>
111 * <s:else>
112 * <td><s:property/></td>
113 * </s:else>
114 * </tr>
115 * </s:iterator>
116 * </table>
117 *
118 * <!-- END SNIPPET: example2code -->
119 * </pre>
120 *
121 * <!--START SNIPPET: example3description -->
122 *
123 * <p> The next example will further demonstrate the use of the status attribute, using a DAO obtained from the action
124 * class through OGNL, iterating over groups and their users (in a security context). The last() method indicates if the
125 * current object is the last available in the iteration, and if not, we need to separate the users using a comma: </p>
126 *
127 * <!-- END SNIPPET: example3description -->
128 *
129 * <pre>
130 * <!-- START SNIPPET: example3code -->
131 *
132 * <s:iterator value="groupDao.groups" status="groupStatus">
133 * <tr class="<s:if test="#groupStatus.odd == true ">odd</s:if><s:else>even</s:else>">
134 * <td><s:property value="name" /></td>
135 * <td><s:property value="description" /></td>
136 * <td>
137 * <s:iterator value="users" status="userStatus">
138 * <s:property value="fullName" /><s:if test="!#userStatus.last">,</s:if>
139 * </s:iterator>
140 * </td>
141 * </tr>
142 * </s:iterator>
143 *
144 * <!-- END SNIPPET: example3code -->
145 * </pre>
146 * <p>
147 *
148 * <!-- START SNIPPET: example4description -->
149 *
150 * </p> The next example iterates over a an action collection and passes every iterator value to another action. The
151 * trick here lies in the use of the '[0]' operator. It takes the current iterator value and passes it on to the edit
152 * action. Using the '[0]' operator has the same effect as using <s:property />. (The latter, however, does not
153 * work from inside the param tag). </p>
154 *
155 * <!-- END SNIPPET: example4description -->
156 *
157 * <pre>
158 * <!-- START SNIPPET: example4code -->
159 *
160 * <s:action name="entries" var="entries"/>
161 * <s:iterator value="#entries.entries" >
162 * <s:property value="name" />
163 * <s:property />
164 * <s:push value="...">
165 * <s:action name="edit" var="edit" >
166 * <s:param name="entry" value="[0]" />
167 * </s:action>
168 * </push>
169 * </s:iterator>
170 *
171 * <!-- END SNIPPET: example4code -->
172 * </pre>
173 *
174 * <!-- START SNIPPET: example5description -->
175 *
176 * </p>A loop that iterates 5 times
177 *
178 * <!-- END SNIPPET: example5description -->
179 *
180 * <pre>
181 * <!-- START SNIPPET: example5code -->
182 *
183 * <s:iterator var="counter" begin="1" end="5" >
184 * <!-- current iteration value (1, ... 5) -->
185 * <s:property value="top" />
186 * </s:iterator>
187 *
188 * <!-- END SNIPPET: example5code -->
189 * </pre>
190 *
191 * <!-- START SNIPPET: example6description -->
192 *
193 * </p>Another way to create a simple loop, similar to JSTL's
194 * <c:forEach begin="..." end="..." ...> is to use some
195 * OGNL magic, which provides some under-the-covers magic to
196 * make 0-n loops trivial. This example also loops five times.
197 *
198 * <!-- END SNIPPET: example6description -->
199 *
200 * <pre>
201 * <!-- START SNIPPET: example6code -->
202 *
203 * <s:iterator status="stat" value="(5).{ #this }" >
204 * <s:property value="#stat.count" /> <!-- Note that "count" is 1-based, "index" is 0-based. -->
205 * </s:iterator>
206 *
207 * <!-- END SNIPPET: example6code -->
208 * </pre>
209 *
210 * <!-- START SNIPPET: example7description -->
211 *
212 * </p>A loop that iterates over a partial list
213 *
214 * <!-- END SNIPPET: example7description -->
215 *
216 * <pre>
217 * <!-- START SNIPPET: example7code -->
218 *
219 * <s:iterator value="{1,2,3,4,5}" begin="2" end="4" >
220 * <!-- current iteration value (2,3,4) -->
221 * <s:property value="top" />
222 * </s:iterator>
223 *
224 * <!-- END SNIPPET: example7code -->
225 * </pre>
226 */
227 @StrutsTag(name="iterator", tldTagClass="org.apache.struts2.views.jsp.IteratorTag", description="Iterate over a iterable value")
228 public class IteratorComponent extends ContextBean {
229 private static final Logger LOG = LoggerFactory.getLogger(IteratorComponent.class);
230
231 protected Iterator iterator;
232 protected IteratorStatus status;
233 protected Object oldStatus;
234 protected IteratorStatus.StatusState statusState;
235 protected String statusAttr;
236 protected String value;
237 protected String beginStr;
238 protected Integer begin;
239 protected String endStr;
240 protected Integer end;
241 protected String stepStr;
242 protected Integer step;
243
244 public IteratorComponent(ValueStack stack) {
245 super(stack);
246 }
247
248 public boolean start(Writer writer) {
249
250 if (statusAttr != null) {
251 statusState = new IteratorStatus.StatusState();
252 status = new IteratorStatus(statusState);
253 }
254
255 if (beginStr != null)
256 begin = (Integer) findValue(beginStr, Integer.class);
257
258 if (endStr != null)
259 end = (Integer) findValue(endStr, Integer.class);
260
261 if (stepStr != null)
262 step = (Integer) findValue(stepStr, Integer.class);
263
264 ValueStack stack = getStack();
265
266 if (value == null && begin == null && end == null) {
267 value = "top";
268 }
269 Object iteratorTarget = findValue(value);
270 iterator = MakeIterator.convert(iteratorTarget);
271
272 if (begin != null) {
273
274 if (step == null)
275 step = 1;
276
277 if (iterator == null) {
278
279 iterator = new CounterIterator(begin, end, step, null);
280 } else if (iterator != null) {
281
282 if (iteratorTarget.getClass().isArray()) {
283 Object[] values = (Object[]) iteratorTarget;
284 if (end == null)
285 end = step > 0 ? values.length - 1 : 0;
286 iterator = new CounterIterator(begin, end, step, Arrays.asList(values));
287 } else if (iteratorTarget instanceof List) {
288 List values = (List) iteratorTarget;
289 if (end == null)
290 end = step > 0 ? values.size() - 1 : 0;
291 iterator = new CounterIterator(begin, end, step, values);
292 } else {
293
294 LOG.error("Incorrect use of the iterator tag. When 'begin' is set, 'value' must be" +
295 " an Array or a List, or not set at all. 'begin', 'end' and 'step' will be ignored");
296 }
297 }
298 }
299
300
301 if ((iterator != null) && iterator.hasNext()) {
302 Object currentValue = iterator.next();
303 stack.push(currentValue);
304
305 String var = getVar();
306
307 if ((var != null) && (currentValue != null)) {
308
309
310 putInContext(currentValue);
311 }
312
313
314 if (statusAttr != null) {
315 statusState.setLast(!iterator.hasNext());
316 oldStatus = stack.getContext().get(statusAttr);
317 stack.getContext().put(statusAttr, status);
318 }
319
320 return true;
321 } else {
322 super.end(writer, "");
323 return false;
324 }
325 }
326
327 public boolean end(Writer writer, String body) {
328 ValueStack stack = getStack();
329 if (iterator != null) {
330 stack.pop();
331 }
332
333 if (iterator!=null && iterator.hasNext()) {
334 Object currentValue = iterator.next();
335 stack.push(currentValue);
336
337 putInContext(currentValue);
338
339
340 if (status != null) {
341 statusState.next();
342 statusState.setLast(!iterator.hasNext());
343 }
344
345 return true;
346 } else {
347
348 if (status != null) {
349 if (oldStatus == null) {
350 stack.getContext().put(statusAttr, null);
351 } else {
352 stack.getContext().put(statusAttr, oldStatus);
353 }
354 }
355 super.end(writer, "");
356 return false;
357 }
358 }
359
360 class CounterIterator implements Iterator<Object> {
361 private int step;
362 private int end;
363 private int currentIndex;
364 private List<Object> values;
365
366 CounterIterator(Integer begin, Integer end, Integer step, List<Object> values) {
367 this.end = end;
368 if (step != null)
369 this.step = step;
370 this.currentIndex = begin - this.step;
371 this.values = values;
372 }
373
374 public boolean hasNext() {
375 int next = peekNextIndex();
376 return step > 0 ? next <= end : next >= end;
377 }
378
379 public Object next() {
380 if (hasNext()) {
381 int nextIndex = peekNextIndex();
382 currentIndex += step;
383 return value != null ? values.get(nextIndex) : nextIndex;
384 } else {
385 throw new IndexOutOfBoundsException("Index " + ( currentIndex + step) + " must be less than or equal to " + end);
386 }
387 }
388
389 private int peekNextIndex() {
390 return currentIndex + step;
391 }
392
393 public void remove() {
394 throw new UnsupportedOperationException("Values cannot be removed from this iterator");
395 }
396 }
397
398 @StrutsTagAttribute(description="If specified, an instanceof IteratorStatus will be pushed into stack upon each iteration",
399 type="Boolean", defaultValue="false")
400 public void setStatus(String status) {
401 this.statusAttr = status;
402 }
403
404 @StrutsTagAttribute(description="the iteratable source to iterate over, else an the object itself will be put into a newly created List")
405 public void setValue(String value) {
406 this.value = value;
407 }
408
409 @StrutsTagAttribute(description="if specified the iteration will start on that index", type="Integer", defaultValue="0")
410 public void setBegin(String begin) {
411 this.beginStr = begin;
412 }
413
414 @StrutsTagAttribute(description="if specified the iteration will end on that index(inclusive)", type="Integer",
415 defaultValue="Size of the 'values' List or array, or 0 if 'step' is negative")
416 public void setEnd(String end) {
417 this.endStr = end;
418 }
419
420 @StrutsTagAttribute(description="if specified the iteration index will be increased by this value on each iteration. It can be a negative " +
421 "value, in which case 'begin' must be greater than 'end'", type="Integer", defaultValue="1")
422 public void setStep(String step) {
423 this.stepStr = step;
424 }
425 }