/* * Copyright 2002,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.anodyneos.xp.tag.core; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.jsp.el.ELException; import org.anodyneos.xp.XpException; import org.anodyneos.xp.XpOutput; import org.anodyneos.xp.tagext.XpTagSupport; import org.apache.commons.collections.iterators.ArrayIterator; import org.apache.commons.collections.iterators.EnumerationIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.SAXException; /** * Iterates over a collection, iterator or an array of objects. Uses the same * syntax as the <a href="http://java.sun.com/products/jsp/jstl/">JSTL </a> * <code>forEach</code> tag does. * * @author <a href="mailto:jstrachan@apache.org">James Strachan </a> * @version $Revision: 1.3 $ */ public class ForEachTag extends XpTagSupport { protected static final Iterator EMPTY_ITERATOR = Collections.EMPTY_LIST .iterator(); /** The Log to which logging calls will be made. */ private static final Log log = LogFactory.getLog(ForEachTag.class); /** Holds the variable name to export for the item being iterated over. */ private Object items; /** * If specified then the current item iterated through will be defined as * the given variable name. */ private String var; /** * If specified then the current index counter will be defined as the given * variable name. */ private String indexVar; /** variable to hold loop status */ private String statusVar; /** The starting index value */ private int begin; /** The ending index value */ private int end = Integer.MAX_VALUE; /** The index increment step */ private int step = 1; /** The iteration index */ private int index; public ForEachTag() { } // Tag interface // ------------------------------------------------------------------------- public void doTag(XpOutput output) throws XpException, SAXException, ELException { if (log.isDebugEnabled()) { log.debug("running with items: " + items); } if (items != null) { Iterator iter = toIterator(items); if (iter == null) { throw new XpException("Invalid type for attribute items."); } if (log.isDebugEnabled()) { log.debug("Iterating through: " + iter); } // ignore the first items of the iterator for (index = 0; index < begin && iter.hasNext(); index++) { iter.next(); } // set up the status LoopStatus status = null; if (statusVar != null) { // set up statii as required by JSTL Integer statusBegin = (begin == 0) ? null : new Integer(begin); Integer statusEnd = (end == Integer.MAX_VALUE) ? null : new Integer(end); Integer statusStep = (step == 1) ? null : new Integer(step); status = new LoopStatus(statusBegin, statusEnd, statusStep); getXpContext().setAttribute(statusVar, status); } boolean firstTime = true; int count = 0; while (iter.hasNext() && index <= end) { Object value = iter.next(); if (var != null) { getXpContext().setAttribute(var, value); } if (indexVar != null) { getXpContext().setAttribute(indexVar, new Integer(index)); } // set the status var up if (statusVar != null) { count++; status.setCount(count); status.setCurrent(value); status.setFirst(firstTime); status.setIndex(index); // set first time up for the next iteration. if (firstTime) { firstTime = !firstTime; } } // now we need to work out the next index for status isLast // and also advance the iterator and index for the loop. boolean finished = false; index++; for (int i = 1; i < step && !finished; i++, index++) { if (!iter.hasNext()) { finished = true; } else { iter.next(); } } if (statusVar != null) { status.setLast(finished || !iter.hasNext() || index > end); } if (null != getXpBody()) { getXpBody().invoke(output); } } } else { if (end == Integer.MAX_VALUE && begin == 0) { throw new XpException("Attribute items is required."); } else { String varName = var; if (varName == null) { varName = indexVar; } // set up the status LoopStatus status = null; if (statusVar != null) { // set up statii as required by JSTL Integer statusBegin = new Integer(begin); Integer statusEnd = new Integer(end); Integer statusStep = new Integer(step); status = new LoopStatus(statusBegin, statusEnd, statusStep); getXpContext().setAttribute(statusVar, status); } int count = 0; for (index = begin; index <= end; index += step) { Object value = new Integer(index); if (varName != null) { getXpContext().setAttribute(varName, value); } // set the status var up if (status != null) { count++; status.setIndex(index); status.setCount(count); status.setCurrent(value); status.setFirst(index == begin); status.setLast(index > end - step); } getXpBody().invoke(output); } } } } // Properties // ------------------------------------------------------------------------- /** * Sets the expression used to iterate over. This expression could resolve * to an Iterator, Collection, Map, Array, Enumeration or comma separated * String. */ public void setItems(Object items) { this.items = items; } /** * Sets the variable name to export for the item being iterated over */ public void setVar(String var) { this.var = var; } /** * Sets the variable name to export the current index counter to */ public void setIndexVar(String indexVar) { this.indexVar = indexVar; } /** * Sets the starting index value */ public void setBegin(int begin) { this.begin = begin; } /** * Sets the ending index value */ public void setEnd(int end) { this.end = end; } /** * Sets the index increment step */ public void setStep(int step) { this.step = step; } /** * Sets the variable name to export the current status to. The status is an * implementation of the JSTL LoopTagStatus interface that provides the * following bean properties: * <ul> * <li>current - the current value of the loop items being iterated</li> * <li>index - the current index of the items being iterated</li> * <li>first - true if this is the first iteration, false otherwise</li> * <li>last - true if this is the last iteration, false otherwise</li> * <li>begin - the starting index of the loop</li> * <li>step - the stepping value of the loop</li> * <li>end - the end index of the loop</li> * </ul> */ public void setVarStatus(String var) { this.statusVar = var; } /** * Holds the status of the loop. */ public static final class LoopStatus implements LoopTagStatus { private Integer begin; private int count; private Object current; private Integer end; private int index; private Integer step; private boolean first; private boolean last; public LoopStatus(Integer begin, Integer end, Integer step) { this.begin = begin; this.end = end; this.step = step; } /** * @return Returns the begin. */ public Integer getBegin() { return begin; } /** * @return Returns the count. */ public int getCount() { return count; } /** * @return Returns the current. */ public Object getCurrent() { return current; } /** * @return Returns the end. */ public Integer getEnd() { return end; } /** * @return Returns the first. */ public boolean isFirst() { return first; } /** * @return Returns the index. */ public int getIndex() { return index; } /** * @return Returns the last. */ public boolean isLast() { return last; } /** * @return Returns the step. */ public Integer getStep() { return step; } /** * @param count * The count to set. */ public void setCount(int count) { this.count = count; } /** * @param current * The current to set. */ public void setCurrent(Object current) { this.current = current; } /** * @param first * The first to set. */ public void setFirst(boolean first) { this.first = first; } /** * @param last * The last to set. */ public void setLast(boolean last) { this.last = last; } /** * @param index * The index to set. */ public void setIndex(int index) { this.index = index; } } public Iterator toIterator(Object value) { if (value == null) { return EMPTY_ITERATOR; } else if (value.getClass().isArray()) { return new ArrayIterator(value); } else if (value instanceof Iterator) { return (Iterator) value; } else if (value instanceof Map) { Map map = (Map) value; return map.entrySet().iterator(); } else if (value instanceof Enumeration) { return new EnumerationIterator((Enumeration) value); } else if (value instanceof Collection) { Collection collection = (Collection) value; return collection.iterator(); } else if (value instanceof String) { StringTokenizer st = new StringTokenizer((String) value, ","); return new EnumerationIterator(st); } else { // TODO: make sure recipient throws exception "unsupported type" return null; } } }