FileDocCategorySizeDatePackage
LoopTagSupport.javaAPI DocGlassfish v2 API25680Sat May 05 19:17:14 BST 2007javax.servlet.jsp.jstl.core

LoopTagSupport

public abstract class LoopTagSupport extends javax.servlet.jsp.tagext.TagSupport implements javax.servlet.jsp.tagext.IterationTag, LoopTag, javax.servlet.jsp.tagext.TryCatchFinally

Base support class to facilitate implementation of iteration tags.

Since most iteration tags will behave identically with respect to actual iterative behavior, JSTL provides this base support class to facilitate implementation. Many iteration tags will extend this and merely implement the hasNext() and next() methods to provide contents for the handler to iterate over.

In particular, this base class provides support for:

  • Iteration control, based on protected prepare(), next(), and hasNext() methods
  • Subsetting (begin, end, step>functionality, including validation of subset parameters for sensibility)
  • item retrieval (getCurrent())
  • status retrieval (LoopTagStatus)
  • exposing attributes (set by var and varStatus attributes)

In providing support for these tasks, LoopTagSupport contains certain control variables that act to modify the iteration. Accessors are provided for these control variables when the variables represent information needed or wanted at translation time (e.g., var, varStatus). For other variables, accessors cannot be provided here since subclasses may differ on their implementations of how those accessors are received. For instance, one subclass might accept a String and convert it into an object of a specific type by using an expression evaluator; others might accept objects directly. Still others might not want to expose such information to outside control.

author
Shawn Bayern

Fields Summary
protected int
begin
Starting index ('begin' attribute)
protected int
end
Ending index of the iteration ('end' attribute). A value of -1 internally indicates 'no end specified', although accessors for the core JSTL tags do not allow this value to be supplied directly by the user.
protected int
step
Iteration step ('step' attribute)
protected boolean
beginSpecified
Boolean flag indicating whether 'begin' was specified.
protected boolean
endSpecified
Boolean flag indicating whether 'end' was specified.
protected boolean
stepSpecified
Boolean flag indicating whether 'step' was specified.
protected String
itemId
Attribute-exposing control
protected String
statusId
protected ValueExpression
deferredExpression
The deferred expression if any
private ValueExpression
oldMappedValue
A temporary used to hold the previous value (from the enclosing iteration tag) for the EL variable.
private LoopTagStatus
status
private Object
item
private int
index
private int
count
private boolean
last
private IteratedExpression
iteratedExpression
Constructors Summary
public LoopTagSupport()
Constructs a new LoopTagSupport. As with TagSupport, subclasses should not implement constructors with arguments, and no-arguments constructors implemented by subclasses must call the superclass constructor.

        super();
        init();
    
Methods Summary
private booleanatEnd()
Returns true if the iteration has past the 'end' index (with respect to subsetting), false otherwise. ('end' must be set for atEnd() to return true; if 'end' is not set, atEnd() always returns false.)

        return ((end != -1) && (begin + index >= end));
    
private voidcalibrateLast()
Sets 'last' appropriately.

        /*
         * the current round is the last one if (a) there are no remaining
         * elements, or (b) the next one is beyond the 'end'.
         */
        last = !hasNext() || atEnd() ||
            (end != -1 && (begin + index + step > end));
    
private voiddiscard(int n)
Cycles through and discards up to 'n' items from the iteration. We only know "up to 'n'", not "exactly n," since we stop cycling if hasNext() returns false or if we hit the 'end' of the iteration. Note: this does not update the iteration index, since this method is intended as a behind-the-scenes operation. The index must be updated separately. (I don't really like this, but it's the simplest way to support isLast() without storing two separate inconsistent indices. We need to (a) make sure hasNext() refers to the next item we actually *want* and (b) make sure the index refers to the item associated with the *current* round, not the next one. C'est la vie.)

        /*
         * copy index so we can restore it, but we need to update it
         * as we work so that atEnd() works
         */
        int oldIndex = index;
        while (n-- > 0 && !atEnd() && hasNext()) {
            index++;
            next();
        }
        index = oldIndex;
    
private voiddiscardIgnoreSubset(int n)
Discards items ignoring subsetting rules. Useful for discarding items from the beginning (i.e., to implement 'begin') where we don't want factor in the 'begin' value already.

	while (n-- > 0 && hasNext())
	    next();
    
public intdoAfterBody()
Continues the iteration when appropriate -- that is, if we (a) have more items and (b) don't run over our 'end' (given our 'step').


        // re-sync the index, given our prior behind-the-scenes 'step'
        index += step - 1;

        // increment the count by 1 for each round
        count++;

        // everything's been prepared for us, so just get the next item
        if (hasNext() && !atEnd()) {
            index++;
            item = next();
        } else
            return SKIP_BODY;

        /*
         * now discard anything we have to "step" over.
         * (we do this in advance to support LoopTagStatus.isLast())
         */
        discard(step - 1);

        // prepare to re-iterate...
        exposeVariables(false);
        calibrateLast();
        return EVAL_BODY_AGAIN;
    
public voiddoCatch(java.lang.Throwable t)
Rethrows the given Throwable.

	throw t;
    
public voiddoFinally()
Removes any attributes that this LoopTagSupport set.

These attributes are intended to support scripting variables with NESTED scope, so we don't want to pollute attribute space by leaving them lying around.

	/*
	 * Make sure to un-expose variables, restoring them to their
	 * prior values, if applicable.
         */
	unExposeVariables();
    
public intdoStartTag()
Begins iterating by processing the first item.

        if (end != -1 && begin > end) {
            // JSTL 1.1. We simply do not execute the loop.
            return SKIP_BODY;
        }

        // we're beginning a new iteration, so reset our counts (etc.)
        index = 0;
        count = 1;
        last = false;
        iteratedExpression = null;
        deferredExpression = null;

        // let the subclass conduct any necessary preparation
        prepare();

        // throw away the first 'begin' items (if they exist)
        discardIgnoreSubset(begin);

        // get the item we're interested in
        if (hasNext())
            // index is 0-based, so we don't update it for the first item
            item = next();
        else
            return SKIP_BODY;

        /*
         * now discard anything we have to "step" over.
         * (we do this in advance to support LoopTagStatus.isLast())
         */
        discard(step - 1);

        // prepare to include our body...
        exposeVariables(true);
        calibrateLast();
        return EVAL_BODY_INCLUDE;
    
private voidexposeVariables(boolean firstTime)
Exposes attributes (formerly scripting variables, but no longer!) if appropriate. Note that we don't really care, here, whether they're scripting variables or not.


        /*
         * We need to support null items returned from next(); we
         * do this simply by passing such non-items through to the
         * scoped variable as effectively 'null' (that is, by calling
         * removeAttribute()).
         *
         * Also, just to be defensive, we handle the case of a null
         * 'status' object as well.
         *
         * We call getCurrent() and getLoopStatus() (instead of just using
         * 'item' and 'status') to bridge to subclasses correctly.
         * A subclass can override getCurrent() or getLoopStatus() but still
         * depend on our doStartTag() and doAfterBody(), which call this
         * method (exposeVariables()), to expose 'item' and 'status'
         * correctly.
         */

        if (itemId != null) {
            if (getCurrent() == null)
                pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
            else if (deferredExpression != null) {
                VariableMapper vm = 
                    pageContext.getELContext().getVariableMapper();
                if (vm != null) {
                    ValueExpression ve = getVarExpression(deferredExpression);
                    ValueExpression tmpValue = vm.setVariable(itemId, ve);
                    if (firstTime)
                        oldMappedValue = tmpValue;
                }
            } else
                pageContext.setAttribute(itemId, getCurrent());
        }
        if (statusId != null) {
            if (getLoopStatus() == null)
                pageContext.removeAttribute(statusId, PageContext.PAGE_SCOPE);
            else
                pageContext.setAttribute(statusId, getLoopStatus());
        }

    
public java.lang.ObjectgetCurrent()

        return item;
    
protected java.lang.StringgetDelims()

        return ",";
    
public LoopTagStatusgetLoopStatus()


        // local implementation with reasonable default behavior
        class Status implements LoopTagStatus {

            /*
             * All our methods are straightforward.  We inherit
             * our JavaDoc from LoopTagSupport; see that class
             * for more information.
             */

            public Object getCurrent() {
                /*
                 * Access the item through getCurrent() instead of just
                 * returning the item our containing class stores.  This
                 * should allow a subclass of LoopTagSupport to override
                 * getCurrent() without having to rewrite getLoopStatus() too.
                 */
                return (LoopTagSupport.this.getCurrent());
            }
            public int getIndex() {
                return (index + begin);       // our 'index' isn't getIndex()
            }
            public int getCount() {
                return (count);
            }
            public boolean isFirst() {
                return (index == 0);          // our 'index' isn't getIndex()
            }
            public boolean isLast() {
                return (last);                // use cached value
            }
            public Integer getBegin() {
                if (beginSpecified)
                    return Integer.valueOf(begin);
                else
                    return null;
            }
            public Integer getEnd() {
                if (endSpecified)
                    return Integer.valueOf(end);
                else
                    return null;
            }
            public Integer getStep() {
                if (stepSpecified)
                    return Integer.valueOf(step);
                else
                    return null;
            }
        }

        /*
         * We just need one per invocation...  Actually, for the current
         * implementation, we just need one per instance, but I'd rather
         * not keep the reference around once release() has been called.
         */
        if (status == null)
            status = new Status();

        return status;
    
private javax.el.ValueExpressiongetVarExpression(javax.el.ValueExpression expr)

        Object o = expr.getValue(pageContext.getELContext());
        if (o == null)
            return null;

        if (o.getClass().isArray() || o instanceof List) {
            return new IndexedValueExpression(deferredExpression, index);
        }

        if (o instanceof Collection || o instanceof Iterator ||
            o instanceof Enumeration || o instanceof Map ||
            o instanceof String) {

            if (iteratedExpression == null) {
                iteratedExpression =
                    new IteratedExpression(deferredExpression, getDelims());
            }
            return new IteratedValueExpression(iteratedExpression, index);
        }

        throw new ELException("Don't know how to iterate over supplied "
                              + "items in forEach");
    
protected abstract booleanhasNext()

Returns information concerning the availability of more items over which to iterate. This method must be provided by concrete subclasses of LoopTagSupport to assist the iterative logic provided by the supporting base class.

See next for more information about the purpose and expectations behind this tag.

return
true if there is at least one more item to iterate over, false otherwise
exception
javax.servlet.jsp.JspTagException
see
#next

private voidinit()
(Re)initializes state (during release() or construction)

        // defaults for internal bookkeeping
        index = 0;              // internal index always starts at 0
        count = 1;              // internal count always starts at 1
        status = null;          // we clear status on release()
        item = null;            // item will be retrieved for each round
        last = false;           // last must be set explicitly
        beginSpecified = false; // not specified until it's specified :-)
        endSpecified = false;   // (as above)
        stepSpecified = false;  // (as above)

        // defaults for interface with page author
        begin = 0;              // when not specified, 'begin' is 0 by spec.
        end = -1;               // when not specified, 'end' is not used
        step = 1;               // when not specified, 'step' is 1
        itemId = null;          // when not specified, no variable exported
        statusId = null;        // when not specified, no variable exported
    
protected abstract java.lang.Objectnext()

Returns the next object over which the tag should iterate. This method must be provided by concrete subclasses of LoopTagSupport to inform the base logic about what objects it should iterate over.

It is expected that this method will generally be backed by an Iterator, but this will not always be the case. In particular, if retrieving the next object raises the possibility of an exception being thrown, this method allows that exception to propagate back to the JSP container as a JspTagException; a standalone Iterator would not be able to do this. (This explains why LoopTagSupport does not simply call for an Iterator from its subtags.)

return
the java.lang.Object to use in the next round of iteration
exception
java.util.NoSuchElementException if next() is called but no new elements are available
exception
javax.servlet.jsp.JspTagException for other, unexpected exceptions

protected abstract voidprepare()

Prepares for a single tag invocation. Specifically, allows subclasses to prepare for calls to hasNext() and next(). Subclasses can assume that prepare() will be called once for each invocation of doStartTag() in the superclass.

exception
javax.servlet.jsp.JspTagException

public voidrelease()
Releases any resources this LoopTagSupport may have (or inherit).

        super.release();
        init();
    
public voidsetVar(java.lang.String id)
Sets the 'var' attribute.

param
id Name of the exported scoped variable storing the current item of the iteration.

        this.itemId = id;
    
public voidsetVarStatus(java.lang.String statusId)
Sets the 'varStatus' attribute.

param
statusId Name of the exported scoped variable storing the status of the iteration.

        this.statusId = statusId;
    
private voidunExposeVariables()
Removes page attributes that we have exposed and, if applicable, restores them to their prior values (and scopes).

        // "nested" variables are now simply removed
	if (itemId != null) {
            pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
            VariableMapper vm = pageContext.getELContext().getVariableMapper();
            if (vm != null)
                vm.setVariable(itemId, oldMappedValue);
        }
	if (statusId != null)
	    pageContext.removeAttribute(statusId, PageContext.PAGE_SCOPE);
    
protected voidvalidateBegin()
Ensures the "begin" property is sensible, throwing an exception expected to propagate up if it isn't

        if (begin < 0)
            throw new JspTagException("'begin' < 0");
    
protected voidvalidateEnd()
Ensures the "end" property is sensible, throwing an exception expected to propagate up if it isn't

        if (end < 0)
            throw new JspTagException("'end' < 0");
    
protected voidvalidateStep()
Ensures the "step" property is sensible, throwing an exception expected to propagate up if it isn't

        if (step < 1)
            throw new JspTagException("'step' <= 0");