Methods Summary |
---|
private boolean | atEnd()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 void | calibrateLast()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 void | discard(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 void | discardIgnoreSubset(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 int | doAfterBody()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 void | doCatch(java.lang.Throwable t)Rethrows the given Throwable.
throw t;
|
public void | doFinally()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 int | doStartTag()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 void | exposeVariables(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.Object | getCurrent()
return item;
|
protected java.lang.String | getDelims()
return ",";
|
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 (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.ValueExpression | getVarExpression(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 boolean | hasNext()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.
|
private void | init()(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.Object | next()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.)
|
protected abstract void | prepare()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.
|
public void | release()Releases any resources this LoopTagSupport may have (or inherit).
super.release();
init();
|
public void | setVar(java.lang.String id)Sets the 'var' attribute.
this.itemId = id;
|
public void | setVarStatus(java.lang.String statusId)Sets the 'varStatus' attribute.
this.statusId = statusId;
|
private void | unExposeVariables()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 void | validateBegin()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 void | validateEnd()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 void | validateStep()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");
|