KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > servlet > jsp > jstl > core > LoopTagSupport


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package javax.servlet.jsp.jstl.core;
18
19 import javax.servlet.jsp.JspException JavaDoc;
20 import javax.servlet.jsp.JspTagException JavaDoc;
21 import javax.servlet.jsp.PageContext JavaDoc;
22 import javax.servlet.jsp.tagext.IterationTag JavaDoc;
23 import javax.servlet.jsp.tagext.TagSupport JavaDoc;
24 import javax.servlet.jsp.tagext.TryCatchFinally JavaDoc;
25
26 /**
27  * <p>Base support class to facilitate implementation of iteration tags.</p>
28  *
29  * <p>Since most iteration tags will behave identically with respect to
30  * actual iterative behavior, JSTL provides this
31  * base support class to facilitate implementation. Many iteration tags
32  * will extend this and merely implement the <tt>hasNext()</tt> and
33  * <tt>next()</tt> methods
34  * to provide contents for the handler to iterate over.</p>
35  *
36  * <p>In particular, this base class provides support for:</p>
37  *
38  * <ul>
39  * <li> Iteration control, based on protected <tt>prepare()</tt>, <tt>next()</tt>,
40  * and <tt>hasNext()</tt> methods
41  * <li> Subsetting (<tt>begin</tt>, <tt>end</tt>, <tt>step></tt>functionality,
42  * including validation
43  * of subset parameters for sensibility)
44  * <li> item retrieval (<tt>getCurrent()</tt>)
45  * <li> status retrieval (<tt>LoopTagStatus</tt>)
46  * <li> exposing attributes (set by <tt>var</tt> and <tt>varStatus</tt> attributes)
47  * </ul>
48  *
49  * <p>In providing support for these tasks, <tt>LoopTagSupport</tt> contains
50  * certain control variables that act to modify the iteration. Accessors
51  * are provided for these control variables when the variables represent
52  * information needed or wanted at translation time (e.g., <tt>var</tt>,
53  * <tt>varStatus</tt>). For
54  * other variables, accessors cannot be provided here since subclasses
55  * may differ on their implementations of how those accessors are received.
56  * For instance, one subclass might accept a <tt>String</tt> and convert it into
57  * an object of a specific type by using an expression evaluator; others
58  * might accept objects directly. Still others might not want to expose
59  * such information to outside control.</p>
60  *
61  * @author Shawn Bayern
62  */

63
64 public abstract class LoopTagSupport
65     extends TagSupport JavaDoc
66     implements LoopTag, IterationTag JavaDoc, TryCatchFinally JavaDoc
67 {
68     //*********************************************************************
69
// 'Protected' state
70

71     /*
72      * JavaBean-style properties and other state slaved to them. These
73      * properties can be set directly by accessors; they will not be
74      * modified by the LoopTagSupport implementation -- and should
75      * not be modified by subclasses outside accessors unless those
76      * subclasses are perfectly aware of what they're doing.
77      * (An example where such non-accessor modification might be sensible
78      * is in the doStartTag() method of an EL-aware subclass.)
79      */

80
81     /** Starting index ('begin' attribute) */
82     protected int begin;
83
84     /**
85      * Ending index of the iteration ('end' attribute).
86      * A value of -1 internally indicates 'no end
87      * specified', although accessors for the core JSTL tags do not
88      * allow this value to be supplied directly by the user.
89      */

90     protected int end;
91
92     /** Iteration step ('step' attribute) */
93     protected int step;
94
95     /** Boolean flag indicating whether 'begin' was specified. */
96     protected boolean beginSpecified;
97
98     /** Boolean flag indicating whether 'end' was specified. */
99     protected boolean endSpecified;
100
101     /** Boolean flag indicating whether 'step' was specified. */
102     protected boolean stepSpecified;
103
104     /** Attribute-exposing control */
105     protected String JavaDoc itemId, statusId;
106
107
108     //*********************************************************************
109
// 'Private' state (implementation details)
110

111     /*
112      * State exclusively internal to the default, reference implementation.
113      * (While this state is kept private to ensure consistency, 'status'
114      * and 'item' happen to have one-for-one, read-only, accesor methods
115      * as part of the LoopTag interface.)
116      *
117      * 'last' is kept separately for two reasons: (a) to avoid
118      * running a computation every time it's requested, and (b) to
119      * let LoopTagStatus.isLast() avoid throwing any exceptions,
120      * which would complicate subtag and scripting-variable use.
121      *
122      * Our 'internal index' begins at 0 and increases by 'step' each
123      * round; this is arbitrary, but it seemed a simple way of keeping
124      * track of the information we need. To avoid computing
125      * getLoopStatus().getCount() by dividing index / step, we keep
126      * a separate 'count' and increment it by 1 each round (as a minor
127      * performance improvement).
128      */

129     private LoopTagStatus status; // our LoopTagStatus
130
private Object JavaDoc item; // the current item
131
private int index; // the current internal index
132
private int count; // the iteration count
133
private boolean last; // current round == last one?
134

135     //*********************************************************************
136
// Constructor
137

138     /**
139      * Constructs a new LoopTagSupport. As with TagSupport, subclasses
140      * should not implement constructors with arguments, and no-arguments
141      * constructors implemented by subclasses must call the superclass
142      * constructor.
143      */

144     public LoopTagSupport() {
145         super();
146         init();
147     }
148
149
150     //*********************************************************************
151
// Abstract methods
152

153     /**
154      * <p>Returns the next object over which the tag should iterate. This
155      * method must be provided by concrete subclasses of LoopTagSupport
156      * to inform the base logic about what objects it should iterate over.</p>
157      *
158      * <p>It is expected that this method will generally be backed by an
159      * Iterator, but this will not always be the case. In particular, if
160      * retrieving the next object raises the possibility of an exception
161      * being thrown, this method allows that exception to propagate back
162      * to the JSP container as a JspTagException; a standalone Iterator
163      * would not be able to do this. (This explains why LoopTagSupport
164      * does not simply call for an Iterator from its subtags.)</p>
165      *
166      * @return the java.lang.Object to use in the next round of iteration
167      * @exception java.util.NoSuchElementException
168      * if next() is called but no new elements are available
169      * @exception javax.servlet.jsp.JspTagException
170      * for other, unexpected exceptions
171      */

172     protected abstract Object JavaDoc next() throws JspTagException JavaDoc;
173
174     /**
175      * <p>Returns information concerning the availability of more items
176      * over which to iterate. This method must be provided by concrete
177      * subclasses of LoopTagSupport to assist the iterative logic
178      * provided by the supporting base class.</p>
179      *
180      * <p>See <a HREF="#next()">next</a> for more information about the
181      * purpose and expectations behind this tag.</p>
182      *
183      * @return <tt>true</tt> if there is at least one more item to iterate
184      * over, <tt>false</tt> otherwise
185      * @exception javax.servlet.jsp.JspTagException
186      * @see #next
187      */

188     protected abstract boolean hasNext() throws JspTagException JavaDoc;
189
190     /**
191      * <p>Prepares for a single tag invocation. Specifically, allows
192      * subclasses to prepare for calls to hasNext() and next().
193      * Subclasses can assume that prepare() will be called once for
194      * each invocation of doStartTag() in the superclass.</p>
195      *
196      * @exception javax.servlet.jsp.JspTagException
197      */

198     protected abstract void prepare() throws JspTagException JavaDoc;
199
200
201     //*********************************************************************
202
// Lifecycle management and implementation of iterative behavior
203

204     /**
205      * Releases any resources this LoopTagSupport may have (or inherit).
206      */

207     public void release() {
208         super.release();
209         init();
210     }
211
212     /**
213      * Begins iterating by processing the first item.
214      */

215     public int doStartTag() throws JspException JavaDoc {
216         if (end != -1 && begin > end) {
217             // JSTL 1.1. We simply do not execute the loop.
218
return SKIP_BODY;
219         }
220
221         // we're beginning a new iteration, so reset our counts (etc.)
222
index = 0;
223         count = 1;
224         last = false;
225
226         // let the subclass conduct any necessary preparation
227
prepare();
228
229         // throw away the first 'begin' items (if they exist)
230
discardIgnoreSubset(begin);
231
232         // get the item we're interested in
233
if (hasNext())
234             // index is 0-based, so we don't update it for the first item
235
item = next();
236         else
237             return SKIP_BODY;
238
239         /*
240          * now discard anything we have to "step" over.
241          * (we do this in advance to support LoopTagStatus.isLast())
242          */

243         discard(step - 1);
244
245         // prepare to include our body...
246
exposeVariables();
247         calibrateLast();
248         return EVAL_BODY_INCLUDE;
249     }
250
251     /**
252      * Continues the iteration when appropriate -- that is, if we (a) have
253      * more items and (b) don't run over our 'end' (given our 'step').
254      */

255     public int doAfterBody() throws JspException JavaDoc {
256
257         // re-sync the index, given our prior behind-the-scenes 'step'
258
index += step - 1;
259
260         // increment the count by 1 for each round
261
count++;
262
263         // everything's been prepared for us, so just get the next item
264
if (hasNext() && !atEnd()) {
265             index++;
266             item = next();
267         } else
268             return SKIP_BODY;
269
270         /*
271          * now discard anything we have to "step" over.
272          * (we do this in advance to support LoopTagStatus.isLast())
273          */

274         discard(step - 1);
275
276         // prepare to re-iterate...
277
exposeVariables();
278         calibrateLast();
279         return EVAL_BODY_AGAIN;
280     }
281
282     /**
283      * Removes any attributes that this LoopTagSupport set.
284      *
285      * <p> These attributes are intended to support scripting variables with
286      * NESTED scope, so we don't want to pollute attribute space by leaving
287      * them lying around.
288      */

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

294     unExposeVariables();
295     }
296
297     /**
298      * Rethrows the given Throwable.
299      */

300     public void doCatch(Throwable JavaDoc t) throws Throwable JavaDoc {
301     throw t;
302     }
303
304     //*********************************************************************
305
// Accessor methods
306

307     /*
308      * Overview: The getXXX() methods we provide implement the Tag
309      * contract. setXXX() accessors are provided only for those
310      * properties (attributes) that must be known at translation time,
311      * on the premise that these accessors will vary less than the
312      * others in terms of their interface with the page author.
313      */

314
315     /*
316      * (Purposely inherit JavaDoc and semantics from LoopTag.
317      * Subclasses can override this if necessary, but such a need is
318      * expected to be rare.)
319      */

320     public Object JavaDoc getCurrent() {
321         return item;
322     }
323
324     /*
325      * (Purposely inherit JavaDoc and semantics from LoopTag.
326      * Subclasses can override this method for more fine-grained control
327      * over LoopTagStatus, but an effort has been made to simplify
328      * implementation of subclasses that are happy with reasonable default
329      * behavior.)
330      */

331     public LoopTagStatus getLoopStatus() {
332
333         // local implementation with reasonable default behavior
334
class Status implements LoopTagStatus {
335
336             /*
337              * All our methods are straightforward. We inherit
338              * our JavaDoc from LoopTagSupport; see that class
339              * for more information.
340              */

341
342             public Object JavaDoc getCurrent() {
343                 /*
344                  * Access the item through getCurrent() instead of just
345                  * returning the item our containing class stores. This
346                  * should allow a subclass of LoopTagSupport to override
347                  * getCurrent() without having to rewrite getLoopStatus() too.
348                  */

349                 return (LoopTagSupport.this.getCurrent());
350             }
351             public int getIndex() {
352                 return (index + begin); // our 'index' isn't getIndex()
353
}
354             public int getCount() {
355                 return (count);
356             }
357             public boolean isFirst() {
358                 return (index == 0); // our 'index' isn't getIndex()
359
}
360             public boolean isLast() {
361                 return (last); // use cached value
362
}
363             public Integer JavaDoc getBegin() {
364                 if (beginSpecified)
365                     return (new Integer JavaDoc(begin));
366                 else
367                     return null;
368             }
369             public Integer JavaDoc getEnd() {
370                 if (endSpecified)
371                     return (new Integer JavaDoc(end));
372                 else
373                     return null;
374             }
375             public Integer JavaDoc getStep() {
376                 if (stepSpecified)
377                     return (new Integer JavaDoc(step));
378                 else
379                     return null;
380             }
381         }
382
383         /*
384          * We just need one per invocation... Actually, for the current
385          * implementation, we just need one per instance, but I'd rather
386          * not keep the reference around once release() has been called.
387          */

388         if (status == null)
389             status = new Status();
390
391         return status;
392     }
393
394     /*
395      * We only support setter methods for attributes that need to be
396      * offered as Strings or other literals; other attributes will be
397      * handled directly by implementing classes, since there might be
398      * both rtexprvalue- and EL-based varieties, which will have
399      * different signatures. (We can't pollute child classes by having
400      * base implementations of those setters here; child classes that
401      * have attributes with different signatures would end up having
402      * two incompatible setters, which is illegal for a JavaBean.
403      */

404
405     /**
406      * Sets the 'var' attribute.
407      *
408      * @param id Name of the exported scoped variable storing the current item
409      * of the iteration.
410      */

411     public void setVar(String JavaDoc id) {
412         this.itemId = id;
413     }
414
415     /**
416      * Sets the 'varStatus' attribute.
417      *
418      * @param statusId Name of the exported scoped variable storing the status
419      * of the iteration.
420      */

421     public void setVarStatus(String JavaDoc statusId) {
422         this.statusId = statusId;
423     }
424
425
426     //*********************************************************************
427
// Protected utility methods
428

429     /*
430      * These methods validate attributes common to iteration tags.
431      * Call them if your own subclassing implementation modifies them
432      * -- e.g., if you set them through an expression language.
433      */

434
435     /**
436      * Ensures the "begin" property is sensible, throwing an exception
437      * expected to propagate up if it isn't
438      */

439     protected void validateBegin() throws JspTagException JavaDoc {
440         if (begin < 0)
441             throw new JspTagException JavaDoc("'begin' < 0");
442     }
443
444     /**
445      * Ensures the "end" property is sensible, throwing an exception
446      * expected to propagate up if it isn't
447      */

448     protected void validateEnd() throws JspTagException JavaDoc {
449         if (end < 0)
450             throw new JspTagException JavaDoc("'end' < 0");
451     }
452
453     /**
454      * Ensures the "step" property is sensible, throwing an exception
455      * expected to propagate up if it isn't
456      */

457     protected void validateStep() throws JspTagException JavaDoc {
458         if (step < 1)
459             throw new JspTagException JavaDoc("'step' <= 0");
460     }
461
462
463     //*********************************************************************
464
// Private utility methods
465

466     /** (Re)initializes state (during release() or construction) */
467     private void init() {
468         // defaults for internal bookkeeping
469
index = 0; // internal index always starts at 0
470
count = 1; // internal count always starts at 1
471
status = null; // we clear status on release()
472
item = null; // item will be retrieved for each round
473
last = false; // last must be set explicitly
474
beginSpecified = false; // not specified until it's specified :-)
475
endSpecified = false; // (as above)
476
stepSpecified = false; // (as above)
477

478         // defaults for interface with page author
479
begin = 0; // when not specified, 'begin' is 0 by spec.
480
end = -1; // when not specified, 'end' is not used
481
step = 1; // when not specified, 'step' is 1
482
itemId = null; // when not specified, no variable exported
483
statusId = null; // when not specified, no variable exported
484
}
485
486     /** Sets 'last' appropriately. */
487     private void calibrateLast() throws JspTagException JavaDoc {
488         /*
489          * the current round is the last one if (a) there are no remaining
490          * elements, or (b) the next one is beyond the 'end'.
491          */

492         last = !hasNext() || atEnd() ||
493             (end != -1 && (begin + index + step > end));
494     }
495
496     /**
497      * Exposes attributes (formerly scripting variables, but no longer!)
498      * if appropriate. Note that we don't really care, here, whether they're
499      * scripting variables or not.
500      */

501     private void exposeVariables() throws JspTagException JavaDoc {
502
503         /*
504          * We need to support null items returned from next(); we
505          * do this simply by passing such non-items through to the
506          * scoped variable as effectively 'null' (that is, by calling
507          * removeAttribute()).
508          *
509          * Also, just to be defensive, we handle the case of a null
510          * 'status' object as well.
511          *
512          * We call getCurrent() and getLoopStatus() (instead of just using
513          * 'item' and 'status') to bridge to subclasses correctly.
514          * A subclass can override getCurrent() or getLoopStatus() but still
515          * depend on our doStartTag() and doAfterBody(), which call this
516          * method (exposeVariables()), to expose 'item' and 'status'
517          * correctly.
518          */

519
520         if (itemId != null) {
521             if (getCurrent() == null)
522                 pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
523             else
524                 pageContext.setAttribute(itemId, getCurrent());
525         }
526         if (statusId != null) {
527             if (getLoopStatus() == null)
528                 pageContext.removeAttribute(statusId, PageContext.PAGE_SCOPE);
529             else
530                 pageContext.setAttribute(statusId, getLoopStatus());
531         }
532
533     }
534
535     /**
536      * Removes page attributes that we have exposed and, if applicable,
537      * restores them to their prior values (and scopes).
538      */

539     private void unExposeVariables() {
540         // "nested" variables are now simply removed
541
if (itemId != null)
542             pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
543     if (statusId != null)
544         pageContext.removeAttribute(statusId, PageContext.PAGE_SCOPE);
545     }
546
547     /**
548      * Cycles through and discards up to 'n' items from the iteration.
549      * We only know "up to 'n'", not "exactly n," since we stop cycling
550      * if hasNext() returns false or if we hit the 'end' of the iteration.
551      * Note: this does not update the iteration index, since this method
552      * is intended as a behind-the-scenes operation. The index must be
553      * updated separately. (I don't really like this, but it's the simplest
554      * way to support isLast() without storing two separate inconsistent
555      * indices. We need to (a) make sure hasNext() refers to the next
556      * item we actually *want* and (b) make sure the index refers to the
557      * item associated with the *current* round, not the next one.
558      * C'est la vie.)
559      */

560     private void discard(int n) throws JspTagException JavaDoc {
561         /*
562          * copy index so we can restore it, but we need to update it
563          * as we work so that atEnd() works
564          */

565         int oldIndex = index;
566         while (n-- > 0 && !atEnd() && hasNext()) {
567             index++;
568             next();
569         }
570         index = oldIndex;
571     }
572
573     /**
574      * Discards items ignoring subsetting rules. Useful for discarding
575      * items from the beginning (i.e., to implement 'begin') where we
576      * don't want factor in the 'begin' value already.
577      */

578     private void discardIgnoreSubset(int n) throws JspTagException JavaDoc {
579     while (n-- > 0 && hasNext())
580         next();
581     }
582
583     /**
584      * Returns true if the iteration has past the 'end' index (with
585      * respect to subsetting), false otherwise. ('end' must be set
586      * for atEnd() to return true; if 'end' is not set, atEnd()
587      * always returns false.)
588      */

589     private boolean atEnd() {
590         return ((end != -1) && (begin + index >= end));
591     }
592 }
593
Popular Tags