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