2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

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