Save This Page
Home » glassfish-v2ur2-b04-src » javax.servlet.jsp.jstl » core » [javadoc | source]
    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   }

Save This Page
Home » glassfish-v2ur2-b04-src » javax.servlet.jsp.jstl » core » [javadoc | source]