Clover coverage report - Cactus 1.5 for J2EE API 1.3
Coverage timestamp: Wed Feb 18 2004 09:09:13 EST
file stats: LOC: 865   Methods: 25
NCLOC: 322   Classes: 7
 
 Source file Conditionals Statements Methods TOTAL
JspTagLifecycle.java 0% 0% 0% 0%
coverage
 1   
 /*
 2   
  * ====================================================================
 3   
  *
 4   
  * The Apache Software License, Version 1.1
 5   
  *
 6   
  * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 7   
  * reserved.
 8   
  *
 9   
  * Redistribution and use in source and binary forms, with or without
 10   
  * modification, are permitted provided that the following conditions
 11   
  * are met:
 12   
  *
 13   
  * 1. Redistributions of source code must retain the above copyright
 14   
  *    notice, this list of conditions and the following disclaimer.
 15   
  *
 16   
  * 2. Redistributions in binary form must reproduce the above copyright
 17   
  *    notice, this list of conditions and the following disclaimer in
 18   
  *    the documentation and/or other materials provided with the
 19   
  *    distribution.
 20   
  *
 21   
  * 3. The end-user documentation included with the redistribution, if
 22   
  *    any, must include the following acknowlegement:
 23   
  *       "This product includes software developed by the
 24   
  *        Apache Software Foundation (http://www.apache.org/)."
 25   
  *    Alternately, this acknowlegement may appear in the software itself,
 26   
  *    if and wherever such third-party acknowlegements normally appear.
 27   
  *
 28   
  * 4. The names "The Jakarta Project", "Cactus" and "Apache Software
 29   
  *    Foundation" must not be used to endorse or promote products
 30   
  *    derived from this software without prior written permission. For
 31   
  *    written permission, please contact apache@apache.org.
 32   
  *
 33   
  * 5. Products derived from this software may not be called "Apache"
 34   
  *    nor may "Apache" appear in their names without prior written
 35   
  *    permission of the Apache Group.
 36   
  *
 37   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 38   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 39   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 40   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 41   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 42   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 43   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 44   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 45   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 46   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 47   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 48   
  * SUCH DAMAGE.
 49   
  * ====================================================================
 50   
  *
 51   
  * This software consists of voluntary contributions made by many
 52   
  * individuals on behalf of the Apache Software Foundation.  For more
 53   
  * information on the Apache Software Foundation, please see
 54   
  * <http://www.apache.org/>.
 55   
  *
 56   
  */
 57   
 package org.apache.cactus.extension.jsp;
 58   
 
 59   
 import java.io.IOException;
 60   
 import java.util.ArrayList;
 61   
 import java.util.Iterator;
 62   
 import java.util.List;
 63   
 
 64   
 import junit.framework.Assert;
 65   
 
 66   
 import javax.servlet.jsp.JspException;
 67   
 import javax.servlet.jsp.PageContext;
 68   
 import javax.servlet.jsp.tagext.BodyContent;
 69   
 import javax.servlet.jsp.tagext.BodyTag;
 70   
 import javax.servlet.jsp.tagext.IterationTag;
 71   
 import javax.servlet.jsp.tagext.Tag;
 72   
 import javax.servlet.jsp.tagext.TryCatchFinally;
 73   
 
 74   
 import org.apache.commons.logging.Log;
 75   
 import org.apache.commons.logging.LogFactory;
 76   
 
 77   
 /**
 78   
  * Convenience class that supports the testing of JSP tag by managing the tag's
 79   
  * lifecycle as required by the JSP specification.
 80   
  * 
 81   
  * <p>
 82   
  *   This class is basically a stub implementation of the tag management
 83   
  *   facilities that an actual JSP container would provide. The implementation
 84   
  *   attempts to follow the specification as closely as possible, but the tag
 85   
  *   handling functionality of real JSP implementations may vary in some
 86   
  *   details.
 87   
  * </p>
 88   
  * 
 89   
  * <p>
 90   
  *   Although this class works quite well when used in the test methods of a 
 91   
  *   {@link org.apache.cactus.JspTestCase JspTestCase}, it can also safely be
 92   
  *   used outside of the Cactus testing framework, for example when following
 93   
  *   a mock objects approach.
 94   
  * </p>
 95   
  * 
 96   
  * <h4>Testing Simple Tags</h4>
 97   
  * <p>
 98   
  *   This is how you would use this class when testing the
 99   
  *   <code>&lt;c:set&gt;</code>-tag of the JSTL reference implementation:
 100   
  *   <blockquote><pre>
 101   
   SetTag tag = new SetTag();
 102   
   JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
 103   
   tag.setVar("name");
 104   
   tag.setValue("value");
 105   
   lifecycle.invoke();
 106   
   assertEquals("value", pageContext.findAttribute("name"));</pre>
 107   
  *   </blockquote>
 108   
  *   The order is important:
 109   
  *   <ol>
 110   
  *     <li>
 111   
  *       Instantiation of the tag under test
 112   
  *     </li>
 113   
  *     <li>
 114   
  *       Instantiation of the lifecycle helper, passing in the page context and
 115   
  *       the tag instance
 116   
  *     </li>
 117   
  *     <li>
 118   
  *       Set the tag's attributes
 119   
  *     </li>
 120   
  *     <li>
 121   
  *       Start the tag's lifecycle by calling
 122   
  *       {@link #invoke() JspTagLifecycle.invoke()}
 123   
  *     </li>
 124   
  *     <li>
 125   
  *       Make assertions
 126   
  *     </li>
 127   
  *   </ol>
 128   
  * </p>
 129   
  * 
 130   
  * <h4>Adding Expectations to the Lifecycle</h4>
 131   
  * <p>
 132   
  *   <code>JspTagLifecycle</code> features a couple of methods that let you 
 133   
  *   easily add expectations about the tag's lifecycle to the test. For example,
 134   
  *   the method {@link #expectBodySkipped expectBodySkipped()} can be used to 
 135   
  *   verify that tag's body is not evaluated under the conditions set up by the
 136   
  *   test:
 137   
  *   <pre>
 138   
  * IfTag tag = new IfTag();
 139   
  * JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
 140   
  * tag.setTest("false");
 141   
  * lifecycle.expectBodySkipped();
 142   
  * lifecycle.invoke();</pre>
 143   
  * </p>
 144   
  * <p>
 145   
  *   An example of a more sophisticated expectationion is the
 146   
  *   {@link #expectScopedVariableExposed(String, Object[])}
 147   
  *   method, which can verify that a specific scoped variable gets exposed in
 148   
  *   the body of the tag, and that the exposed variable has a specific value in
 149   
  *   each iteration step:
 150   
  *   <pre>
 151   
  * ForEachTag tag = new ForEachTag();
 152   
  * JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
 153   
  * tag.setVar("item");
 154   
  * tag.setItems("One,Two,Three");
 155   
  * lifecycle.expectBodyEvaluated(3);
 156   
  * lifecycle.expectScopedVariableExposed(
 157   
  *     "item", new Object[] {"One", "Two", "Three"});
 158   
  * lifecycle.invoke();</pre>
 159   
  * </p>
 160   
  * 
 161   
  * <h4>Custom Expectations</h4>
 162   
  * <p>
 163   
  *   In some cases, using the expectations offered by 
 164   
  *   <code>JspTagLifecycle</code> does not suffice. In such cases, you need to 
 165   
  *   use custom expectations. You can add custom expectations by creating a 
 166   
  *   concrete subclass of the {@link JspTagLifecycle.Interceptor Interceptor}
 167   
  *   class, and adding it to the list of the tag lifecycles interceptors through
 168   
  *   {@link JspTagLifecycle#addInterceptor addInterceptor()}:
 169   
  *   <pre>
 170   
  * ForEachTag tag = new ForEachTag();
 171   
  * JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
 172   
  * tag.setVarStatus("status");
 173   
  * tag.setBegin("0");
 174   
  * tag.setEnd("2");
 175   
  * lifecycle.addInterceptor(new JspTagLifecycle.Interceptor() {
 176   
  *     public void evalBody(int theIteration, BodyContent theBody) {
 177   
  *         LoopTagStatus status = (LoopTagStatus)
 178   
  *             pageContext.findAttribute("status");
 179   
  *         assertNotNull(status);
 180   
  *         if (theIteration == 0) {
 181   
  *             assertTrue(status.isFirst());
 182   
  *             assertFalse(status.isLast());
 183   
  *         }
 184   
  *         else if (theIteration == 1) {
 185   
  *             assertFalse(status.isFirst());
 186   
  *             assertFalse(status.isLast());
 187   
  *         }
 188   
  *         else if (theIteration == 2) {
 189   
  *             assertFalse(status.isFirst());
 190   
  *             assertTrue(status.isLast());
 191   
  *         }
 192   
  *     }
 193   
  * });
 194   
  * lifecycle.invoke();</pre>
 195   
  * </p>
 196   
  * 
 197   
  * <h4>Specifying Nested Content</h4>
 198   
  * <p>
 199   
  *   <code>JspTagLifecycle</code> let's you add nested tempate text as well as 
 200   
  *   nested tags to the tag under test. The most important use of this feature 
 201   
  *   is testing of collaboration between tags, but it also allows you to easily
 202   
  *   check whether a tag correctly handles its body content.
 203   
  * </p>
 204   
  * <p>
 205   
  *   The following example demonstrates how to add nested template text to the 
 206   
  *   tag, and how to assert that the body was written to the HTTP response on
 207   
  *   the client side:
 208   
  *   <pre>
 209   
  * public void testOutTagDefaultBody() throws JspException, IOException {
 210   
  *     OutTag tag = new OutTag();
 211   
  *     JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
 212   
  *     tag.setValue(null);
 213   
  *     lifecycle.addNestedText("Default");
 214   
  *     lifecycle.expectBodyEvaluated();
 215   
  *     lifecycle.invoke();
 216   
  * }
 217   
  * public void endOutTagDefaultBody(WebResponse theResponse) {
 218   
  *     String output = theResponse.getText();
 219   
  *     assertEquals("Default", output);
 220   
  * }</pre>
 221   
  * </p>
 222   
  * <p>
 223   
  *   In sophisticated tag libraries, there will be many cases where tags need 
 224   
  *   to collaborate with each other in some way. This is usually done by nesting
 225   
  *   such tags within eachother. <code>JspTagLifecycle</code> supports such 
 226   
  *   scenarios by allowing you to add nested tags to an existing tag lifecycle.
 227   
  *   The nested tags can than be decorated with expectations themselves, as you
 228   
  *   can see in the following example:
 229   
  *   <pre>
 230   
  * ChooseTag chooseTag = new ChooseTag();
 231   
  * JspTagLifecycle chooseLifecycle =
 232   
  *     new JspTagLifecycle(pageContext, chooseTag);
 233   
  * WhenTag whenTag = new WhenTag();
 234   
  * JspTagLifecycle whenLifecycle =
 235   
  *     chooseLifecycle.addNestedTag(whenTag);
 236   
  * whenTag.setTest("false");
 237   
  * whenLifecycle.expectBodySkipped();
 238   
  * OtherwiseTag otherwiseTag = new OtherwiseTag();
 239   
  * JspTagLifecycle otherwiseLifecycle =
 240   
  *     chooseLifecycle.addNestedTag(otherwiseTag);
 241   
  * otherwiseLifecycle.expectBodyEvaluated();
 242   
  * chooseLifecycle.invoke();</pre>
 243   
  *   The code above creates a constellation of tags equivalent to the following
 244   
  *   JSP fragment:
 245   
  *   <pre>
 246   
  * &lt;c:choose&gt;
 247   
  *  &lt;c:when test='false'&gt;
 248   
  *   &lt;%-- body content not significant for the test --%&gt;
 249   
  *  &lt;/c:when&gt;
 250   
  *  &lt;c:otherwise&gt;
 251   
  *   &lt;%-- body content not significant for the test --%&gt;
 252   
  *  &lt;/c:otherwise&gt;
 253   
  * &lt;/c:choose&gt;</pre>
 254   
  * </p>
 255   
  * 
 256   
  * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 257   
  * @since Cactus 1.5
 258   
  * 
 259   
  * @version $Id: JspTagLifecycle.java,v 1.17 2003/03/30 12:31:22 vmassol Exp $
 260   
  * @see org.apache.cactus.JspTestCase
 261   
  */
 262   
 public final class JspTagLifecycle
 263   
 {   
 264   
     // Inner Classes -----------------------------------------------------------
 265   
     
 266   
     /**
 267   
      * Abstract class for intercepting the tag lifecycle. You can override any
 268   
      * of the methods to insert expectations about the tag's behaviour while it
 269   
      * is being executed.
 270   
      * 
 271   
      * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 272   
      * @since Cactus 1.5
 273   
      */
 274   
     public abstract static class Interceptor
 275   
     {
 276   
         
 277   
         /**
 278   
          * Method called when the body of the tag would be evaluated. Can be
 279   
          * used in specific test cases to perform assertions.
 280   
          * 
 281   
          * Please note that if you're testing a <code>BodyTag</code>, you
 282   
          * should not write content to the
 283   
          * {@link org.apache.cactus.JspTestCase#out} instance variable while 
 284   
          * the body is being evaluated. This is because the actual implicit
 285   
          * object <code>out</code> in JSP pages gets replaced by the current 
 286   
          * nested <code>BodyContent</code>, whereas in <code>JspTestCase</code>
 287   
          * the <code>out</code> variable always refers to the top level
 288   
          * <code>JspWriter</code>. Instead, simply use the 
 289   
          * <code>BodyContent</code> parameter passed into the
 290   
          * {@link JspTagLifecycle.Interceptor#evalBody evalBody()} method or 
 291   
          * the <code>JspWriter</code> retrieved by a call to 
 292   
          * {javax.servlet.jsp.PageContext#getOut pageContext.getOut()}. 
 293   
          * 
 294   
          * @param theIteration The number of times the body has been evaluated
 295   
          * @param theBody The body content, or <tt>null</tt> if the tag isn't a
 296   
          *        <tt>BodyTag</tt>
 297   
          * @throws JspException If thrown by a nested tag
 298   
          * @throws IOException If an error occurs when reading or writing the
 299   
          *         body content
 300   
          */
 301  0
         public void evalBody(int theIteration, BodyContent theBody)
 302   
             throws JspException, IOException
 303   
         {
 304   
             // default implementation does nothing
 305   
         }
 306   
         
 307   
         /**
 308   
          * Method called when the body of the tag would be skipped. Can be used 
 309   
          * in specific test cases to perform assertions.
 310   
          */
 311  0
         public void skipBody()
 312   
         {
 313   
             // default implementation does nothing
 314   
         }
 315   
         
 316   
     }
 317   
     
 318   
     /**
 319   
      * A specialized interceptor that verifies that the tag's body is evaluated
 320   
      * at least once.
 321   
      * 
 322   
      * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 323   
      * @since Cactus 1.5
 324   
      */
 325   
     private static class ExpectBodyEvaluatedInterceptor
 326   
         extends Interceptor
 327   
     {
 328   
         /**
 329   
          * The actual number of times the tag's body has been evaluated.
 330   
          */
 331   
         private int actualNumIterations;
 332   
         
 333   
         /**
 334   
          * The number of times the tag's body is expected to be evaluated.
 335   
          */
 336   
         private int expectedNumIterations;
 337   
         
 338   
         /**
 339   
          * Constructor.
 340   
          * 
 341   
          * @param theNumIterations The number of iterations expected
 342   
          */
 343  0
         public ExpectBodyEvaluatedInterceptor(int theNumIterations)
 344   
         {
 345  0
             this.expectedNumIterations = theNumIterations;
 346   
         }
 347   
         
 348   
         /**
 349   
          * Overridden to assert that the body doesn't get evaluated more often
 350   
          * than expected.
 351   
          * 
 352   
          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
 353   
          */
 354  0
         public void evalBody(int theIteration, BodyContent theBody)
 355   
         {
 356  0
             actualNumIterations++;
 357  0
             if (actualNumIterations > expectedNumIterations)
 358   
             {
 359  0
                 Assert.fail("Expected " + expectedNumIterations
 360   
                     + " iterations, but was " + actualNumIterations);
 361   
             }
 362   
         }
 363   
         
 364   
         /**
 365   
          * Overridden to assert that the body got evaluated as often as
 366   
          * expected.
 367   
          */
 368  0
         public void skipBody()
 369   
         {
 370  0
             if (actualNumIterations < expectedNumIterations)
 371   
             {
 372  0
                 Assert.fail("Expected " + expectedNumIterations
 373   
                     + " iterations, but was " + actualNumIterations);
 374   
             }
 375   
         }
 376   
     }
 377   
     
 378   
     /**
 379   
      * A specialized interceptor that asserts that the tag's body is skipped.
 380   
      * 
 381   
      * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 382   
      * @since Cactus 1.5
 383   
      */
 384   
     private static class ExpectBodySkippedInterceptor
 385   
         extends Interceptor
 386   
     {
 387   
         /**
 388   
          * Overridden to assert that the body doesn't get evaluated.
 389   
          * 
 390   
          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
 391   
          */
 392  0
         public void evalBody(int theIteration, BodyContent theBody)
 393   
         {
 394  0
             Assert.fail("Tag body should have been skipped");
 395   
         }
 396   
     }
 397   
     
 398   
     /**
 399   
      * A specialized interceptor that checks whether a specific scoped variable
 400   
      * is exposed in the body of the tag with a specific value.
 401   
      * 
 402   
      * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 403   
      * @since Cactus 1.5
 404   
      */
 405   
     private class ExpectScopedVariableExposedInterceptor
 406   
         extends Interceptor
 407   
     {
 408   
         /**
 409   
          * The name of the scoped variable.
 410   
          */
 411   
         private String name;
 412   
         
 413   
         /**
 414   
          * The list of expected values of the variable.
 415   
          */
 416   
         private Object[] expectedValues;
 417   
         
 418   
         /**
 419   
          * The scope in which the variable is stored.
 420   
          */
 421   
         private int scope;
 422   
         
 423   
         /**
 424   
          * Constructor.
 425   
          * 
 426   
          * @param theName The name of the scoped variable to check for
 427   
          * @param theExpectedValues An array containing the expected values, 
 428   
          *        one item for every iteration step
 429   
          * @param theScope The scope to search for the scoped variable
 430   
          */
 431  0
         public ExpectScopedVariableExposedInterceptor(String theName,
 432   
             Object[] theExpectedValues, int theScope)
 433   
         {
 434  0
             this.name = theName;
 435  0
             this.expectedValues = theExpectedValues;
 436  0
             this.scope = theScope;
 437   
         }
 438   
         
 439   
         /**
 440   
          * Overridden to assert that the scoped variable is exposed as expected.
 441   
          * 
 442   
          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
 443   
          */
 444  0
         public void evalBody(int theIteration, BodyContent theBody)
 445   
         {
 446  0
             Assert.assertEquals(expectedValues[theIteration],
 447   
                 pageContext.getAttribute(name, scope));
 448   
         }
 449   
     }
 450   
     
 451   
     /**
 452   
      * A specialized interceptor that invokes the lifecycle of a nested tag.
 453   
      * 
 454   
      * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 455   
      * @since Cactus 1.5
 456   
      */
 457   
     private class NestedTagInterceptor
 458   
         extends Interceptor
 459   
     {
 460   
         /**
 461   
          * The lifecycle object of the nested tag.
 462   
          */
 463   
         private JspTagLifecycle lifecycle;
 464   
         
 465   
         /**
 466   
          * Constructor.
 467   
          * 
 468   
          * @param theLifecycle The lifecycle instance associated with the nested
 469   
          *        tag
 470   
          */
 471  0
         public NestedTagInterceptor(JspTagLifecycle theLifecycle)
 472   
         {
 473  0
             this.lifecycle = theLifecycle;
 474   
         }
 475   
         
 476   
         /**
 477   
          * Overridden to invoke the lifecycle of the nested tag.
 478   
          * 
 479   
          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
 480   
          */
 481  0
         public void evalBody(int theIteration, BodyContent theBody)
 482   
             throws JspException, IOException
 483   
         {
 484  0
             lifecycle.invoke();
 485   
         }
 486   
     }
 487   
     
 488   
     /**
 489   
      * A specialized interceptor that prints nested template text when the tag's
 490   
      * body is evaluated.
 491   
      * 
 492   
      * @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
 493   
      * @since Cactus 1.5
 494   
      */
 495   
     private class NestedTextInterceptor
 496   
         extends Interceptor
 497   
     {
 498   
         /**
 499   
          * The nested text.
 500   
          */
 501   
         private String text;
 502   
         
 503   
         /**
 504   
          * Constructor.
 505   
          * 
 506   
          * @param theText The nested text
 507   
          */
 508  0
         public NestedTextInterceptor(String theText)
 509   
         {
 510  0
             this.text = theText;
 511   
         }
 512   
         
 513   
         /**
 514   
          * Overridden to write the nested text to the current writer.
 515   
          * 
 516   
          * @see JspTagLifecycle.Interceptor#evalBody(int,BodyContent)
 517   
          */
 518  0
         public void evalBody(int theIteration, BodyContent theBody)
 519   
             throws IOException
 520   
         {
 521  0
             if (theBody != null)
 522   
             {
 523  0
                 theBody.print(text);
 524   
             }
 525   
             else
 526   
             {
 527  0
                 pageContext.getOut().print(text);
 528   
             }
 529   
         }
 530   
     }
 531   
     
 532   
     // Class Variables ---------------------------------------------------------
 533   
     
 534   
     /**
 535   
      * The log target.
 536   
      */
 537   
     private static Log log = LogFactory.getLog(JspTagLifecycle.class);
 538   
     
 539   
     // Instance Variables ------------------------------------------------------
 540   
     
 541   
     /**
 542   
      * The JSP page context.
 543   
      */
 544   
     protected PageContext pageContext;
 545   
     
 546   
     /**
 547   
      * The JSP tag handler.
 548   
      */
 549   
     private Tag tag;
 550   
         
 551   
     /**
 552   
      * The interceptor chain.
 553   
      */
 554   
     private List interceptors;
 555   
     
 556   
     // Constructors ------------------------------------------------------------
 557   
     
 558   
     /**
 559   
      * Constructor.
 560   
      * 
 561   
      * @param thePageContext The JSP page context
 562   
      * @param theTag The JSP tag
 563   
      */
 564  0
     public JspTagLifecycle(PageContext thePageContext, Tag theTag)
 565   
     {
 566  0
         if ((thePageContext == null) || (theTag == null))
 567   
         {
 568  0
             throw new NullPointerException();
 569   
         }
 570  0
         this.tag = theTag;
 571  0
         this.pageContext = thePageContext;
 572  0
         this.tag.setPageContext(this.pageContext);
 573   
     }
 574   
     
 575   
     // Public Methods ----------------------------------------------------------
 576   
     
 577   
     /**
 578   
      * Adds an interceptor to the interceptor chain.
 579   
      * 
 580   
      * @param theInterceptor The interceptor to add
 581   
      */
 582  0
     public void addInterceptor(Interceptor theInterceptor)
 583   
     {
 584  0
         if (theInterceptor == null)
 585   
         {
 586  0
             throw new NullPointerException();
 587   
         }
 588  0
         if (this.interceptors == null)
 589   
         {
 590  0
             this.interceptors = new ArrayList();
 591   
         }
 592  0
         this.interceptors.add(theInterceptor);
 593   
     }
 594   
     
 595   
     /**
 596   
      * Adds a nested tag. The tag will be invoked when the body content of the
 597   
      * enclosing tag is evaluated.
 598   
      * 
 599   
      * @return The lifecycle wrapper for the nested tag, can be used to add 
 600   
      *         expectations for the nested tag
 601   
      * @param theNestedTag The tag to be nested
 602   
      */
 603  0
     public JspTagLifecycle addNestedTag(Tag theNestedTag)
 604   
     {
 605  0
         if (theNestedTag == null)
 606   
         {
 607  0
             throw new NullPointerException();
 608   
         }
 609  0
         JspTagLifecycle lifecycle =
 610   
             new JspTagLifecycle(this.pageContext, theNestedTag);
 611  0
         theNestedTag.setParent(this.tag);
 612  0
         addInterceptor(new NestedTagInterceptor(lifecycle));
 613  0
         return lifecycle;
 614   
     }
 615   
     
 616   
     /**
 617   
      * Adds template text to nest inside the tag. The text will be printed to 
 618   
      * the body content when it is evaluated.
 619   
      * 
 620   
      * @param theNestedText The string containing the template text
 621   
      */
 622  0
     public void addNestedText(String theNestedText)
 623   
     {
 624  0
         if (theNestedText == null)
 625   
         {
 626  0
             throw new NullPointerException();
 627   
         }
 628  0
         addInterceptor(new NestedTextInterceptor(theNestedText));
 629   
     }
 630   
     
 631   
     /**
 632   
      * Adds the expectation that the tag body must be evaluated once in the 
 633   
      * course of the tags lifecycle.
 634   
      */
 635  0
     public void expectBodyEvaluated()
 636   
     {
 637  0
         addInterceptor(new ExpectBodyEvaluatedInterceptor(1));
 638   
     }
 639   
     
 640   
     /**
 641   
      * Adds the expectation that the tag body must be evaluated a specific
 642   
      * number of times in the course of the tags lifecycle.
 643   
      * 
 644   
      * @param theNumIterations The number of times the body is expected to get 
 645   
      *        evaluated
 646   
      */
 647  0
     public void expectBodyEvaluated(int theNumIterations)
 648   
     {
 649  0
         addInterceptor(new ExpectBodyEvaluatedInterceptor(theNumIterations));
 650   
     }
 651   
     
 652   
     /**
 653   
      * Adds the expectation that the tag body must be skipped. Essentially, this
 654   
      * expectation verifies that the tag returns <code>SKIP_BODY</code> from
 655   
      * <code>doStartTag()</code>.
 656   
      */
 657  0
     public void expectBodySkipped()
 658   
     {
 659  0
         addInterceptor(new ExpectBodySkippedInterceptor());
 660   
     }
 661   
     
 662   
     /**
 663   
      * Adds a special expectation that verifies that a specific scoped variable
 664   
      * is exposed in the body of the tag.
 665   
      * 
 666   
      * @param theName The name of the variable
 667   
      * @param theExpectedValues An ordered list containing the expected values 
 668   
      *        values of the scoped variable, one for each expected iteration
 669   
      *        step
 670   
      */
 671  0
     public void expectScopedVariableExposed(String theName,
 672   
         Object[] theExpectedValues)
 673   
     {
 674  0
         expectScopedVariableExposed(theName, theExpectedValues,
 675   
             PageContext.PAGE_SCOPE);
 676   
     }
 677   
     
 678   
     /**
 679   
      * Adds a special expectation that verifies that a specific scoped variable
 680   
      * is exposed in the body of the tag.
 681   
      * 
 682   
      * @param theName The name of the variable
 683   
      * @param theExpectedValues An ordered list containing the expected values 
 684   
      *        values of the scoped variable, one for each expected iteration
 685   
      *        step
 686   
      * @param theScope The scope under which the variable is stored
 687   
      */
 688  0
     public void expectScopedVariableExposed(String theName,
 689   
         Object[] theExpectedValues, int theScope)
 690   
     {
 691  0
         if ((theName == null) || (theExpectedValues == null))
 692   
         {
 693  0
             throw new NullPointerException();
 694   
         }
 695  0
         if (theExpectedValues.length == 0)
 696   
         {
 697  0
             throw new IllegalArgumentException();
 698   
         }
 699  0
         if ((theScope != PageContext.PAGE_SCOPE)
 700   
          && (theScope != PageContext.REQUEST_SCOPE)
 701   
          && (theScope != PageContext.SESSION_SCOPE)
 702   
          && (theScope != PageContext.APPLICATION_SCOPE))
 703   
         {
 704  0
             throw new IllegalArgumentException();
 705   
         }
 706  0
         addInterceptor(
 707   
             new ExpectScopedVariableExposedInterceptor(theName,
 708   
                 theExpectedValues, theScope));
 709   
     }
 710   
     
 711   
     /**
 712   
      * Invokes the tag with the provided interceptor. The tag should have been
 713   
      * populated with its properties before calling this method. The tag is not
 714   
      * released after the tag's lifecycle is over.
 715   
      * 
 716   
      * @throws JspException If the tag throws an exception
 717   
      * @throws IOException If an error occurs when reading or writing the body
 718   
      *         content
 719   
      */
 720  0
     public void invoke() throws JspException, IOException
 721   
     {
 722  0
         if (this.tag instanceof TryCatchFinally)
 723   
         {
 724  0
             TryCatchFinally tryCatchFinally = (TryCatchFinally) this.tag;
 725  0
             try
 726   
             {
 727  0
                 invokeInternal();
 728   
             }
 729   
             catch (Throwable t1)
 730   
             {
 731  0
                 try
 732   
                 {
 733  0
                     tryCatchFinally.doCatch(t1);
 734   
                 }
 735   
                 catch (Throwable t2)
 736   
                 {
 737  0
                     throw new JspException(t2.getMessage());
 738   
                 }
 739   
             }
 740   
             finally
 741   
             {
 742  0
                 tryCatchFinally.doFinally();
 743   
             }
 744   
         }
 745   
         else
 746   
         {
 747  0
             invokeInternal();
 748   
         }
 749   
     }
 750   
     
 751   
     // Private Methods ---------------------------------------------------------
 752   
     
 753   
     /**
 754   
      * Notify all interceptors about a body evaluation.
 755   
      * 
 756   
      * @param theIteration The iteration
 757   
      * @param theBody The body content
 758   
      * @throws JspException If thrown by a nested tag
 759   
      * @throws IOException If an error occurs when reading or writing the body
 760   
      *         content
 761   
      */
 762  0
     private void fireEvalBody(int theIteration, BodyContent theBody)
 763   
         throws JspException, IOException
 764   
     {
 765  0
         if (this.interceptors != null)
 766   
         {
 767  0
             for (Iterator i = this.interceptors.iterator(); i.hasNext();)
 768   
             {
 769  0
                 ((Interceptor) i.next()).evalBody(theIteration, theBody);
 770   
             }
 771   
         }
 772   
     }
 773   
     
 774   
     /**
 775   
      * Notify all interceptors that the body has been skipped.
 776   
      */
 777  0
     private void fireSkipBody()
 778   
     {
 779  0
         if (this.interceptors != null)
 780   
         {
 781  0
             for (Iterator i = this.interceptors.iterator(); i.hasNext();)
 782   
             {
 783  0
                 ((Interceptor) i.next()).skipBody();
 784   
             }
 785   
         }
 786   
     }
 787   
     
 788   
     /**
 789   
      * Internal method to invoke a tag without doing exception handling.
 790   
      * 
 791   
      * @throws JspException If the tag throws an exception
 792   
      * @throws IOException If an error occurs when reading or writing the body
 793   
      *         content
 794   
      */
 795  0
     private void invokeInternal()
 796   
         throws JspException, IOException
 797   
     {
 798  0
         int status = this.tag.doStartTag();
 799  0
         if (this.tag instanceof IterationTag)
 800   
         {
 801  0
             if (status != Tag.SKIP_BODY)
 802   
             {
 803  0
                 BodyContent body = null;
 804  0
                 try
 805   
                 {
 806  0
                     IterationTag iterationTag = (IterationTag) this.tag;
 807  0
                     if ((status == BodyTag.EVAL_BODY_BUFFERED)
 808   
                         && (this.tag instanceof BodyTag))
 809   
                     {
 810  0
                         BodyTag bodyTag = (BodyTag) this.tag;
 811  0
                         body = pageContext.pushBody();
 812  0
                         if (log.isDebugEnabled())
 813   
                         {
 814  0
                             log.debug("Pushed body content ["
 815   
                                 + body.getString() + "]");
 816   
                         }
 817  0
                         bodyTag.setBodyContent(body);
 818  0
                         bodyTag.doInitBody();
 819   
                     }
 820  0
                     int iteration = 0;
 821  0
                     do
 822   
                     {
 823  0
                         fireEvalBody(iteration, body);
 824  0
                         if (log.isDebugEnabled())
 825   
                         {
 826  0
                             log.debug("Body evaluated for the ["
 827   
                                 + iteration + "] time");
 828   
                         }
 829  0
                         status = iterationTag.doAfterBody();
 830  0
                         iteration++;
 831  0
                     } while (status == IterationTag.EVAL_BODY_AGAIN);
 832  0
                     if (log.isDebugEnabled())
 833   
                     {
 834  0
                         log.debug("Body skipped");
 835   
                     }
 836  0
                     fireSkipBody();
 837   
                 }
 838   
                 finally
 839   
                 {
 840  0
                     if (body != null)
 841   
                     {
 842  0
                         if (log.isDebugEnabled())
 843   
                         {
 844  0
                             log.debug("Popping body content ["
 845   
                                 + body.getString() + "]");
 846   
                         }
 847  0
                         pageContext.popBody();
 848  0
                         body = null;
 849   
                     }
 850   
                 }
 851   
             }
 852   
             else
 853   
             {
 854  0
                 if (log.isDebugEnabled())
 855   
                 {
 856  0
                     log.debug("Body skipped");
 857   
                 }
 858  0
                 fireSkipBody();
 859   
             }
 860   
         }
 861  0
         status = tag.doEndTag();
 862   
     }
 863   
     
 864   
 }
 865