Author: musachy Date: Thu Feb 5 16:55:14 2009 New Revision: 741179 URL: http://svn.apache.org/viewvc?rev=741179&view=rev Log: WW-2984 Improve iterator tag to support "begin", "end" and "step" attributes
Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/IteratorComponent.java struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/jsp/IteratorTag.java struts/struts2/trunk/core/src/site/resources/tags/iterator.html struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/jsp/IteratorTagTest.java Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/IteratorComponent.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/IteratorComponent.java?rev=741179&r1=741178&r2=741179&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/IteratorComponent.java (original) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/IteratorComponent.java Thu Feb 5 16:55:14 2009 @@ -23,6 +23,8 @@ import java.io.Writer; import java.util.Iterator; +import java.util.List; +import java.util.Arrays; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; @@ -30,6 +32,8 @@ import org.apache.struts2.views.jsp.IteratorStatus; import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; /** * <!-- START SNIPPET: javadoc --> @@ -49,6 +53,13 @@ * <li>id (String) - if specified the current iteration object will be place with this id in Struts stack's context * scope</li> * + * <li>begin (Integer) - if specified the iteration will start on that index</li> + * + * <li>end (Integer) - if specified the iteration will end on that index(inclusive)</li> + * + * <li>step (Integer) - if specified the iteration index will be increased by this value on each iteration. It can be a negative + * value, in which case 'begin' must be greater than 'end'</li> + * * </ul> * * <!-- END SNIPPET: params --> @@ -162,19 +173,14 @@ * * <!-- START SNIPPET: example5description --> * - * </p>To simulate a simple loop with iterator tag, the following could be done. - * It does the loop 5 times. + * </p>A loop that iterates 5 times * * <!-- END SNIPPET: example5description --> * * <pre> * <!-- START SNIPPET: example5code --> * - * <s:iterator status="stat" value="{1,2,3,4,5}" > - * <!-- grab the index (start with 0 ... ) --> - * <s:property value="#stat.index" /> - * - * <!-- grab the top of the stack which should be the --> + * <s:iterator var="counter" begin="1" end="5" > * <!-- current iteration value (1, ... 5) --> * <s:property value="top" /> * </s:iterator> @@ -201,15 +207,39 @@ * <!-- END SNIPPET: example6code --> * </pre> * + * <!-- START SNIPPET: example7description --> + * + * </p>A loop that iterates over a partial list + * + * <!-- END SNIPPET: example7description --> + * + * <pre> + * <!-- START SNIPPET: example7code --> + * + * <s:iterator value="{1,2,3,4,5}" begin="2" end="4" > + * <!-- current iteration value (2,3,4) --> + * <s:property value="top" /> + * </s:iterator> + * + * <!-- END SNIPPET: example7code --> + * </pre> */ @StrutsTag(name="iterator", tldTagClass="org.apache.struts2.views.jsp.IteratorTag", description="Iterate over a iterable value") public class IteratorComponent extends ContextBean { + private static final Logger LOG = LoggerFactory.getLogger(IteratorComponent.class); + protected Iterator iterator; protected IteratorStatus status; protected Object oldStatus; protected IteratorStatus.StatusState statusState; protected String statusAttr; protected String value; + protected String beginStr; + protected Integer begin; + protected String endStr; + protected Integer end; + protected String stepStr; + protected Integer step; public IteratorComponent(ValueStack stack) { super(stack); @@ -222,12 +252,50 @@ status = new IteratorStatus(statusState); } + if (beginStr != null) + begin = (Integer) findValue(beginStr, Integer.class); + + if (endStr != null) + end = (Integer) findValue(endStr, Integer.class); + + if (stepStr != null) + step = (Integer) findValue(stepStr, Integer.class); + ValueStack stack = getStack(); - if (value == null) { + if (value == null && begin == null && end == null) { value = "top"; } - iterator = MakeIterator.convert(findValue(value)); + Object iteratorTarget = findValue(value); + iterator = MakeIterator.convert(iteratorTarget); + + if (begin != null) { + //default step to 1 + if (step == null) + step = 1; + + if (iterator == null) { + //classic for loop from 'begin' to 'end' + iterator = new CounterIterator(begin, end, step, null); + } else if (iterator != null) { + //only arrays and lists are supported + if (iteratorTarget.getClass().isArray()) { + Object[] values = (Object[]) iteratorTarget; + if (end == null) + end = step > 0 ? values.length - 1 : 0; + iterator = new CounterIterator(begin, end, step, Arrays.asList(values)); + } else if (iteratorTarget instanceof List) { + List values = (List) iteratorTarget; + if (end == null) + end = step > 0 ? values.size() - 1 : 0; + iterator = new CounterIterator(begin, end, step, values); + } else { + //so the iterator is not based on an array or a list + LOG.error("Incorrect use of the iterator tag. When 'begin' is set, 'value' must be" + + " an Array or a List, or not set at all. 'begin', 'end' and 'step' will be ignored"); + } + } + } // get the first if ((iterator != null) && iterator.hasNext()) { @@ -289,6 +357,44 @@ } } + class CounterIterator implements Iterator<Object> { + private int step; + private int end; + private int currentIndex; + private List<Object> values; + + CounterIterator(Integer begin, Integer end, Integer step, List<Object> values) { + this.end = end; + if (step != null) + this.step = step; + this.currentIndex = begin - this.step; + this.values = values; + } + + public boolean hasNext() { + int next = peekNextIndex(); + return step > 0 ? next <= end : next >= end; + } + + public Object next() { + if (hasNext()) { + int nextIndex = peekNextIndex(); + currentIndex += step; + return value != null ? values.get(nextIndex) : nextIndex; + } else { + throw new IndexOutOfBoundsException("Index " + ( currentIndex + step) + " must be less than or equal to " + end); + } + } + + private int peekNextIndex() { + return currentIndex + step; + } + + public void remove() { + throw new UnsupportedOperationException("Values cannot be removed from this iterator"); + } + } + @StrutsTagAttribute(description="If specified, an instanceof IteratorStatus will be pushed into stack upon each iteration", type="Boolean", defaultValue="false") public void setStatus(String status) { @@ -300,4 +406,19 @@ this.value = value; } + @StrutsTagAttribute(description="if specified the iteration will start on that index", type="Integer", defaultValue="0") + public void setBegin(String begin) { + this.beginStr = begin; + } + + @StrutsTagAttribute(description="if specified the iteration will end on that index(inclusive)", type="Integer", defaultValue="Size of the 'values' collection or array") + public void setEnd(String end) { + this.endStr = end; + } + + @StrutsTagAttribute(description="if specified the iteration index will be increased by this value on each iteration. It can be a negative " + + "value, in which case 'begin' must be greater than 'end'", type="Integer", defaultValue="1") + public void setStep(String step) { + this.stepStr = step; + } } Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/jsp/IteratorTag.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/jsp/IteratorTag.java?rev=741179&r1=741178&r2=741179&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/jsp/IteratorTag.java (original) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/jsp/IteratorTag.java Thu Feb 5 16:55:14 2009 @@ -39,6 +39,9 @@ protected String statusAttr; protected String value; + protected String begin; + protected String end; + protected String step; public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new IteratorComponent(stack); @@ -50,6 +53,9 @@ IteratorComponent tag = (IteratorComponent) getComponent(); tag.setStatus(statusAttr); tag.setValue(value); + tag.setBegin(begin); + tag.setEnd(end); + tag.setStep(step); } public void setStatus(String status) { @@ -60,6 +66,18 @@ this.value = value; } + public void setBegin(String begin) { + this.begin = begin; + } + + public void setEnd(String end) { + this.end = end; + } + + public void setStep(String step) { + this.step = step; + } + public int doEndTag() throws JspException { component = null; return EVAL_PAGE; Modified: struts/struts2/trunk/core/src/site/resources/tags/iterator.html URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/site/resources/tags/iterator.html?rev=741179&r1=741178&r2=741179&view=diff ============================================================================== --- struts/struts2/trunk/core/src/site/resources/tags/iterator.html (original) +++ struts/struts2/trunk/core/src/site/resources/tags/iterator.html Thu Feb 5 16:55:14 2009 @@ -34,6 +34,22 @@ <th align="left" valign="top"><h4>Description</h4></th> </tr> <tr> + <td align="left" valign="top">begin</td> + <td align="left" valign="top">false</td> + <td align="left" valign="top">0</td> + <td align="left" valign="top">false</td> + <td align="left" valign="top">Integer</td> + <td align="left" valign="top">if specified the iteration will start on that index</td> + </tr> + <tr> + <td align="left" valign="top">end</td> + <td align="left" valign="top">false</td> + <td align="left" valign="top">Size of the 'values' collection or array</td> + <td align="left" valign="top">false</td> + <td align="left" valign="top">Integer</td> + <td align="left" valign="top">if specified the iteration will end on that index(inclusive)</td> + </tr> + <tr> <td align="left" valign="top">id</td> <td align="left" valign="top">false</td> <td align="left" valign="top"></td> @@ -50,6 +66,14 @@ <td align="left" valign="top">If specified, an instanceof IteratorStatus will be pushed into stack upon each iteration</td> </tr> <tr> + <td align="left" valign="top">step</td> + <td align="left" valign="top">false</td> + <td align="left" valign="top">1</td> + <td align="left" valign="top">false</td> + <td align="left" valign="top">Integer</td> + <td align="left" valign="top">if specified the iteration index will be increased by this value on each iteration. It can be a negative value, in which case 'begin' must be greater than 'end'</td> + </tr> + <tr> <td align="left" valign="top">value</td> <td align="left" valign="top">false</td> <td align="left" valign="top"></td> Modified: struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/jsp/IteratorTagTest.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/jsp/IteratorTagTest.java?rev=741179&r1=741178&r2=741179&view=diff ============================================================================== --- struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/jsp/IteratorTagTest.java (original) +++ struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/jsp/IteratorTagTest.java Thu Feb 5 16:55:14 2009 @@ -21,18 +21,19 @@ package org.apache.struts2.views.jsp; +import com.mockobjects.servlet.MockBodyContent; +import com.mockobjects.servlet.MockJspWriter; +import org.apache.commons.collections.ListUtils; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.TagSupport; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.TagSupport; - -import com.mockobjects.servlet.MockBodyContent; -import com.mockobjects.servlet.MockJspWriter; - /** * Test Case for Iterator Tag @@ -352,6 +353,294 @@ validateSkipBody(); } + public void testCounter() throws JspException { + tag.setBegin("0"); + tag.setEnd("5"); + validateCounter(new Integer[]{0, 1, 2, 3, 4, 5}); + } + + public void testCounterWithStackValues() throws JspException { + stack.getContext().put("begin", 0); + stack.getContext().put("end", 5); + tag.setBegin("%{#begin}"); + tag.setEnd("%{#end}"); + validateCounter(new Integer[]{0, 1, 2, 3, 4, 5}); + } + + public void testCounterWithList() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setBegin("0"); + tag.setEnd("2"); + validateCounter(new String[]{"a", "b", "c"}); + } + + public void testCounterWithArray() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + foo.setArray(new String[]{"a", "b", "c", "d"}); + + stack.push(foo); + + tag.setValue("array"); + + tag.setBegin("0"); + tag.setEnd("2"); + validateCounter(new String[]{"a", "b", "c"}); + } + + + public void testCounterWithListNoEnd() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setBegin("0"); + validateCounter(new String[]{"a", "b", "c", "d"}); + } + + public void testCounterWithArrayNoEnd() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + foo.setArray(new String[]{"a", "b", "c", "d"}); + + stack.push(foo); + + tag.setValue("array"); + + tag.setBegin("0"); + validateCounter(new String[]{"a", "b", "c", "d"}); + } + + public void testCounterWithList2() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setBegin("1"); + tag.setEnd("2"); + validateCounter(new String[]{"b", "c"}); + } + + public void testCounterWithArray2() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + foo.setArray(new String[]{"a", "b", "c", "d"}); + + stack.push(foo); + + tag.setValue("array"); + + tag.setBegin("1"); + tag.setEnd("2"); + validateCounter(new String[]{"b", "c"}); + } + + public void testCounterWithListNoEnd2() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setBegin("2"); + validateCounter(new String[]{"c", "d"}); + } + + public void testCounterWithArrayNoEnd2() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + foo.setArray(new String[]{"a", "b", "c", "d"}); + + stack.push(foo); + + tag.setValue("array"); + + tag.setBegin("2"); + validateCounter(new String[]{"c", "d"}); + } + + public void testCounter2() throws JspException { + tag.setBegin("2"); + tag.setEnd("5"); + validateCounter(new Integer[]{2, 3, 4, 5}); + } + + public void testCounterWithStep() throws JspException { + tag.setBegin("0"); + tag.setEnd("5"); + tag.setStep("2"); + validateCounter(new Integer[]{0, 2, 4}); + } + + public void testCounterWithListAndStep() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setStep("2"); + tag.setBegin("0"); + tag.setEnd("3"); + + validateCounter(new String[]{"a", "c"}); + } + + public void testCounterWithArrayAndStep() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + foo.setArray(new String[]{"a", "b", "c", "d"}); + + stack.push(foo); + + tag.setValue("array"); + + tag.setStep("2"); + tag.setBegin("0"); + tag.setEnd("3"); + + validateCounter(new String[]{"a", "c"}); + } + + public void testCounterWithListAndStepNoEnd() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setStep("2"); + tag.setBegin("0"); + + validateCounter(new String[]{"a", "c"}); + } + + public void testCounterWithArrayAndStepNoEnd() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + foo.setArray(new String[]{"a", "b", "c", "d"}); + + stack.push(foo); + + tag.setValue("array"); + + tag.setStep("2"); + tag.setBegin("0"); + + validateCounter(new String[]{"a", "c"}); + } + + public void testCounterWithNegativeStep() throws JspException { + tag.setBegin("8"); + tag.setEnd("5"); + tag.setStep("-1"); + validateCounter(new Integer[]{8, 7, 6, 5}); + } + + public void testCounterWithListAndNegativeStep() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setStep("-1"); + tag.setBegin("3"); + tag.setEnd("1"); + + validateCounter(new String[]{"d", "c", "b"}); + } + + public void testCounterWithArrayAndNegativeStep() throws JspException { + Foo foo = new Foo(); + ArrayList list = new ArrayList(); + list.add("a"); + list.add("b"); + list.add("c"); + list.add("d"); + foo.setList(list); + + stack.push(foo); + + tag.setValue("list"); + + tag.setStep("-1"); + tag.setBegin("3"); + tag.setEnd("1"); + + validateCounter(new String[]{"d", "c", "b"}); + } + + protected void validateCounter(Object[] expectedValues) throws JspException { + List values = new ArrayList(); + try { + int result = tag.doStartTag(); + assertEquals(result, TagSupport.EVAL_BODY_INCLUDE); + values.add(stack.getRoot().peek()); + } catch (JspException e) { + e.printStackTrace(); + fail(); + } + + while (tag.doAfterBody() == TagSupport.EVAL_BODY_AGAIN) { + values.add(stack.getRoot().peek()); + } + + assertEquals(expectedValues.length, values.size()); + ListUtils.isEqualList(Arrays.asList(expectedValues), values); + } + protected void setUp() throws Exception { super.setUp();