KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > taglib > 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 org.apache.cocoon.taglib.core;
18
19 import org.apache.cocoon.environment.ObjectModelHelper;
20 import org.apache.cocoon.environment.Request;
21 import org.apache.cocoon.taglib.IterationTag;
22 import org.apache.cocoon.taglib.VarTagSupport;
23 import org.xml.sax.Attributes JavaDoc;
24 import org.xml.sax.SAXException JavaDoc;
25
26 /**
27  * <p>Cocoon taglib allows developers to write custom iteration tags by
28  * implementing the LoopTag interface. (This is not to be confused with
29  * org.apache.cocoon.taglib.IterationTag)
30  * LoopTag establishes a mechanism for iteration tags to be recognized
31  * and for type-safe communication with custom subtags.
32  * </p>
33  *
34  * <p>Since most iteration tags will behave identically with respect to
35  * actual iterative behavior, however, Cocoon taglib provides this
36  * base support class to facilitate implementation. Many iteration tags
37  * will extend this and merely implement the hasNext() and next() methods
38  * to provide contents for the handler to iterate over.</p>
39  *
40  * <p>In particular, this base class provides support for:</p>
41  *
42  * <ul>
43  * <li> iteration control, based on protected next() and hasNext() methods
44  * <li> subsetting (begin, end, step functionality, including validation
45  * of subset parameters for sensibility)
46  * <li> item retrieval (getCurrent())
47  * <li> status retrieval (LoopTagStatus)
48  * <li> exposing attributes (set by 'var' and 'varStatus' attributes)
49  * </ul>
50  *
51  * <p>In providing support for these tasks, LoopTagSupport contains
52  * certain control variables that act to modify the iteration. Accessors
53  * are provided for these control variables when the variables represent
54  * information needed or wanted at translation time (e.g., var, status). For
55  * other variables, accessors cannot be provided here since subclasses
56  * may differ on their implementations of how those accessors are received.
57  * For instance, one subclass might accept a String and convert it into
58  * an object of a specific type by using an expression evaluator; others
59  * might accept objects directly. Still others might not want to expose
60  * such information to outside control.</p>
61  *
62  * Migration from JSTL1.0
63  * @see javax.servlet.jsp.jstl.core.LoopTagSupport
64  *
65  * @author <a HREF="mailto:volker.schmitt@basf-it-services.com">Volker Schmitt</a>
66  * @version CVS $Id: LoopTagSupport.java 158423 2005-03-21 09:15:22Z cziegeler $
67  */

68 public abstract class LoopTagSupport extends VarTagSupport implements LoopTag, IterationTag //, TryCatchFinally
69
{
70     //*********************************************************************
71
// 'Protected' state
72

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

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

91     protected int end;
92
93     /** Iteration step ('step' attribute) */
94     protected int step;
95
96     /** Boolean flag indicating whether 'begin' was specified. */
97     protected boolean beginSpecified;
98
99     /** Boolean flag indicating whether 'end' was specified. */
100     protected boolean endSpecified;
101
102     /** Boolean flag indicating whether 'step' was specified. */
103     protected boolean stepSpecified;
104
105     /** Attribute-exposing control */
106     protected String JavaDoc statusId;
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      * getIteratorStatus().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
protected int index; // the current internal index
132
protected int count; // the iteration count
133
protected boolean last; // current round == last one?
134

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

138     /**
139      * Constructs a new LoopTagSupport. As with TagSupport, subclasses
140      * should not provide other constructors and are expected to call
141      * the superclass constructor
142      */

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

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

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

184     protected abstract boolean hasNext() throws SAXException JavaDoc;
185
186     /**
187      * <p>Prepares for a single tag invocation. Specifically, allows
188      * subclasses to prepare for calls to hasNext() and next().
189      * Subclasses can assume that prepare() will be called once for
190      * each invocation of doStartTag() in the superclass.</p>
191      *
192      * @exception org.xml.sax.SAXException
193      */

194     protected abstract void prepare() throws SAXException JavaDoc;
195
196     //*********************************************************************
197
// Lifecycle management and implementation of iterative behavior
198

199     // Releases any resources we may have (or inherit)
200
public void recycle() {
201         unExposeVariables(); // XXX if doFinally is supported this can removed
202
init();
203         super.recycle();
204     }
205
206     // Begins iterating by processing the first item.
207
public int doStartTag(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts) throws SAXException JavaDoc {
208
209         // make sure 'begin' isn't greater than 'end'
210
if (end != -1 && begin > end)
211             throw new SAXException JavaDoc("begin (" + begin + ") > end (" + end + ")");
212
213         // we're beginning a new iteration, so reset our counts (etc.)
214
index = 0;
215         count = 1;
216         last = false;
217
218         // let the subclass conduct any necessary preparation
219
prepare();
220
221         // throw away the first 'begin' items (if they exist)
222
discardIgnoreSubset(begin);
223
224         // get the item we're interested in
225
if (hasNext())
226             // index is 0-based, so we don't update it for the first item
227
item = next();
228         else
229             return SKIP_BODY;
230
231         /*
232          * now discard anything we have to "step" over.
233          * (we do this in advance to support LoopTagStatus.isLast())
234          */

235         discard(step - 1);
236
237         // prepare to include our body...
238
exposeVariables();
239         calibrateLast();
240         return EVAL_BODY;
241     }
242
243     /*
244      * Continues the iteration when appropriate -- that is, if we (a) have
245      * more items and (b) don't run over our 'end' (given our 'step').
246      */

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

266         discard(step - 1);
267
268         // prepare to re-iterate...
269
exposeVariables();
270         calibrateLast();
271         return EVAL_BODY_AGAIN;
272     }
273
274     /*
275      * Removes attributes that our tag set; these attributes are intended
276      * to support scripting variables with NESTED scope, so we don't want
277      * to pollute attribute space by leaving them lying around.
278      */

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

284         unExposeVariables();
285     }
286
287     /*
288      * Be transparent with respect to exceptions: rethrow anything we get.
289      */

290     public void doCatch(Throwable JavaDoc t) throws Throwable JavaDoc {
291         throw t;
292     }
293
294     //*********************************************************************
295
// Accessor methods
296

297     /*
298      * Overview: The getXXX() methods we provide implement the Tag
299      * contract. setXXX() accessors are provided only for those
300      * properties (attributes) that must be known at translation time,
301      * on the premise that these accessors will vary less than the
302      * others in terms of their interface with the page author.
303      */

304
305     /*
306      * (Purposely inherit JavaDoc and semantics from LoopTag.
307      * Subclasses can override this if necessary, but such a need is
308      * expected to be rare.)
309      */

310     public Object JavaDoc getCurrent() {
311         return item;
312     }
313
314     /*
315      * (Purposely inherit JavaDoc and semantics from LoopTag.
316      * Subclasses can override this method for more fine-grained control
317      * over LoopTagStatus, but an effort has been made to simplify
318      * implementation of subclasses that are happy with reasonable default
319      * behavior.)
320      */

321     public LoopTagStatus getIteratorStatus() {
322
323         // local implementation with reasonable default behavior
324
class Status implements LoopTagStatus {
325
326             /*
327              * All our methods are straightforward. We inherit
328              * our JavaDoc from LoopTagSupport; see that class
329              * for more information.
330              */

331
332             public Object JavaDoc getCurrent() {
333                 /*
334                  * Access the item through getCurrent() instead of just
335                  * returning the item our containing class stores. This
336                  * should allow a subclass of LoopTagSupport to override
337                  * getCurrent() without having to rewrite getIteratorStatus() too.
338                  */

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

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

395
396
397     // for tag attribute
398
public void setVarStatus(String JavaDoc statusId) {
399         this.statusId = statusId;
400     }
401
402     //*********************************************************************
403
// Protected utility methods
404

405     /*
406      * These methods validate attributes common to iteration tags.
407      * Call them if your own subclassing implementation modifies them
408      * -- e.g., if you set them through an expression language.
409      */

410
411     /**
412      * Ensures the "begin" property is sensible, throwing an exception
413      * expected to propagate up if it isn't
414      */

415     protected void validateBegin() throws SAXException JavaDoc {
416         if (begin < 0)
417             throw new SAXException JavaDoc("'begin' < 0");
418     }
419
420     /**
421      * Ensures the "end" property is sensible, throwing an exception
422      * expected to propagate up if it isn't
423      */

424     protected void validateEnd() throws SAXException JavaDoc {
425         if (end < 0)
426             throw new SAXException JavaDoc("'end' < 0");
427     }
428
429     /**
430      * Ensures the "step" property is sensible, throwing an exception
431      * expected to propagate up if it isn't
432      */

433     protected void validateStep() throws SAXException JavaDoc {
434         if (step < 1)
435             throw new SAXException JavaDoc("'step' <= 0");
436     }
437
438     //*********************************************************************
439
// Private utility methods
440

441     /** (Re)initializes state (during release() or construction) */
442     private void init() {
443         // defaults for internal bookkeeping
444
index = 0; // internal index always starts at 0
445
count = 1; // internal count always starts at 1
446
status = null; // we clear status on release()
447
item = null; // item will be retrieved for each round
448
last = false; // last must be set explicitly
449
beginSpecified = false; // not specified until it's specified :-)
450
endSpecified = false; // (as above)
451
stepSpecified = false; // (as above)
452

453         // defaults for interface with page author
454
begin = 0; // when not specified, 'begin' is 0 by spec.
455
end = -1; // when not specified, 'end' is not used
456
step = 1; // when not specified, 'step' is 1
457
statusId = null; // when not specified, no variable exported
458
}
459
460     /** Sets 'last' appropriately. */
461     private void calibrateLast() throws SAXException JavaDoc {
462         /*
463          * the current round is the last one if (a) there are no remaining
464          * elements, or (b) the next one is beyond the 'end'.
465          */

466         last = !hasNext() || atEnd() || (end != -1 && (begin + index + step > end));
467     }
468
469     /**
470      * Exposes attributes (formerly scripting variables, but no longer!)
471      * if appropriate. Note that we don't really care, here, whether they're
472      * scripting variables or not.
473      */

474     private void exposeVariables() throws SAXException JavaDoc {
475
476         /*
477          * We need to support null items returned from next(); we
478          * do this simply by passing such non-items through to the
479          * scoped variable as effectively 'null' (that is, by calling
480          * removeAttribute()).
481          *
482          * Also, just to be defensive, we handle the case of a null
483          * 'status' object as well.
484          *
485          * We call getCurrent() and getIteratorStatus() (instead of just using
486          * 'item' and 'status') to bridge to subclasses correctly.
487          * A subclass can override getCurrent() or getIteratorStatus() but still
488          * depend on our doStartTag() and doAfterBody(), which call this
489          * method (exposeVariables()), to expose 'item' and 'status'
490          * correctly.
491          */

492
493         if (var != null) {
494             if (getCurrent() == null)
495                 removeVariable(var);
496             else
497                 setVariable(var, getCurrent());
498         }
499         if (statusId != null) {
500             if (getIteratorStatus() == null)
501                 removeVariable(statusId);
502             else
503                 setVariable(statusId, getIteratorStatus());
504         }
505
506     }
507
508     /**
509      * Removes page attributes that we have exposed and, if applicable,
510      * restores them to their prior values (and scopes).
511      */

512     private void unExposeVariables() {
513         // "nested" variables are now simply removed
514
Request request = ObjectModelHelper.getRequest(objectModel);
515         if (var != null)
516             request.removeAttribute(var);
517         if (statusId != null)
518             request.removeAttribute(statusId);
519     }
520
521     /**
522      * Cycles through and discards up to 'n' items from the iteration.
523      * We only know "up to 'n'", not "exactly n," since we stop cycling
524      * if hasNext() returns false or if we hit the 'end' of the iteration.
525      * Note: this does not update the iteration index, since this method
526      * is intended as a behind-the-scenes operation. The index must be
527      * updated separately. (I don't really like this, but it's the simplest
528      * way to support isLast() without storing two separate inconsistent
529      * indices. We need to (a) make sure hasNext() refers to the next
530      * item we actually *want* and (b) make sure the index refers to the
531      * item associated with the *current* round, not the next one.
532      * C'est la vie.)
533      */

534     private void discard(int n) throws SAXException JavaDoc {
535         /*
536          * copy index so we can restore it, but we need to update it
537          * as we work so that atEnd() works
538          */

539         int oldIndex = index;
540         while (n-- > 0 && !atEnd() && hasNext()) {
541             index++;
542             next();
543         }
544         index = oldIndex;
545     }
546
547     /**
548      * Discards items ignoring subsetting rules. Useful for discarding
549      * items from the beginning (i.e., to implement 'begin') where we
550      * don't want factor in the 'begin' value already.
551      */

552     private void discardIgnoreSubset(int n) throws SAXException JavaDoc {
553         while (n-- > 0 && hasNext())
554             next();
555     }
556
557     /**
558      * Returns true if the iteration has past the 'end' index (with
559      * respect to subsetting), false otherwise. ('end' must be set
560      * for atEnd() to return true; if 'end' is not set, atEnd()
561      * always returns false.)
562      */

563     private boolean atEnd() {
564         return ((end != -1) && (begin + index >= end));
565     }
566 }
567
Popular Tags