Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
   *
   * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
   *
   * The contents of this file are subject to the terms of either the GNU
   * General Public License Version 2 only ("GPL") or the Common Development
   * and Distribution License("CDDL") (collectively, the "License").  You
   * may not use this file except in compliance with the License.  You can
  * obtain a copy of the License at
  * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
  * or packager/legal/LICENSE.txt.  See the License for the specific
  * language governing permissions and limitations under the License.
  *
  * When distributing the software, include this License Header Notice in each
  * file and include the License file at packager/legal/LICENSE.txt.
  *
  * GPL Classpath Exception:
  * Oracle designates this particular file as subject to the "Classpath"
  * exception as provided by Oracle in the GPL Version 2 section of the License
  * file that accompanied this code.
  *
  * Modifications:
  * If applicable, add the following below the License Header, with the fields
  * enclosed by brackets [] replaced by your own identifying information:
  * "Portions Copyright [year] [name of copyright owner]"
  *
  * Contributor(s):
  * If you wish your version of this file to be governed by only the CDDL or
  * only the GPL Version 2, indicate your decision by adding "[Contributor]
  * elects to include this software in this distribution under the [CDDL or GPL
  * Version 2] license."  If you don't indicate a single choice of license, a
  * recipient has the option to distribute your version of this file under
  * either the CDDL, the GPL Version 2 or to extend the choice of license to
  * its licensees as provided above.  However, if you add GPL Version 2 code
  * and therefore, elected the GPL Version 2 license, then the option applies
  * only if the new code is made subject to such option by the copyright
  * holder.
  *
  *
  * This file incorporates work covered by the following copyright and
  * permission notice:
  *
  * Copyright 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 javax.servlet.jsp.jstl.core;
 
 import java.util.List;
 import java.util.Map;
 
 

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(s):
Shawn Bayern
public abstract class LoopTagSupport
    extends TagSupport
    implements LoopTagIterationTagTryCatchFinally
    //*********************************************************************
    // 'Protected' state 
    /*
     * JavaBean-style properties and other state slaved to them.  These
     * properties can be set directly by accessors; they will not be
     * modified by the LoopTagSupport implementation -- and should
     * not be modified by subclasses outside accessors unless those
     * subclasses are perfectly aware of what they're doing.
     * (An example where such non-accessor modification might be sensible
     * is in the doStartTag() method of an EL-aware subclass.)
     */

    
Starting index ('begin' attribute)
    protected int begin;

    
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 end;

    
Iteration step ('step' attribute)
    protected int step;

    
Boolean flag indicating whether 'begin' was specified.
    protected boolean beginSpecified;

    
Boolean flag indicating whether 'end' was specified.
    protected boolean endSpecified;

    
Boolean flag indicating whether 'step' was specified.
    protected boolean stepSpecified;

    
Attribute-exposing control
    protected String itemIdstatusId;

    
The deferred expression if any
    protected ValueExpression deferredExpression;

    
A temporary used to hold the previous value (from the enclosing iteration tag) for the EL variable.
    //*********************************************************************
    // 'Private' state (implementation details)
    /*
     * State exclusively internal to the default, reference implementation.
     * (While this state is kept private to ensure consistency, 'status'
     * and 'item' happen to have one-for-one, read-only, accesor methods
     * as part of the LoopTag interface.)
     *
     * 'last' is kept separately for two reasons:  (a) to avoid
     * running a computation every time it's requested, and (b) to
     * let LoopTagStatus.isLast() avoid throwing any exceptions,
     * which would complicate subtag and scripting-variable use.
     *
     * Our 'internal index' begins at 0 and increases by 'step' each
     * round; this is arbitrary, but it seemed a simple way of keeping
     * track of the information we need.  To avoid computing
     * getLoopStatus().getCount() by dividing index / step, we keep
     * a separate 'count' and increment it by 1 each round (as a minor
     * performance improvement).
     */
    private LoopTagStatus status;               // our LoopTagStatus
    private Object item;                        // the current item
    private int index;                          // the current internal index
    private int count;                          // the iteration count
    private boolean last;                       // current round == last one?
                // holds an instance shared by all ValueExpression created
                // for variableMapper, for iterators.
    //*********************************************************************
    // Constructor

    
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.
    public LoopTagSupport() {
        super();
        init();
    }
    //*********************************************************************
    // Abstract methods

    

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.)

Returns:
the java.lang.Object to use in the next round of iteration
Throws:
java.util.NoSuchElementException if next() is called but no new elements are available
javax.servlet.jsp.JspTagException for other, unexpected exceptions
    protected abstract Object next() throws JspTagException;

    

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.

Returns:
true if there is at least one more item to iterate over, false otherwise
Throws:
javax.servlet.jsp.JspTagException
See also:
next()
    protected abstract boolean hasNext() throws JspTagException;

    

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.

    protected abstract void prepare() throws JspTagException;
    //*********************************************************************
    // Lifecycle management and implementation of iterative behavior

    
Releases any resources this LoopTagSupport may have (or inherit).
    public void release() {
        super.release();
        init();
    }

    
Begins iterating by processing the first item.
    public int doStartTag() throws JspException {
        if ( != -1 &&  > ) {
            // JSTL 1.1. We simply do not execute the loop.
            return ;
        }
        // we're beginning a new iteration, so reset our counts (etc.)
         = 0;
         = 1;
         = false;
         = null;
         = null;
        // let the subclass conduct any necessary preparation
        prepare();
        // throw away the first 'begin' items (if they exist)
        discardIgnoreSubset();
        // get the item we're interested in
        if (hasNext())
            // index is 0-based, so we don't update it for the first item
             = next();
        else
            return ;
        /*
         * now discard anything we have to "step" over.
         * (we do this in advance to support LoopTagStatus.isLast())
         */
        discard( - 1);
        // prepare to include our body...
        exposeVariables(true);
        calibrateLast();
        return ;
    }

    
Continues the iteration when appropriate -- that is, if we (a) have more items and (b) don't run over our 'end' (given our 'step').
    public int doAfterBody() throws JspException {
        // re-sync the index, given our prior behind-the-scenes 'step'
         +=  - 1;
        // increment the count by 1 for each round
        ++;
        // everything's been prepared for us, so just get the next item
        if (hasNext() && !atEnd()) {
            ++;
             = next();
        } else
            return ;
        /*
         * now discard anything we have to "step" over.
         * (we do this in advance to support LoopTagStatus.isLast())
         */
        discard( - 1);
        // prepare to re-iterate...
        exposeVariables(false);
        calibrateLast();
        return ;
    }

    
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.

    public void doFinally() {
	/*
	 * Make sure to un-expose variables, restoring them to their
	 * prior values, if applicable.
         */
    }

    
Rethrows the given Throwable.
    public void doCatch(Throwable tthrows Throwable {
	throw t;
    }
    //*********************************************************************
    // Accessor methods
    /*
     * Overview:  The getXXX() methods we provide implement the Tag
     * contract.  setXXX() accessors are provided only for those
     * properties (attributes) that must be known at translation time,
     * on the premise that these accessors will vary less than the
     * others in terms of their interface with the page author.
     */
    /*
     * (Purposely inherit JavaDoc and semantics from LoopTag.
     * Subclasses can override this if necessary, but such a need is
     * expected to be rare.)
     */
    public Object getCurrent() {
        return ;
    }
    /*
     * (Purposely inherit JavaDoc and semantics from LoopTag.
     * Subclasses can override this method for more fine-grained control
     * over LoopTagStatus, but an effort has been made to simplify
     * implementation of subclasses that are happy with reasonable default
     * behavior.)
     */
    public LoopTagStatus getLoopStatus() {
        // 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 ( + );       // our 'index' isn't getIndex()
            }
            public int getCount() {
                return ();
            }
            public boolean isFirst() {
                return ( == 0);          // our 'index' isn't getIndex()
            }
            public boolean isLast() {
                return ();                // use cached value
            }
            public Integer getBegin() {
                if ()
                    return Integer.valueOf();
                else
                    return null;
            }
            public Integer getEnd() {
                if ()
                    return Integer.valueOf();
                else
                    return null;
            }
            public Integer getStep() {
                if ()
                    return Integer.valueOf();
                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 ( == null)
             = new Status();
        return ;
    }
    /*
     * Get the delimiter for string tokens.  Used only for constructing
     * the deferred expression for it.
     */
    protected String getDelims() {
        return ",";
    }
    /*
     * We only support setter methods for attributes that need to be
     * offered as Strings or other literals; other attributes will be
     * handled directly by implementing classes, since there might be
     * both rtexprvalue- and EL-based varieties, which will have
     * different signatures.  (We can't pollute child classes by having
     * base implementations of those setters here; child classes that
     * have attributes with different signatures would end up having
     * two incompatible setters, which is illegal for a JavaBean.
     */

    
Sets the 'var' attribute.

Parameters:
id Name of the exported scoped variable storing the current item of the iteration.
    public void setVar(String id) {
        this. = id;
    }

    
Sets the 'varStatus' attribute.

Parameters:
statusId Name of the exported scoped variable storing the status of the iteration.
    public void setVarStatus(String statusId) {
        this. = statusId;
    }
    //*********************************************************************
    // Protected utility methods
    /* 
     * These methods validate attributes common to iteration tags.
     * Call them if your own subclassing implementation modifies them
     * -- e.g., if you set them through an expression language.
     */

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

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

    
Ensures the "step" property is sensible, throwing an exception expected to propagate up if it isn't
    protected void validateStep() throws JspTagException {
        if ( < 1)
            throw new JspTagException("'step' <= 0");
    }
    //*********************************************************************
    // Private utility methods

    
(Re)initializes state (during release() or construction)
    private void init() {
        // defaults for internal bookkeeping
         = 0;              // internal index always starts at 0
         = 1;              // internal count always starts at 1
         = null;          // we clear status on release()
         = null;            // item will be retrieved for each round
         = false;           // last must be set explicitly
         = false// not specified until it's specified :-)
         = false;   // (as above)
         = false;  // (as above)
        // defaults for interface with page author
         = 0;              // when not specified, 'begin' is 0 by spec.
         = -1;               // when not specified, 'end' is not used
         = 1;               // when not specified, 'step' is 1
         = null;          // when not specified, no variable exported
         = null;        // when not specified, no variable exported
    }

    
Sets 'last' appropriately.
    private void calibrateLast() throws JspTagException {
        /*
         * the current round is the last one if (a) there are no remaining
         * elements, or (b) the next one is beyond the 'end'.
         */
         = !hasNext() || atEnd() ||
            ( != -1 && ( +  +  > ));
    }

    
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.
    private void exposeVariables(boolean firstTimethrows JspTagException {
        /*
         * 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 ( != null) {
            if (getCurrent() == null)
                .removeAttribute(.);
            else if ( != null) {
                VariableMapper vm = 
                    .getELContext().getVariableMapper();
                if (vm != null) {
                    ValueExpression ve = getVarExpression();
                    ValueExpression tmpValue = vm.setVariable(ve);
                    if (firstTime)
                         = tmpValue;
                }
            } else
                .setAttribute(getCurrent());
        }
        if ( != null) {
            if (getLoopStatus() == null)
                .removeAttribute(.);
            else
                .setAttribute(getLoopStatus());
        }
    }

    
Removes page attributes that we have exposed and, if applicable, restores them to their prior values (and scopes).
    private void unExposeVariables() {
        // "nested" variables are now simply removed
	if ( != null) {
            VariableMapper vm = .getELContext().getVariableMapper();
            if (vm != null)
                vm.setVariable();
        }
	if ( != null)
    }

    
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.)
    private void discard(int nthrows JspTagException {
        /*
         * copy index so we can restore it, but we need to update it
         * as we work so that atEnd() works
         */
        int oldIndex = ;
        while (n-- > 0 && !atEnd() && hasNext()) {
            ++;
            next();
        }
         = oldIndex;
    }

    
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.
    private void discardIgnoreSubset(int nthrows JspTagException {
	while (n-- > 0 && hasNext())
	    next();
    }

    
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.)
    private boolean atEnd() {
        return (( != -1) && ( +  >= ));
    }
        Object o = expr.getValue(.getELContext());
        if (o == null)
            return null;
        if (o.getClass().isArray() || o instanceof List) {
            return new IndexedValueExpression(+);
        }
        if (o instanceof Collection || o instanceof Iterator ||
            o instanceof Enumeration || o instanceof Map ||
            o instanceof String) {
            if ( == null) {
                 =
                    new IteratedExpression(getDelims());
            }
            return new IteratedValueExpression(+);
        }
        throw new ELException("Don't know how to iterate over supplied "
                              + "items in forEach");
    }
New to GrepCode? Check out our FAQ X