001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Portions Copyright Apache Software Foundation.
007: *
008: * The contents of this file are subject to the terms of either the GNU
009: * General Public License Version 2 only ("GPL") or the Common Development
010: * and Distribution License("CDDL") (collectively, the "License"). You
011: * may not use this file except in compliance with the License. You can obtain
012: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
013: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
014: * language governing permissions and limitations under the License.
015: *
016: * When distributing the software, include this License Header Notice in each
017: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
018: * Sun designates this particular file as subject to the "Classpath" exception
019: * as provided by Sun in the GPL Version 2 section of the License file that
020: * accompanied this code. If applicable, add the following below the License
021: * Header, with the fields enclosed by brackets [] replaced by your own
022: * identifying information: "Portions Copyrighted [year]
023: * [name of copyright owner]"
024: *
025: * Contributor(s):
026: *
027: * If you wish your version of this file to be governed by only the CDDL or
028: * only the GPL Version 2, indicate your decision by adding "[Contributor]
029: * elects to include this software in this distribution under the [CDDL or GPL
030: * Version 2] license." If you don't indicate a single choice of license, a
031: * recipient has the option to distribute your version of this file under
032: * either the CDDL, the GPL Version 2 or to extend the choice of license to
033: * its licensees as provided above. However, if you add GPL Version 2 code
034: * and therefore, elected the GPL Version 2 license, then the option applies
035: * only if the new code is made subject to such option by the copyright
036: * holder.
037: */
038:
039: package javax.servlet.jsp.jstl.core;
040:
041: import java.util.List;
042: import java.util.Collection;
043: import java.util.Enumeration;
044: import java.util.Map;
045: import java.util.Iterator;
046:
047: import javax.el.ValueExpression;
048: import javax.el.VariableMapper;
049: import javax.el.ELException;
050:
051: import javax.servlet.jsp.JspException;
052: import javax.servlet.jsp.JspTagException;
053: import javax.servlet.jsp.PageContext;
054: import javax.servlet.jsp.tagext.IterationTag;
055: import javax.servlet.jsp.tagext.TagSupport;
056: import javax.servlet.jsp.tagext.TryCatchFinally;
057:
058: /**
059: * <p>Base support class to facilitate implementation of iteration tags.</p>
060: *
061: * <p>Since most iteration tags will behave identically with respect to
062: * actual iterative behavior, JSTL provides this
063: * base support class to facilitate implementation. Many iteration tags
064: * will extend this and merely implement the <tt>hasNext()</tt> and
065: * <tt>next()</tt> methods
066: * to provide contents for the handler to iterate over.</p>
067: *
068: * <p>In particular, this base class provides support for:</p>
069: *
070: * <ul>
071: * <li> Iteration control, based on protected <tt>prepare()</tt>, <tt>next()</tt>,
072: * and <tt>hasNext()</tt> methods
073: * <li> Subsetting (<tt>begin</tt>, <tt>end</tt>, <tt>step></tt>functionality,
074: * including validation
075: * of subset parameters for sensibility)
076: * <li> item retrieval (<tt>getCurrent()</tt>)
077: * <li> status retrieval (<tt>LoopTagStatus</tt>)
078: * <li> exposing attributes (set by <tt>var</tt> and <tt>varStatus</tt> attributes)
079: * </ul>
080: *
081: * <p>In providing support for these tasks, <tt>LoopTagSupport</tt> contains
082: * certain control variables that act to modify the iteration. Accessors
083: * are provided for these control variables when the variables represent
084: * information needed or wanted at translation time (e.g., <tt>var</tt>,
085: * <tt>varStatus</tt>). For
086: * other variables, accessors cannot be provided here since subclasses
087: * may differ on their implementations of how those accessors are received.
088: * For instance, one subclass might accept a <tt>String</tt> and convert it into
089: * an object of a specific type by using an expression evaluator; others
090: * might accept objects directly. Still others might not want to expose
091: * such information to outside control.</p>
092: *
093: * @author Shawn Bayern
094: */
095:
096: public abstract class LoopTagSupport extends TagSupport implements
097: LoopTag, IterationTag, TryCatchFinally {
098: //*********************************************************************
099: // 'Protected' state
100:
101: /*
102: * JavaBean-style properties and other state slaved to them. These
103: * properties can be set directly by accessors; they will not be
104: * modified by the LoopTagSupport implementation -- and should
105: * not be modified by subclasses outside accessors unless those
106: * subclasses are perfectly aware of what they're doing.
107: * (An example where such non-accessor modification might be sensible
108: * is in the doStartTag() method of an EL-aware subclass.)
109: */
110:
111: /** Starting index ('begin' attribute) */
112: protected int begin;
113:
114: /**
115: * Ending index of the iteration ('end' attribute).
116: * A value of -1 internally indicates 'no end
117: * specified', although accessors for the core JSTL tags do not
118: * allow this value to be supplied directly by the user.
119: */
120: protected int end;
121:
122: /** Iteration step ('step' attribute) */
123: protected int step;
124:
125: /** Boolean flag indicating whether 'begin' was specified. */
126: protected boolean beginSpecified;
127:
128: /** Boolean flag indicating whether 'end' was specified. */
129: protected boolean endSpecified;
130:
131: /** Boolean flag indicating whether 'step' was specified. */
132: protected boolean stepSpecified;
133:
134: /** Attribute-exposing control */
135: protected String itemId, statusId;
136:
137: /** The deferred expression if any */
138: protected ValueExpression deferredExpression;
139:
140: /** A temporary used to hold the previous value (from the enclosing
141: iteration tag) for the EL variable. */
142: private ValueExpression oldMappedValue;
143:
144: //*********************************************************************
145: // 'Private' state (implementation details)
146:
147: /*
148: * State exclusively internal to the default, reference implementation.
149: * (While this state is kept private to ensure consistency, 'status'
150: * and 'item' happen to have one-for-one, read-only, accesor methods
151: * as part of the LoopTag interface.)
152: *
153: * 'last' is kept separately for two reasons: (a) to avoid
154: * running a computation every time it's requested, and (b) to
155: * let LoopTagStatus.isLast() avoid throwing any exceptions,
156: * which would complicate subtag and scripting-variable use.
157: *
158: * Our 'internal index' begins at 0 and increases by 'step' each
159: * round; this is arbitrary, but it seemed a simple way of keeping
160: * track of the information we need. To avoid computing
161: * getLoopStatus().getCount() by dividing index / step, we keep
162: * a separate 'count' and increment it by 1 each round (as a minor
163: * performance improvement).
164: */
165: private LoopTagStatus status; // our LoopTagStatus
166: private Object item; // the current item
167: private int index; // the current internal index
168: private int count; // the iteration count
169: private boolean last; // current round == last one?
170: private IteratedExpression iteratedExpression;
171:
172: // holds an instance shared by all ValueExpression created
173: // for variableMapper, for iterators.
174:
175: //*********************************************************************
176: // Constructor
177:
178: /**
179: * Constructs a new LoopTagSupport. As with TagSupport, subclasses
180: * should not implement constructors with arguments, and no-arguments
181: * constructors implemented by subclasses must call the superclass
182: * constructor.
183: */
184: public LoopTagSupport() {
185: super ();
186: init();
187: }
188:
189: //*********************************************************************
190: // Abstract methods
191:
192: /**
193: * <p>Returns the next object over which the tag should iterate. This
194: * method must be provided by concrete subclasses of LoopTagSupport
195: * to inform the base logic about what objects it should iterate over.</p>
196: *
197: * <p>It is expected that this method will generally be backed by an
198: * Iterator, but this will not always be the case. In particular, if
199: * retrieving the next object raises the possibility of an exception
200: * being thrown, this method allows that exception to propagate back
201: * to the JSP container as a JspTagException; a standalone Iterator
202: * would not be able to do this. (This explains why LoopTagSupport
203: * does not simply call for an Iterator from its subtags.)</p>
204: *
205: * @return the java.lang.Object to use in the next round of iteration
206: * @exception java.util.NoSuchElementException
207: * if next() is called but no new elements are available
208: * @exception javax.servlet.jsp.JspTagException
209: * for other, unexpected exceptions
210: */
211: protected abstract Object next() throws JspTagException;
212:
213: /**
214: * <p>Returns information concerning the availability of more items
215: * over which to iterate. This method must be provided by concrete
216: * subclasses of LoopTagSupport to assist the iterative logic
217: * provided by the supporting base class.</p>
218: *
219: * <p>See <a href="#next()">next</a> for more information about the
220: * purpose and expectations behind this tag.</p>
221: *
222: * @return <tt>true</tt> if there is at least one more item to iterate
223: * over, <tt>false</tt> otherwise
224: * @exception javax.servlet.jsp.JspTagException
225: * @see #next
226: */
227: protected abstract boolean hasNext() throws JspTagException;
228:
229: /**
230: * <p>Prepares for a single tag invocation. Specifically, allows
231: * subclasses to prepare for calls to hasNext() and next().
232: * Subclasses can assume that prepare() will be called once for
233: * each invocation of doStartTag() in the superclass.</p>
234: *
235: * @exception javax.servlet.jsp.JspTagException
236: */
237: protected abstract void prepare() throws JspTagException;
238:
239: //*********************************************************************
240: // Lifecycle management and implementation of iterative behavior
241:
242: /**
243: * Releases any resources this LoopTagSupport may have (or inherit).
244: */
245: public void release() {
246: super .release();
247: init();
248: }
249:
250: /**
251: * Begins iterating by processing the first item.
252: */
253: public int doStartTag() throws JspException {
254: if (end != -1 && begin > end) {
255: // JSTL 1.1. We simply do not execute the loop.
256: return SKIP_BODY;
257: }
258:
259: // we're beginning a new iteration, so reset our counts (etc.)
260: index = 0;
261: count = 1;
262: last = false;
263: iteratedExpression = null;
264: deferredExpression = null;
265:
266: // let the subclass conduct any necessary preparation
267: prepare();
268:
269: // throw away the first 'begin' items (if they exist)
270: discardIgnoreSubset(begin);
271:
272: // get the item we're interested in
273: if (hasNext())
274: // index is 0-based, so we don't update it for the first item
275: item = next();
276: else
277: return SKIP_BODY;
278:
279: /*
280: * now discard anything we have to "step" over.
281: * (we do this in advance to support LoopTagStatus.isLast())
282: */
283: discard(step - 1);
284:
285: // prepare to include our body...
286: exposeVariables(true);
287: calibrateLast();
288: return EVAL_BODY_INCLUDE;
289: }
290:
291: /**
292: * Continues the iteration when appropriate -- that is, if we (a) have
293: * more items and (b) don't run over our 'end' (given our 'step').
294: */
295: public int doAfterBody() throws JspException {
296:
297: // re-sync the index, given our prior behind-the-scenes 'step'
298: index += step - 1;
299:
300: // increment the count by 1 for each round
301: count++;
302:
303: // everything's been prepared for us, so just get the next item
304: if (hasNext() && !atEnd()) {
305: index++;
306: item = next();
307: } else
308: return SKIP_BODY;
309:
310: /*
311: * now discard anything we have to "step" over.
312: * (we do this in advance to support LoopTagStatus.isLast())
313: */
314: discard(step - 1);
315:
316: // prepare to re-iterate...
317: exposeVariables(false);
318: calibrateLast();
319: return EVAL_BODY_AGAIN;
320: }
321:
322: /**
323: * Removes any attributes that this LoopTagSupport set.
324: *
325: * <p> These attributes are intended to support scripting variables with
326: * NESTED scope, so we don't want to pollute attribute space by leaving
327: * them lying around.
328: */
329: public void doFinally() {
330: /*
331: * Make sure to un-expose variables, restoring them to their
332: * prior values, if applicable.
333: */
334: unExposeVariables();
335: }
336:
337: /**
338: * Rethrows the given Throwable.
339: */
340: public void doCatch(Throwable t) throws Throwable {
341: throw t;
342: }
343:
344: //*********************************************************************
345: // Accessor methods
346:
347: /*
348: * Overview: The getXXX() methods we provide implement the Tag
349: * contract. setXXX() accessors are provided only for those
350: * properties (attributes) that must be known at translation time,
351: * on the premise that these accessors will vary less than the
352: * others in terms of their interface with the page author.
353: */
354:
355: /*
356: * (Purposely inherit JavaDoc and semantics from LoopTag.
357: * Subclasses can override this if necessary, but such a need is
358: * expected to be rare.)
359: */
360: public Object getCurrent() {
361: return item;
362: }
363:
364: /*
365: * (Purposely inherit JavaDoc and semantics from LoopTag.
366: * Subclasses can override this method for more fine-grained control
367: * over LoopTagStatus, but an effort has been made to simplify
368: * implementation of subclasses that are happy with reasonable default
369: * behavior.)
370: */
371: public LoopTagStatus getLoopStatus() {
372:
373: // local implementation with reasonable default behavior
374: class Status implements LoopTagStatus {
375:
376: /*
377: * All our methods are straightforward. We inherit
378: * our JavaDoc from LoopTagSupport; see that class
379: * for more information.
380: */
381:
382: public Object getCurrent() {
383: /*
384: * Access the item through getCurrent() instead of just
385: * returning the item our containing class stores. This
386: * should allow a subclass of LoopTagSupport to override
387: * getCurrent() without having to rewrite getLoopStatus() too.
388: */
389: return (LoopTagSupport.this .getCurrent());
390: }
391:
392: public int getIndex() {
393: return (index + begin); // our 'index' isn't getIndex()
394: }
395:
396: public int getCount() {
397: return (count);
398: }
399:
400: public boolean isFirst() {
401: return (index == 0); // our 'index' isn't getIndex()
402: }
403:
404: public boolean isLast() {
405: return (last); // use cached value
406: }
407:
408: public Integer getBegin() {
409: if (beginSpecified)
410: return Integer.valueOf(begin);
411: else
412: return null;
413: }
414:
415: public Integer getEnd() {
416: if (endSpecified)
417: return Integer.valueOf(end);
418: else
419: return null;
420: }
421:
422: public Integer getStep() {
423: if (stepSpecified)
424: return Integer.valueOf(step);
425: else
426: return null;
427: }
428: }
429:
430: /*
431: * We just need one per invocation... Actually, for the current
432: * implementation, we just need one per instance, but I'd rather
433: * not keep the reference around once release() has been called.
434: */
435: if (status == null)
436: status = new Status();
437:
438: return status;
439: }
440:
441: /*
442: * Get the delimiter for string tokens. Used only for constructing
443: * the deferred expression for it.
444: */
445: protected String getDelims() {
446: return ",";
447: }
448:
449: /*
450: * We only support setter methods for attributes that need to be
451: * offered as Strings or other literals; other attributes will be
452: * handled directly by implementing classes, since there might be
453: * both rtexprvalue- and EL-based varieties, which will have
454: * different signatures. (We can't pollute child classes by having
455: * base implementations of those setters here; child classes that
456: * have attributes with different signatures would end up having
457: * two incompatible setters, which is illegal for a JavaBean.
458: */
459:
460: /**
461: * Sets the 'var' attribute.
462: *
463: * @param id Name of the exported scoped variable storing the current item
464: * of the iteration.
465: */
466: public void setVar(String id) {
467: this .itemId = id;
468: }
469:
470: /**
471: * Sets the 'varStatus' attribute.
472: *
473: * @param statusId Name of the exported scoped variable storing the status
474: * of the iteration.
475: */
476: public void setVarStatus(String statusId) {
477: this .statusId = statusId;
478: }
479:
480: //*********************************************************************
481: // Protected utility methods
482:
483: /*
484: * These methods validate attributes common to iteration tags.
485: * Call them if your own subclassing implementation modifies them
486: * -- e.g., if you set them through an expression language.
487: */
488:
489: /**
490: * Ensures the "begin" property is sensible, throwing an exception
491: * expected to propagate up if it isn't
492: */
493: protected void validateBegin() throws JspTagException {
494: if (begin < 0)
495: throw new JspTagException("'begin' < 0");
496: }
497:
498: /**
499: * Ensures the "end" property is sensible, throwing an exception
500: * expected to propagate up if it isn't
501: */
502: protected void validateEnd() throws JspTagException {
503: if (end < 0)
504: throw new JspTagException("'end' < 0");
505: }
506:
507: /**
508: * Ensures the "step" property is sensible, throwing an exception
509: * expected to propagate up if it isn't
510: */
511: protected void validateStep() throws JspTagException {
512: if (step < 1)
513: throw new JspTagException("'step' <= 0");
514: }
515:
516: //*********************************************************************
517: // Private utility methods
518:
519: /** (Re)initializes state (during release() or construction) */
520: private void init() {
521: // defaults for internal bookkeeping
522: index = 0; // internal index always starts at 0
523: count = 1; // internal count always starts at 1
524: status = null; // we clear status on release()
525: item = null; // item will be retrieved for each round
526: last = false; // last must be set explicitly
527: beginSpecified = false; // not specified until it's specified :-)
528: endSpecified = false; // (as above)
529: stepSpecified = false; // (as above)
530:
531: // defaults for interface with page author
532: begin = 0; // when not specified, 'begin' is 0 by spec.
533: end = -1; // when not specified, 'end' is not used
534: step = 1; // when not specified, 'step' is 1
535: itemId = null; // when not specified, no variable exported
536: statusId = null; // when not specified, no variable exported
537: }
538:
539: /** Sets 'last' appropriately. */
540: private void calibrateLast() throws JspTagException {
541: /*
542: * the current round is the last one if (a) there are no remaining
543: * elements, or (b) the next one is beyond the 'end'.
544: */
545: last = !hasNext() || atEnd()
546: || (end != -1 && (begin + index + step > end));
547: }
548:
549: /**
550: * Exposes attributes (formerly scripting variables, but no longer!)
551: * if appropriate. Note that we don't really care, here, whether they're
552: * scripting variables or not.
553: */
554: private void exposeVariables(boolean firstTime)
555: throws JspTagException {
556:
557: /*
558: * We need to support null items returned from next(); we
559: * do this simply by passing such non-items through to the
560: * scoped variable as effectively 'null' (that is, by calling
561: * removeAttribute()).
562: *
563: * Also, just to be defensive, we handle the case of a null
564: * 'status' object as well.
565: *
566: * We call getCurrent() and getLoopStatus() (instead of just using
567: * 'item' and 'status') to bridge to subclasses correctly.
568: * A subclass can override getCurrent() or getLoopStatus() but still
569: * depend on our doStartTag() and doAfterBody(), which call this
570: * method (exposeVariables()), to expose 'item' and 'status'
571: * correctly.
572: */
573:
574: if (itemId != null) {
575: if (getCurrent() == null)
576: pageContext.removeAttribute(itemId,
577: PageContext.PAGE_SCOPE);
578: else if (deferredExpression != null) {
579: VariableMapper vm = pageContext.getELContext()
580: .getVariableMapper();
581: if (vm != null) {
582: ValueExpression ve = getVarExpression(deferredExpression);
583: ValueExpression tmpValue = vm.setVariable(itemId,
584: ve);
585: if (firstTime)
586: oldMappedValue = tmpValue;
587: }
588: } else
589: pageContext.setAttribute(itemId, getCurrent());
590: }
591: if (statusId != null) {
592: if (getLoopStatus() == null)
593: pageContext.removeAttribute(statusId,
594: PageContext.PAGE_SCOPE);
595: else
596: pageContext.setAttribute(statusId, getLoopStatus());
597: }
598:
599: }
600:
601: /**
602: * Removes page attributes that we have exposed and, if applicable,
603: * restores them to their prior values (and scopes).
604: */
605: private void unExposeVariables() {
606: // "nested" variables are now simply removed
607: if (itemId != null) {
608: pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
609: VariableMapper vm = pageContext.getELContext()
610: .getVariableMapper();
611: if (vm != null)
612: vm.setVariable(itemId, oldMappedValue);
613: }
614: if (statusId != null)
615: pageContext.removeAttribute(statusId,
616: PageContext.PAGE_SCOPE);
617: }
618:
619: /**
620: * Cycles through and discards up to 'n' items from the iteration.
621: * We only know "up to 'n'", not "exactly n," since we stop cycling
622: * if hasNext() returns false or if we hit the 'end' of the iteration.
623: * Note: this does not update the iteration index, since this method
624: * is intended as a behind-the-scenes operation. The index must be
625: * updated separately. (I don't really like this, but it's the simplest
626: * way to support isLast() without storing two separate inconsistent
627: * indices. We need to (a) make sure hasNext() refers to the next
628: * item we actually *want* and (b) make sure the index refers to the
629: * item associated with the *current* round, not the next one.
630: * C'est la vie.)
631: */
632: private void discard(int n) throws JspTagException {
633: /*
634: * copy index so we can restore it, but we need to update it
635: * as we work so that atEnd() works
636: */
637: int oldIndex = index;
638: while (n-- > 0 && !atEnd() && hasNext()) {
639: index++;
640: next();
641: }
642: index = oldIndex;
643: }
644:
645: /**
646: * Discards items ignoring subsetting rules. Useful for discarding
647: * items from the beginning (i.e., to implement 'begin') where we
648: * don't want factor in the 'begin' value already.
649: */
650: private void discardIgnoreSubset(int n) throws JspTagException {
651: while (n-- > 0 && hasNext())
652: next();
653: }
654:
655: /**
656: * Returns true if the iteration has past the 'end' index (with
657: * respect to subsetting), false otherwise. ('end' must be set
658: * for atEnd() to return true; if 'end' is not set, atEnd()
659: * always returns false.)
660: */
661: private boolean atEnd() {
662: return ((end != -1) && (begin + index >= end));
663: }
664:
665: private ValueExpression getVarExpression(ValueExpression expr) {
666: Object o = expr.getValue(pageContext.getELContext());
667: if (o == null)
668: return null;
669:
670: if (o.getClass().isArray() || o instanceof List) {
671: return new IndexedValueExpression(deferredExpression, index);
672: }
673:
674: if (o instanceof Collection || o instanceof Iterator
675: || o instanceof Enumeration || o instanceof Map
676: || o instanceof String) {
677:
678: if (iteratedExpression == null) {
679: iteratedExpression = new IteratedExpression(
680: deferredExpression, getDelims());
681: }
682: return new IteratedValueExpression(iteratedExpression,
683: index);
684: }
685:
686: throw new ELException(
687: "Don't know how to iterate over supplied "
688: + "items in forEach");
689: }
690: }
|