View Javadoc

1   /*
2    * $Id: IteratorComponent.java 741183 2009-02-05 17:12:08Z musachy $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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 &lt;s:property/&gt; tag prints out the current value of the iterator.</p>
71   *
72   * <!-- END SNIPPET: example1description -->
73   *
74   * <pre>
75   * <!-- START SNIPPET: example1code -->
76   * &lt;s:iterator value="days"&gt;
77   *   &lt;p&gt;day is: &lt;s:property/&gt;&lt;/p&gt;
78   * &lt;/s:iterator&gt;
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   * &lt;s:bean name="org.apache.struts2.example.IteratorExample" var="it"&gt;
97   *   &lt;s:param name="day" value="'foo'"/&gt;
98   *   &lt;s:param name="day" value="'bar'"/&gt;
99   * &lt;/s:bean&gt;
100  * <p/>
101  * &lt;table border="0" cellspacing="0" cellpadding="1"&gt;
102  * &lt;tr&gt;
103  *   &lt;th&gt;Days of the week&lt;/th&gt;
104  * &lt;/tr&gt;
105  * <p/>
106  * &lt;s:iterator value="#it.days" status="rowstatus"&gt;
107  *   &lt;tr&gt;
108  *     &lt;s:if test="#rowstatus.odd == true"&gt;
109  *       &lt;td style="background: grey"&gt;&lt;s:property/&gt;&lt;/td&gt;
110  *     &lt;/s:if&gt;
111  *     &lt;s:else&gt;
112  *       &lt;td&gt;&lt;s:property/&gt;&lt;/td&gt;
113  *     &lt;/s:else&gt;
114  *   &lt;/tr&gt;
115  * &lt;/s:iterator&gt;
116  * &lt;/table&gt;
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  *  &lt;s:iterator value="groupDao.groups" status="groupStatus"&gt;
133  *      &lt;tr class="&lt;s:if test="#groupStatus.odd == true "&gt;odd&lt;/s:if&gt;&lt;s:else&gt;even&lt;/s:else&gt;"&gt;
134  *          &lt;td&gt;&lt;s:property value="name" /&gt;&lt;/td&gt;
135  *          &lt;td&gt;&lt;s:property value="description" /&gt;&lt;/td&gt;
136  *          &lt;td&gt;
137  *              &lt;s:iterator value="users" status="userStatus"&gt;
138  *                  &lt;s:property value="fullName" /&gt;&lt;s:if test="!#userStatus.last"&gt;,&lt;/s:if&gt;
139  *              &lt;/s:iterator&gt;
140  *          &lt;/td&gt;
141  *      &lt;/tr&gt;
142  *  &lt;/s:iterator&gt;
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 &lt;s:property /&gt;. (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  *      &lt;s:action name="entries" var="entries"/&gt;
161  *      &lt;s:iterator value="#entries.entries" &gt;
162  *          &lt;s:property value="name" /&gt;
163  *          &lt;s:property /&gt;
164  *          &lt;s:push value="..."&gt;
165  *              &lt;s:action name="edit" var="edit" &gt;
166  *                  &lt;s:param name="entry" value="[0]" /&gt;
167  *              &lt;/s:action&gt;
168  *          &lt;/push&gt;
169  *      &lt;/s:iterator&gt;
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  * &lt;s:iterator var="counter" begin="1" end="5" &gt;
184  *    &lt;!-- current iteration value (1, ... 5) --&gt;
185  *    &lt;s:property value="top" /&gt;
186  * &lt;/s:iterator&gt;
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  * &lt;c:forEach begin="..." end="..." ...&gt; 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  * &lt;s:iterator status="stat" value="(5).{ #this }" &gt;
204  *    &lt;s:property value="#stat.count" /&gt; &lt;!-- Note that "count" is 1-based, "index" is 0-based. --&gt;
205  * &lt;/s:iterator&gt;
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  * &lt;s:iterator value="{1,2,3,4,5}" begin="2" end="4" &gt;
220  *    &lt;!-- current iteration value (2,3,4) --&gt;
221  *    &lt;s:property value="top" /&gt;
222  * &lt;/s:iterator&gt;
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         //Create an iterator status if the status attribute was set.
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             //default step to 1
274             if (step == null)
275                 step = 1;
276 
277             if (iterator == null) {
278                 //classic for loop from 'begin' to 'end'
279                 iterator = new CounterIterator(begin, end, step, null);
280             } else if (iterator != null) {
281                 //only arrays and lists are supported
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                     //so the iterator is not based on an array or a list
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         // get the first
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                 //pageContext.setAttribute(id, currentValue);
309                 //pageContext.setAttribute(id, currentValue, PageContext.REQUEST_SCOPE);
310                 putInContext(currentValue);
311             }
312 
313             // Status object
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             // Update status
340             if (status != null) {
341                 statusState.next(); // Increase counter
342                 statusState.setLast(!iterator.hasNext());
343             }
344 
345             return true;
346         } else {
347             // Reset status object in case someone else uses the same name in another iterator tag instance
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 }