001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.cocoon.taglib.core;
019:
020: import org.apache.cocoon.environment.ObjectModelHelper;
021: import org.apache.cocoon.environment.Request;
022: import org.apache.cocoon.taglib.IterationTag;
023: import org.apache.cocoon.taglib.VarTagSupport;
024: import org.xml.sax.Attributes;
025: import org.xml.sax.SAXException;
026:
027: /**
028: * <p>Cocoon taglib allows developers to write custom iteration tags by
029: * implementing the LoopTag interface. (This is not to be confused with
030: * org.apache.cocoon.taglib.IterationTag)
031: * LoopTag establishes a mechanism for iteration tags to be recognized
032: * and for type-safe communication with custom subtags.
033: * </p>
034: *
035: * <p>Since most iteration tags will behave identically with respect to
036: * actual iterative behavior, however, Cocoon taglib provides this
037: * base support class to facilitate implementation. Many iteration tags
038: * will extend this and merely implement the hasNext() and next() methods
039: * to provide contents for the handler to iterate over.</p>
040: *
041: * <p>In particular, this base class provides support for:</p>
042: *
043: * <ul>
044: * <li> iteration control, based on protected next() and hasNext() methods
045: * <li> subsetting (begin, end, step functionality, including validation
046: * of subset parameters for sensibility)
047: * <li> item retrieval (getCurrent())
048: * <li> status retrieval (LoopTagStatus)
049: * <li> exposing attributes (set by 'var' and 'varStatus' attributes)
050: * </ul>
051: *
052: * <p>In providing support for these tasks, LoopTagSupport contains
053: * certain control variables that act to modify the iteration. Accessors
054: * are provided for these control variables when the variables represent
055: * information needed or wanted at translation time (e.g., var, status). For
056: * other variables, accessors cannot be provided here since subclasses
057: * may differ on their implementations of how those accessors are received.
058: * For instance, one subclass might accept a String and convert it into
059: * an object of a specific type by using an expression evaluator; others
060: * might accept objects directly. Still others might not want to expose
061: * such information to outside control.</p>
062: *
063: * Migration from JSTL1.0
064: * @see javax.servlet.jsp.jstl.core.LoopTagSupport
065: *
066: * @author <a href="mailto:volker.schmitt@basf-it-services.com">Volker Schmitt</a>
067: * @version CVS $Id: LoopTagSupport.java 433543 2006-08-22 06:22:54Z crossley $
068: */
069: public abstract class LoopTagSupport extends VarTagSupport implements
070: LoopTag, IterationTag //, TryCatchFinally
071: {
072: //*********************************************************************
073: // 'Protected' state
074:
075: /*
076: * JavaBean-style properties and other state slaved to them. These
077: * properties can be set directly by accessors; they will not be
078: * modified by the LoopTagSupport implementation -- and should
079: * not be modified by subclasses outside accessors unless those
080: * subclasses are perfectly aware of what they're doing.
081: * (An example where such non-accessor modification might be sensible
082: * is in the doStartTag() method of an EL-aware subclass.)
083: */
084:
085: /** Starting index ('begin' attribute) */
086: protected int begin;
087:
088: /**
089: * Ending index ('end' attribute). -1 internally indicates 'no end
090: * specified', although accessors for the core JSTL tags do not
091: * allow this value to be supplied directly by the user.
092: */
093: protected int end;
094:
095: /** Iteration step ('step' attribute) */
096: protected int step;
097:
098: /** Boolean flag indicating whether 'begin' was specified. */
099: protected boolean beginSpecified;
100:
101: /** Boolean flag indicating whether 'end' was specified. */
102: protected boolean endSpecified;
103:
104: /** Boolean flag indicating whether 'step' was specified. */
105: protected boolean stepSpecified;
106:
107: /** Attribute-exposing control */
108: protected String statusId;
109:
110: //*********************************************************************
111: // 'Private' state (implementation details)
112:
113: /*
114: * State exclusively internal to the default, reference implementation.
115: * (While this state is kept private to ensure consistency, 'status'
116: * and 'item' happen to have one-for-one, read-only, accesor methods
117: * as part of the LoopTag interface.)
118: *
119: * 'last' is kept separately for two reasons: (a) to avoid
120: * running a computation every time it's requested, and (b) to
121: * let LoopTagStatus.isLast() avoid throwing any exceptions,
122: * which would complicate subtag and scripting-variable use.
123: *
124: * Our 'internal index' begins at 0 and increases by 'step' each
125: * round; this is arbitrary, but it seemed a simple way of keeping
126: * track of the information we need. To avoid computing
127: * getIteratorStatus().getCount() by dividing index / step, we keep
128: * a separate 'count' and increment it by 1 each round (as a minor
129: * performance improvement).
130: */
131: private LoopTagStatus status; // our LoopTagStatus
132: private Object item; // the current item
133: protected int index; // the current internal index
134: protected int count; // the iteration count
135: protected boolean last; // current round == last one?
136:
137: //*********************************************************************
138: // Constructor
139:
140: /**
141: * Constructs a new LoopTagSupport. As with TagSupport, subclasses
142: * should not provide other constructors and are expected to call
143: * the superclass constructor
144: */
145: public LoopTagSupport() {
146: super ();
147: init();
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 container as a SAXException; 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 org.xml.sax.SAXException
168: * for other, unexpected exceptions
169: */
170: protected abstract Object next() throws SAXException;
171:
172: /**
173: * <p>Returns information concerning the availability of more items
174: * over which to iterate. This method must be provided by concrete
175: * subclasses of LoopTagSupport to assist the iterative logic
176: * provided by the supporting base class.</p>
177: *
178: * <p>See <a href="#next()">next</a> for more information about the
179: * purpose and expectations behind this tag.</p>
180: *
181: * @return <tt>true</tt> if there is at least one more item to iterate
182: * over, <tt>false</tt> otherwise
183: * @exception org.xml.sax.SAXException
184: * @see #next()
185: */
186: protected abstract boolean hasNext() throws SAXException;
187:
188: /**
189: * <p>Prepares for a single tag invocation. Specifically, allows
190: * subclasses to prepare for calls to hasNext() and next().
191: * Subclasses can assume that prepare() will be called once for
192: * each invocation of doStartTag() in the superclass.</p>
193: *
194: * @exception org.xml.sax.SAXException
195: */
196: protected abstract void prepare() throws SAXException;
197:
198: //*********************************************************************
199: // Lifecycle management and implementation of iterative behavior
200:
201: // Releases any resources we may have (or inherit)
202: public void recycle() {
203: unExposeVariables(); // XXX if doFinally is supported this can removed
204: init();
205: super .recycle();
206: }
207:
208: // Begins iterating by processing the first item.
209: public int doStartTag(String namespaceURI, String localName,
210: String qName, Attributes atts) throws SAXException {
211:
212: // make sure 'begin' isn't greater than 'end'
213: if (end != -1 && begin > end)
214: throw new SAXException("begin (" + begin + ") > end ("
215: + end + ")");
216:
217: // we're beginning a new iteration, so reset our counts (etc.)
218: index = 0;
219: count = 1;
220: last = false;
221:
222: // let the subclass conduct any necessary preparation
223: prepare();
224:
225: // throw away the first 'begin' items (if they exist)
226: discardIgnoreSubset(begin);
227:
228: // get the item we're interested in
229: if (hasNext())
230: // index is 0-based, so we don't update it for the first item
231: item = next();
232: else
233: return SKIP_BODY;
234:
235: /*
236: * now discard anything we have to "step" over.
237: * (we do this in advance to support LoopTagStatus.isLast())
238: */
239: discard(step - 1);
240:
241: // prepare to include our body...
242: exposeVariables();
243: calibrateLast();
244: return EVAL_BODY;
245: }
246:
247: /*
248: * Continues the iteration when appropriate -- that is, if we (a) have
249: * more items and (b) don't run over our 'end' (given our 'step').
250: */
251: public int doAfterBody() throws SAXException {
252:
253: // re-sync the index, given our prior behind-the-scenes 'step'
254: index += step - 1;
255:
256: // increment the count by 1 for each round
257: count++;
258:
259: // everything's been prepared for us, so just get the next item
260: if (hasNext() && !atEnd()) {
261: index++;
262: item = next();
263: } else
264: return SKIP_BODY;
265:
266: /*
267: * now discard anything we have to "step" over.
268: * (we do this in advance to support LoopTagStatus.isLast())
269: */
270: discard(step - 1);
271:
272: // prepare to re-iterate...
273: exposeVariables();
274: calibrateLast();
275: return EVAL_BODY_AGAIN;
276: }
277:
278: /*
279: * Removes attributes that our tag set; these attributes are intended
280: * to support scripting variables with NESTED scope, so we don't want
281: * to pollute attribute space by leaving them lying around.
282: */
283: public void doFinally() {
284: /*
285: * Make sure to un-expose variables, restoring them to their
286: * prior values, if applicable.
287: */
288: unExposeVariables();
289: }
290:
291: /*
292: * Be transparent with respect to exceptions: rethrow anything we get.
293: */
294: public void doCatch(Throwable t) throws Throwable {
295: throw t;
296: }
297:
298: //*********************************************************************
299: // Accessor methods
300:
301: /*
302: * Overview: The getXXX() methods we provide implement the Tag
303: * contract. setXXX() accessors are provided only for those
304: * properties (attributes) that must be known at translation time,
305: * on the premise that these accessors will vary less than the
306: * others in terms of their interface with the page author.
307: */
308:
309: /*
310: * (Purposely inherit JavaDoc and semantics from LoopTag.
311: * Subclasses can override this if necessary, but such a need is
312: * expected to be rare.)
313: */
314: public Object getCurrent() {
315: return item;
316: }
317:
318: /*
319: * (Purposely inherit JavaDoc and semantics from LoopTag.
320: * Subclasses can override this method for more fine-grained control
321: * over LoopTagStatus, but an effort has been made to simplify
322: * implementation of subclasses that are happy with reasonable default
323: * behavior.)
324: */
325: public LoopTagStatus getIteratorStatus() {
326:
327: // local implementation with reasonable default behavior
328: class Status implements LoopTagStatus {
329:
330: /*
331: * All our methods are straightforward. We inherit
332: * our JavaDoc from LoopTagSupport; see that class
333: * for more information.
334: */
335:
336: public Object getCurrent() {
337: /*
338: * Access the item through getCurrent() instead of just
339: * returning the item our containing class stores. This
340: * should allow a subclass of LoopTagSupport to override
341: * getCurrent() without having to rewrite getIteratorStatus() too.
342: */
343: return (LoopTagSupport.this .getCurrent());
344: }
345:
346: public int getIndex() {
347: return index + begin; // our 'index' isn't getIndex()
348: }
349:
350: public int getCount() {
351: return count;
352: }
353:
354: public boolean isFirst() {
355: return (index == 0); // our 'index' isn't getIndex()
356: }
357:
358: public boolean isLast() {
359: return (last); // use cached value
360: }
361:
362: public Integer getBegin() {
363: if (beginSpecified) {
364: return (new Integer(begin));
365: }
366: return null;
367: }
368:
369: public Integer getEnd() {
370: if (endSpecified) {
371: return (new Integer(end));
372: }
373: return null;
374: }
375:
376: public Integer getStep() {
377: if (stepSpecified) {
378: return (new Integer(step));
379: }
380: return null;
381: }
382: }
383:
384: /*
385: * We just need one per invocation... Actually, for the current
386: * implementation, we just need one per instance, but I'd rather
387: * not keep the reference around once release() has been called.
388: */
389: if (status == null) {
390: status = new Status();
391: }
392:
393: return status;
394: }
395:
396: /*
397: * We only support setter methods for attributes that need to be
398: * offered as Strings or other literals; other attributes will be
399: * handled directly by implementing classes, since there might be
400: * both rtexprvalue- and EL-based varieties, which will have
401: * different signatures. (We can't pollute child classes by having
402: * base implementations of those setters here; child classes that
403: * have attributes with different signatures would end up having
404: * two incompatible setters, which is illegal for a JavaBean.
405: */
406:
407: // for tag attribute
408: public void setVarStatus(String statusId) {
409: this .statusId = statusId;
410: }
411:
412: //*********************************************************************
413: // Protected utility methods
414:
415: /*
416: * These methods validate attributes common to iteration tags.
417: * Call them if your own subclassing implementation modifies them
418: * -- e.g., if you set them through an expression language.
419: */
420:
421: /**
422: * Ensures the "begin" property is sensible, throwing an exception
423: * expected to propagate up if it isn't
424: */
425: protected void validateBegin() throws SAXException {
426: if (begin < 0)
427: throw new SAXException("'begin' < 0");
428: }
429:
430: /**
431: * Ensures the "end" property is sensible, throwing an exception
432: * expected to propagate up if it isn't
433: */
434: protected void validateEnd() throws SAXException {
435: if (end < 0)
436: throw new SAXException("'end' < 0");
437: }
438:
439: /**
440: * Ensures the "step" property is sensible, throwing an exception
441: * expected to propagate up if it isn't
442: */
443: protected void validateStep() throws SAXException {
444: if (step < 1)
445: throw new SAXException("'step' <= 0");
446: }
447:
448: //*********************************************************************
449: // Private utility methods
450:
451: /** (Re)initializes state (during release() or construction) */
452: private void init() {
453: // defaults for internal bookkeeping
454: index = 0; // internal index always starts at 0
455: count = 1; // internal count always starts at 1
456: status = null; // we clear status on release()
457: item = null; // item will be retrieved for each round
458: last = false; // last must be set explicitly
459: beginSpecified = false; // not specified until it's specified :-)
460: endSpecified = false; // (as above)
461: stepSpecified = false; // (as above)
462:
463: // defaults for interface with page author
464: begin = 0; // when not specified, 'begin' is 0 by spec.
465: end = -1; // when not specified, 'end' is not used
466: step = 1; // when not specified, 'step' is 1
467: statusId = null; // when not specified, no variable exported
468: }
469:
470: /** Sets 'last' appropriately. */
471: private void calibrateLast() throws SAXException {
472: /*
473: * the current round is the last one if (a) there are no remaining
474: * elements, or (b) the next one is beyond the 'end'.
475: */
476: last = !hasNext() || atEnd()
477: || (end != -1 && (begin + index + step > end));
478: }
479:
480: /**
481: * Exposes attributes (formerly scripting variables, but no longer!)
482: * if appropriate. Note that we don't really care, here, whether they're
483: * scripting variables or not.
484: */
485: private void exposeVariables() throws SAXException {
486:
487: /*
488: * We need to support null items returned from next(); we
489: * do this simply by passing such non-items through to the
490: * scoped variable as effectively 'null' (that is, by calling
491: * removeAttribute()).
492: *
493: * Also, just to be defensive, we handle the case of a null
494: * 'status' object as well.
495: *
496: * We call getCurrent() and getIteratorStatus() (instead of just using
497: * 'item' and 'status') to bridge to subclasses correctly.
498: * A subclass can override getCurrent() or getIteratorStatus() but still
499: * depend on our doStartTag() and doAfterBody(), which call this
500: * method (exposeVariables()), to expose 'item' and 'status'
501: * correctly.
502: */
503:
504: if (var != null) {
505: if (getCurrent() == null)
506: removeVariable(var);
507: else
508: setVariable(var, getCurrent());
509: }
510: if (statusId != null) {
511: if (getIteratorStatus() == null)
512: removeVariable(statusId);
513: else
514: setVariable(statusId, getIteratorStatus());
515: }
516:
517: }
518:
519: /**
520: * Removes page attributes that we have exposed and, if applicable,
521: * restores them to their prior values (and scopes).
522: */
523: private void unExposeVariables() {
524: // "nested" variables are now simply removed
525: Request request = ObjectModelHelper.getRequest(objectModel);
526: if (var != null)
527: request.removeAttribute(var);
528: if (statusId != null)
529: request.removeAttribute(statusId);
530: }
531:
532: /**
533: * Cycles through and discards up to 'n' items from the iteration.
534: * We only know "up to 'n'", not "exactly n," since we stop cycling
535: * if hasNext() returns false or if we hit the 'end' of the iteration.
536: * Note: this does not update the iteration index, since this method
537: * is intended as a behind-the-scenes operation. The index must be
538: * updated separately. (I don't really like this, but it's the simplest
539: * way to support isLast() without storing two separate inconsistent
540: * indices. We need to (a) make sure hasNext() refers to the next
541: * item we actually *want* and (b) make sure the index refers to the
542: * item associated with the *current* round, not the next one.
543: * C'est la vie.)
544: */
545: private void discard(int n) throws SAXException {
546: /*
547: * copy index so we can restore it, but we need to update it
548: * as we work so that atEnd() works
549: */
550: int oldIndex = index;
551: while (n-- > 0 && !atEnd() && hasNext()) {
552: index++;
553: next();
554: }
555: index = oldIndex;
556: }
557:
558: /**
559: * Discards items ignoring subsetting rules. Useful for discarding
560: * items from the beginning (i.e., to implement 'begin') where we
561: * don't want factor in the 'begin' value already.
562: */
563: private void discardIgnoreSubset(int n) throws SAXException {
564: while (n-- > 0 && hasNext())
565: next();
566: }
567:
568: /**
569: * Returns true if the iteration has past the 'end' index (with
570: * respect to subsetting), false otherwise. ('end' must be set
571: * for atEnd() to return true; if 'end' is not set, atEnd()
572: * always returns false.)
573: */
574: private boolean atEnd() {
575: return ((end != -1) && (begin + index >= end));
576: }
577: }
|