001: /*
002: * Copyright 2002,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.jelly.tags.core;
018:
019: import java.util.Iterator;
020:
021: import javax.servlet.jsp.jstl.core.LoopTagStatus;
022:
023: import org.apache.commons.jelly.JellyTagException;
024: import org.apache.commons.jelly.MissingAttributeException;
025: import org.apache.commons.jelly.TagSupport;
026: import org.apache.commons.jelly.XMLOutput;
027: import org.apache.commons.jelly.expression.Expression;
028: import org.apache.commons.jelly.impl.BreakException;
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: /**
033: * Iterates over a collection, iterator or an array of objects.
034: * Uses the same syntax as the <a href="http://java.sun.com/products/jsp/jstl/">JSTL</a>
035: * <code>forEach</code> tag does.
036: *
037: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
038: * @version $Revision: 155420 $
039: */
040: public class ForEachTag extends TagSupport {
041:
042: /** The Log to which logging calls will be made. */
043: private static final Log log = LogFactory.getLog(ForEachTag.class);
044:
045: /** Holds the variable name to export for the item being iterated over. */
046: private Expression items;
047:
048: /**
049: * If specified then the current item iterated through will be defined
050: * as the given variable name.
051: */
052: private String var;
053:
054: /**
055: * If specified then the current index counter will be defined
056: * as the given variable name.
057: */
058: private String indexVar;
059:
060: /** variable to hold loop status */
061: private String statusVar;
062:
063: /** The starting index value */
064: private int begin;
065:
066: /** The ending index value */
067: private int end = Integer.MAX_VALUE;
068:
069: /** The index increment step */
070: private int step = 1;
071:
072: /** The iteration index */
073: private int index;
074:
075: public ForEachTag() {
076: }
077:
078: // Tag interface
079:
080: //-------------------------------------------------------------------------
081: public void doTag(XMLOutput output)
082: throws MissingAttributeException, JellyTagException {
083:
084: if (log.isDebugEnabled()) {
085: log.debug("running with items: " + items);
086: }
087:
088: try {
089: if (items != null) {
090: Iterator iter = items.evaluateAsIterator(context);
091:
092: if (log.isDebugEnabled()) {
093: log.debug("Iterating through: " + iter);
094: }
095:
096: // ignore the first items of the iterator
097: for (index = 0; index < begin && iter.hasNext(); index++) {
098: iter.next();
099: }
100:
101: // set up the status
102: LoopStatus status = null;
103: if (statusVar != null) {
104: // set up statii as required by JSTL
105: Integer statusBegin = (begin == 0) ? null
106: : new Integer(begin);
107: Integer statusEnd = (end == Integer.MAX_VALUE) ? null
108: : new Integer(end);
109: Integer statusStep = (step == 1) ? null
110: : new Integer(step);
111: status = new LoopStatus(statusBegin, statusEnd,
112: statusStep);
113: context.setVariable(statusVar, status);
114: }
115:
116: boolean firstTime = true;
117: int count = 0;
118: while (iter.hasNext() && index <= end) {
119: Object value = iter.next();
120: if (var != null) {
121: context.setVariable(var, value);
122: }
123: if (indexVar != null) {
124: context.setVariable(indexVar,
125: new Integer(index));
126: }
127: // set the status var up
128: if (statusVar != null) {
129: count++;
130: status.setCount(count);
131: status.setCurrent(value);
132: status.setFirst(firstTime);
133: status.setIndex(index);
134: // set first time up for the next iteration.
135: if (firstTime) {
136: firstTime = !firstTime;
137: }
138: }
139: // now we need to work out the next index for status isLast
140: // and also advance the iterator and index for the loop.
141: boolean finished = false;
142: index++;
143: for (int i = 1; i < step && !finished; i++, index++) {
144: if (!iter.hasNext()) {
145: finished = true;
146: } else {
147: iter.next();
148: }
149: }
150:
151: if (statusVar != null) {
152: status.setLast(finished || !iter.hasNext()
153: || index > end);
154: }
155: invokeBody(output);
156:
157: }
158: } else {
159: if (end == Integer.MAX_VALUE && begin == 0) {
160: throw new MissingAttributeException("items");
161: } else {
162: String varName = var;
163: if (varName == null) {
164: varName = indexVar;
165: }
166: // set up the status
167: LoopStatus status = null;
168: if (statusVar != null) {
169: // set up statii as required by JSTL
170: Integer statusBegin = new Integer(begin);
171: Integer statusEnd = new Integer(end);
172: Integer statusStep = new Integer(step);
173: status = new LoopStatus(statusBegin, statusEnd,
174: statusStep);
175: context.setVariable(statusVar, status);
176: }
177:
178: int count = 0;
179: for (index = begin; index <= end; index += step) {
180:
181: Object value = new Integer(index);
182: if (varName != null) {
183: context.setVariable(varName, value);
184: }
185: // set the status var up
186: if (status != null) {
187: count++;
188: status.setIndex(index);
189: status.setCount(count);
190: status.setCurrent(value);
191: status.setFirst(index == begin);
192: status.setLast(index > end - step);
193: }
194: invokeBody(output);
195: }
196: }
197: }
198: } catch (BreakException e) {
199: if (log.isDebugEnabled()) {
200: log.debug("loop terminated by break: " + e, e);
201: }
202: }
203: }
204:
205: // Properties
206: //-------------------------------------------------------------------------
207:
208: /**
209: * Sets the expression used to iterate over.
210: * This expression could resolve to an Iterator, Collection, Map, Array,
211: * Enumeration or comma separated String.
212: */
213: public void setItems(Expression items) {
214: this .items = items;
215: }
216:
217: /** Sets the variable name to export for the item being iterated over
218: */
219: public void setVar(String var) {
220: this .var = var;
221: }
222:
223: /** Sets the variable name to export the current index counter to
224: */
225: public void setIndexVar(String indexVar) {
226: this .indexVar = indexVar;
227: }
228:
229: /** Sets the starting index value
230: */
231: public void setBegin(int begin) {
232: this .begin = begin;
233: }
234:
235: /** Sets the ending index value
236: */
237: public void setEnd(int end) {
238: this .end = end;
239: }
240:
241: /** Sets the index increment step
242: */
243: public void setStep(int step) {
244: this .step = step;
245: }
246:
247: /**
248: * Sets the variable name to export the current status to.
249: * The status is an implementation of the JSTL LoopTagStatus interface that provides
250: * the following bean properties:
251: * <ul>
252: * <li>current - the current value of the loop items being iterated</li>
253: * <li>index - the current index of the items being iterated</li>
254: * <li>first - true if this is the first iteration, false otherwise</li>
255: * <li>last - true if this is the last iteration, false otherwise</li>
256: * <li>begin - the starting index of the loop</li>
257: * <li>step - the stepping value of the loop</li>
258: * <li>end - the end index of the loop</li>
259: * </ul>
260: */
261: public void setVarStatus(String var) {
262: this .statusVar = var;
263: }
264:
265: /**
266: * Holds the status of the loop.
267: */
268: public static final class LoopStatus implements LoopTagStatus {
269: private Integer begin;
270: private int count;
271: private Object current;
272: private Integer end;
273: private int index;
274: private Integer step;
275: private boolean first;
276: private boolean last;
277:
278: public LoopStatus(Integer begin, Integer end, Integer step) {
279: this .begin = begin;
280: this .end = end;
281: this .step = step;
282: }
283:
284: /**
285: * @return Returns the begin.
286: */
287: public Integer getBegin() {
288: return begin;
289: }
290:
291: /**
292: * @return Returns the count.
293: */
294: public int getCount() {
295: return count;
296: }
297:
298: /**
299: * @return Returns the current.
300: */
301: public Object getCurrent() {
302: return current;
303: }
304:
305: /**
306: * @return Returns the end.
307: */
308: public Integer getEnd() {
309: return end;
310: }
311:
312: /**
313: * @return Returns the first.
314: */
315: public boolean isFirst() {
316: return first;
317: }
318:
319: /**
320: * @return Returns the index.
321: */
322: public int getIndex() {
323: return index;
324: }
325:
326: /**
327: * @return Returns the last.
328: */
329: public boolean isLast() {
330: return last;
331: }
332:
333: /**
334: * @return Returns the step.
335: */
336: public Integer getStep() {
337: return step;
338: }
339:
340: /**
341: * @param count The count to set.
342: */
343: public void setCount(int count) {
344: this .count = count;
345: }
346:
347: /**
348: * @param current The current to set.
349: */
350: public void setCurrent(Object current) {
351: this .current = current;
352: }
353:
354: /**
355: * @param first The first to set.
356: */
357: public void setFirst(boolean first) {
358: this .first = first;
359: }
360:
361: /**
362: * @param last The last to set.
363: */
364: public void setLast(boolean last) {
365: this .last = last;
366: }
367:
368: /**
369: * @param index The index to set.
370: */
371: public void setIndex(int index) {
372: this.index = index;
373: }
374: }
375: }
|