Hi Vincent et al.,

attached is a batch of improvements (I hope ;-) ) for the JspTagLifecycle stuff:
- fixed coding style
- added lots of javadoc comments
- some bug fixes

I'd appreciate feedback on what people think of this extension. Useful or not? API improvements? etc.

--
Christopher Lenz
/=/ cmlenz at gmx.de
Index: framework/src/java/j2ee13/org/apache/cactus/extension/jsp/JspTagLifecycle.java
===================================================================
RCS file: 
framework/src/java/j2ee13/org/apache/cactus/extension/jsp/JspTagLifecycle.java,v
retrieving revision 1.1
diff -u -r1.1 JspTagLifecycle.java
--- framework/src/java/j2ee13/org/apache/cactus/extension/jsp/JspTagLifecycle.java     
 24 Nov 2002 21:44:56 -0000      1.1
+++ framework/src/java/j2ee13/org/apache/cactus/extension/jsp/JspTagLifecycle.java     
+ 25 Nov 2002 21:47:06 -0000
@@ -70,9 +70,112 @@
  * Convenience class that supports the testing of JSP tag by managing the tag's
  * lifecycle as required by the JSP specification.
  * 
+ * <p>
+ *   This class is basically a stub implementation of the tag management
+ *   facilities that an actual JSP container would provide. The implementation
+ *   attempts to follow the specification as closely as possible, but the tag
+ *   handling functionality of real JSP implementations may vary in some
+ *   details.
+ * </p>
+ * 
+ * <p>
+ *   Although this class works quite well when used in the test methods of a 
+ *   {@link org.apache.cactus.JspTestCase JspTestCase}, it can also safely be
+ *   used outside of the Cactus testing framework, for example when following
+ *   a mock objects approach.
+ * </p>
+ * 
+ * <h4>Testing Simple Tags</h4>
+ * <p>
+ *   This is how you would use this class when testing the
+ *   <code>&lt;c:set&gt;</code>-tag of the JSTL reference implementation:
+ *   <blockquote><pre>
+  SetTag tag = new SetTag();
+  JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
+  tag.setVar("name");
+  tag.setValue("value");
+  lifecycle.invoke();
+  assertEquals("value", pageContext.findAttribute("name"));</pre>
+ *   </blockquote>
+ *   The order is important:
+ *   <ol>
+ *     <li>
+ *       Instantiation of the tag under test
+ *     </li>
+ *     <li>
+ *       Instantiation of the lifecycle helper, passing in the page context,
+ *       the tag instance and optionally the parent tag
+ *     </li>
+ *     <li>
+ *       Set the tag's attributes
+ *     </li>
+ *     <li>
+ *       Start the tag's lifecycle by calling
+ *       {@link #invoke() JspTagLifecycle.invoke()}
+ *     </li>
+ *     <li>
+ *       Make assertions
+ *     </li>
+ *   </ol>
+ * </p>
+ * 
+ * <h4>Testing Iteration and Body Tags with Lifecycle Interceptors</h4>
+ * <p>
+ *   In the example above, the tag's lifecycle is simply run through from start
+ *   to finish. However, <code>JspTagLifecycle</code> also let's you get
+ *   <em>inside</em> significant phases of the tag's lifecycle. For this you
+ *   need to use the method
+ *   {@link #invoke(JspTagLifecycle.Interceptor) invoke(Interceptor)}
+ *   supplying a custom
+ *   {@link JspTagLifecycle.Interceptor Interceptor} implementation.
+ * </p>
+ * 
+ * <p>
+ *   This feature can be used to test iteration and body tags. The following 
+ *   code snippet is a simple example for testing the
+ *   <code>&lt;c:forEach&gt;</code>-tag of the JSTL reference implementation:
+ *   <blockquote><pre>
+  ForEachTag tag = new ForEachTag();
+  JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
+  tag.setVar("item");
+  tag.setItems("one,two,three");
+  lifecycle.invoke(new JspTagLifecycle.Interceptor() {
+    public void evalBody(int iteration, BodyContent body) {
+      String item = (String)pageContext.findAttribute("item");
+      if (iteration == 0) {
+        assertEquals("one", item);
+      } else if (iteration == 1) {
+        assertEquals("two", item);
+      } else if (iteration == 2) {
+        assertEquals("three", item);
+      } else {
+        fail("More iterations than expected!");
+      }
+    }
+  });</pre>
+ * </blockquote></p>
+ * 
+ * <p>
+ *   To test a tag that does buffered evaluation of its body content, the 
+ *   {@link JspTagLifecycle.Interceptor#evalBody Interceptor.evalBody()} method
+ *   can be overridden to write the content that the tag will see. The following
+ *   example demonstrates this using the <code>&lt;c:out&gt;</code> tag:
+ *   <blockquote><pre>
+  OutTag tag = new OutTag();
+  JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
+  tag.setValue(null);
+  lifecycle.invoke(new JspTagLifecycle.Interceptor() {
+    public void evalBody(int iteration, BodyContent body)
+        throws IOException {
+        body.print("Default Value");
+    }
+  });
+  </blockquote>
+ * </p>
+ * 
  * @author <a href="mailto:[EMAIL PROTECTED]";>Christopher Lenz</a>
  * 
- * @version $Id: $
+ * @version $Id$
  * @see org.apache.cactus.JspTestCase
  */
 public class JspTagLifecycle
@@ -80,7 +183,10 @@
     // Inner Classes -----------------------------------------------------------
     
     /**
-     *  
+     * Abstract class for intercepting the tag lifecycle. You can override any
+     * of the methods to insert assertions that verify the tag's behaviour while
+     * it is being executed.
+     * 
      * @author <a href="mailto:[EMAIL PROTECTED]";>Christopher Lenz</a>
      */
     public abstract static class Interceptor
@@ -161,17 +267,19 @@
     public JspTagLifecycle(PageContext thePageContext, Tag theTag, 
         Tag theParent)
     {
-        this.pageContext = thePageContext;
         this.tag = theTag;
+        this.pageContext = thePageContext;
+        tag.setPageContext(pageContext);
         this.parent = theParent;
+        tag.setParent(parent);
     }
     
     // Public Methods ----------------------------------------------------------
     
     /**
-     * Invokes the tag in the provided page context. The tag should have been
-     * populated with its properties before calling this method. The tag is not
-     * released after the tag's lifecycle is over.
+     * Invokes the tag. The tag should have been populated with its properties
+     * before calling this method. The tag is not released after the tag's
+     * lifecycle is over.
      * 
      * @throws JspException If the tag throws an exception
      * @throws IOException If an error occurs when reading or writing the body
@@ -183,12 +291,12 @@
     }
     
     /**
-     * Invokes the tag in the provided page context. The tag should have been
+     * Invokes the tag with the provided interceptor. The tag should have been
      * populated with its properties before calling this method. The tag is not
      * released after the tag's lifecycle is over.
      * 
      * @param theInterceptor The interceptor that will be notified about 
-     *        lifecycle events
+     *        important lifecycle events
      * @throws JspException If the tag throws an exception
      * @throws IOException If an error occurs when reading or writing the body
      *         content
@@ -200,11 +308,7 @@
         {
             throw new NullPointerException();
         }
-
-        tag.setPageContext(pageContext);
-        tag.setParent(parent);
         BodyContent body = null;
-
         if (tag instanceof TryCatchFinally)
         {
             TryCatchFinally tryCatchFinally = (TryCatchFinally) tag;
@@ -268,9 +372,9 @@
     {
         BodyContent body = null;
         int status = tag.doStartTag();
-        if (status != Tag.SKIP_BODY)
+        if (tag instanceof IterationTag)
         {
-            if (tag instanceof IterationTag)
+            if (status != Tag.SKIP_BODY)
             {
                 IterationTag iterationTag = (IterationTag) tag;
                 if ((status == BodyTag.EVAL_BODY_BUFFERED)
@@ -289,10 +393,10 @@
                     iteration++;
                 } while (status == IterationTag.EVAL_BODY_AGAIN);
             }
-        }
-        else
-        {
-            theInterceptor.skipBody();
+            else
+            {
+                theInterceptor.skipBody();
+            }
         }
         status = tag.doEndTag();
         return body;
Index: framework/src/java/j2ee13/org/apache/cactus/extension/jsp/package.html
===================================================================
RCS file: framework/src/java/j2ee13/org/apache/cactus/extension/jsp/package.html
diff -N framework/src/java/j2ee13/org/apache/cactus/extension/jsp/package.html
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ framework/src/java/j2ee13/org/apache/cactus/extension/jsp/package.html      25 Nov 
+2002 21:47:06 -0000
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<title></title>
+</head>
+
+<body>
+
+<p>
+  Contains utility classes to support unit testing of JSP tag libraries.
+</p>
+
+</body>
+</html>
Index: sample-servlet/src/unit/j2ee13/org/apache/cactus/unit/TestJspTagLifecycle.java
===================================================================
RCS file: 
sample-servlet/src/unit/j2ee13/org/apache/cactus/unit/TestJspTagLifecycle.java,v
retrieving revision 1.1
diff -u -r1.1 TestJspTagLifecycle.java
--- sample-servlet/src/unit/j2ee13/org/apache/cactus/unit/TestJspTagLifecycle.java     
 24 Nov 2002 21:44:55 -0000      1.1
+++ sample-servlet/src/unit/j2ee13/org/apache/cactus/unit/TestJspTagLifecycle.java     
+ 25 Nov 2002 21:47:07 -0000
@@ -71,21 +71,29 @@
 import org.apache.taglibs.standard.tag.el.core.WhenTag;
 
 /**
+ * Tests for the <code>JspTagLifecycle</code> extension.
  * 
+ * <p>
+ *   The lifecycle helper is tested here by testing the reference implementation
+ *   of the JSP standard tag library (JSTL), available at
+ *   <a href="http://jakarta.apache.org/taglibs/";>.
+ * </p>
  * 
  * @author <a href="mailto:[EMAIL PROTECTED]";>Christopher Lenz</a>
  */
 public class TestJspTagLifecycle
-    extends JspTestCase {
+    extends JspTestCase
+{
     
     // Constructors ------------------------------------------------------------
     
     /**
-     * Defines the testcase name for JUnit.
-     *
-     * @param theName the testcase's name.
+     * Constructor.
+     * 
+     * @param theName The name of the test case
      */
-    public TestJspTagLifecycle(String theName) {
+    public TestJspTagLifecycle(String theName)
+    {
         super(theName);
     }
     
@@ -104,123 +112,153 @@
     // Test Methods ------------------------------------------------------------
     
     /**
+     * Tests the <code>&lt;c:out&gt;</code>-tag with a proper, literal value for
+     * it's <code>value</code> attribute.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testOutTag()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         OutTag tag = new OutTag();
-        tag.setValue("TEST");
         JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
-        lifecycle.invoke(new JspTagLifecycle.Interceptor() {
-            public void evalBody(int iteration, BodyContent body) {
-                assertEquals("TEST", body.getString());
-            }
-        });
+        tag.setValue("Value");
+        lifecycle.invoke();
     }
     
     /**
+     * Verifies that the response has been correctly rendered by the 
+     * <code>&lt;c:out&gt;</code>-tag.
      * 
-     * @param theResponse
+     * @param theResponse The HTTP response
      */
-    public void endOutTag(WebResponse theResponse) {
-        
+    public void endOutTag(WebResponse theResponse)
+    {
         String output = theResponse.getText();
-        assertEquals("TEST", output);
+        assertEquals("Value", output);
     }
     
     /**
+     * Tests the <code>&lt;c:out&gt;</code>-tag with <code>null</code> for
+     * it's <code>value</code> attribute, and a proper, literal value for it's
+     * <code>default</code> attribute.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testOutTagDefaultAttribute()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         OutTag tag = new OutTag();
-        tag.setValue(null);
-        tag.setDefault("Default Value");
         JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
+        tag.setValue(null);
+        tag.setDefault("Default");
         lifecycle.invoke();
     }
     
     /**
-     * 
-     * @param theResponse
+     * Verifies that the response has been correctly rendered by the 
+     * <code>&lt;c:out&gt;</code>-tag.
+     *  
+     * @param theResponse The HTTP response
      */
-    public void endOutTagWithDefaultAttribute(WebResponse theResponse) {
-        
+    public void endOutTagWithDefaultAttribute(WebResponse theResponse)
+    {
         String output = theResponse.getText();
-        assertEquals("Default Value", output);
+        assertEquals("Default", output);
     }
     
     /**
+     * Tests the &lt;c:out&gt;-Tag with a value that evaluates to
+     * <code>null</code>, and the default value specified in the tag's body.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testOutTagDefaultBody()
-        throws JspException, IOException {
-/*      
+        throws JspException, IOException
+    {
         OutTag tag = new OutTag();
+        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
         tag.setValue(null);
-        new JspTagLifecycle(tag) {
-            protected void evalBody(int iteration, BodyContent body)
-                throws IOException {
-                body.print("Default Value");
+        lifecycle.invoke(new JspTagLifecycle.Interceptor()
+        {
+            public void evalBody(int iteration, BodyContent body)
+                throws IOException
+            {
+                body.print("Default");
             }
-        }.invoke(pageContext, null);*/
+        });
     }
     
     /**
+     * Verifies that the response has been correctly rendered by the 
+     * <code>&lt;c:out&gt;</code>-tag.
      * 
-     * @param theResponse
+     * @param theResponse The HTTP response
+     * @todo This test currently fails if commented in
      */
-    public void endOutTagDefaultBody(WebResponse theResponse) {
-/*        
+    public void endOutTagDefaultBody(WebResponse theResponse)
+    {
         String output = theResponse.getText();
-        assertEquals("Default Value", output);*/
+        //assertEquals("Default", output);
     }
     
     /**
+     * Tests the &lt;c:set&gt;-tag with a proper, literal values for it's
+     * <code>var</code> and <code>value</code> attributes. Verification is done
+     * by checking the scoped variable stored by the tag.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testSetTag()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         SetTag tag = new SetTag();
-        tag.setVar("name");
-        tag.setValue("value");
         JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
+        tag.setVar("Var");
+        tag.setValue("Value");
         lifecycle.invoke();
-        assertEquals("value", pageContext.findAttribute("name"));
+        assertEquals("Value", pageContext.findAttribute("Var"));
     }
     
     /**
+     * Tests the tag &lt;c:forEach&gt; by providing a comma-delimited list of 
+     * string to it's <code>items</code> attributes, and checking the exposed
+     * scoped variable on every iteration step.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testForEachTag()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         ForEachTag tag = new ForEachTag();
-        tag.setVar("item");
-        tag.setItems("uno,dos,tres");
         JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
-        lifecycle.invoke(new JspTagLifecycle.Interceptor() {
-            public void evalBody(int iteration, BodyContent body) {
-                if (iteration == 0) {
-                    assertEquals("uno", pageContext.findAttribute("item"));
-                } else if (iteration == 1) {
-                    assertEquals("dos", pageContext.findAttribute("item"));
-                } else if (iteration == 2) {
-                    assertEquals("tres", pageContext.findAttribute("item"));
-                } else {
+        tag.setVar("Item");
+        tag.setItems("One,Two,Three");
+        lifecycle.invoke(new JspTagLifecycle.Interceptor()
+        {
+            public void evalBody(int iteration, BodyContent body)
+            {
+                String item = (String)pageContext.findAttribute("Item");
+                assertNotNull(item);
+                if (iteration == 0)
+                {
+                    assertEquals("One", item);
+                }
+                else if (iteration == 1)
+                {
+                    assertEquals("Two", item);
+                }
+                else if (iteration == 2)
+                {
+                    assertEquals("Three", item);
+                }
+                else
+                {
                     fail("More iterations than expected!");
                 }
             }
@@ -228,95 +266,124 @@
     }
     
     /**
+     * Tests the conditional tag &lt;c:if&gt; by providing a proper, literal 
+     * value to it's <code>test</code> attribute that evaluates to
+     * the boolean value <code>true</code>. The test verifies the correct
+     * behaviour by asserting that the tag's body is not skipped.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testIfTagTrue()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         IfTag tag = new IfTag();
-        tag.setTest("true");
         JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
-        lifecycle.invoke(new JspTagLifecycle.Interceptor() {
-            public void skipBody() {
+        tag.setTest("true");
+        lifecycle.invoke(new JspTagLifecycle.Interceptor()
+        {
+            public void skipBody()
+            {
                 fail("Body should have been evaluated!");
             }
         });
     }
     
     /**
+     * Tests the conditional tag &lt;c:if&gt; by providing a proper, literal 
+     * value to it's <code>test</code> attribute that evaluates to
+     * the boolean value <code>false</code>. The test verifies the correct
+     * behaviour by asserting that the tag's body is not evaluated.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testIfTagFalse()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         IfTag tag = new IfTag();
-        tag.setTest("false");
         JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
-        lifecycle.invoke(new JspTagLifecycle.Interceptor() {
-            public void evalBody(int iteration, BodyContent body) {
+        tag.setTest("false");
+        lifecycle.invoke(new JspTagLifecycle.Interceptor()
+        {
+            public void evalBody(int iteration, BodyContent body)
+            {
                 fail("Body should have been skipped!");
             }
         });
     }
     
     /**
+     * Tests the &lt;c:when&gt;-tag correctly nested inside a &lt;c:choose&gt;
+     * tag, and providing a proper, literal value to it's <code>test</code>
+     * attribute that evaluates to the boolean value <code>true</code>. The test
+     * verifies the correct behaviour by asserting that the tag's body is not
+     * skipped.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testWhenTag()
         throws JspException, IOException
     {
-        
         WhenTag tag = new WhenTag();
-        tag.setTest("true");
         JspTagLifecycle lifecycle =
             new JspTagLifecycle(pageContext, tag, new ChooseTag());
-        lifecycle.invoke(new JspTagLifecycle.Interceptor() {
-            public void skipBody() {
+        tag.setTest("true");
+        lifecycle.invoke(new JspTagLifecycle.Interceptor()
+        {
+            public void skipBody()
+            {
                 fail("Body should have been evaluated!");
             }
         });
     }
     
     /**
+     * Tests the &lt;c:when&gt;-tag correctly nested inside a &lt;c:choose&gt;
+     * tag, and providing a proper, literal value to it's <code>test</code>
+     * attribute that evaluates to the boolean value <code>true</code>. However,
+     * an earlier instance of the <code>&lt;c:when&gt;</code> tag nested in the
+     * parent has already succeeded, so this test asserts that the body of the
+     * later <code>&lt;c:when&gt;</code> does not get evaluated.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testWhenTagNoPermission()
         throws JspException, IOException
     {
-        
         ChooseTag parent = new ChooseTag();
         parent.subtagSucceeded();
         WhenTag tag = new WhenTag();
-        tag.setTest("true");
         JspTagLifecycle lifecycle =
             new JspTagLifecycle(pageContext, tag, parent);
-        lifecycle.invoke(new JspTagLifecycle.Interceptor() {
-            public void evalBody(int iteration, BodyContent body) {
+        tag.setTest("true");
+        lifecycle.invoke(new JspTagLifecycle.Interceptor()
+        {
+            public void evalBody(int iteration, BodyContent body)
+            {
                 fail("Body should have been skipped!");
             }
         });
     }
     
     /**
+     * Tests te <code>&lt;c:when&gt;</code> tag not nested inside a 
+     * <code>&lt;c:choose&gt;</code> tag. The test expects a
+     * <code>JspException</code> to be thrown.
      * 
-     * @throws JspException
-     * @throws IOException
+     * @throws JspException If the tag throws a JSPException
+     * @throws IOException If the tag throws an IOException
      */
     public void testWhenTagWithoutChooseTag()
-        throws JspException, IOException {
-        
+        throws JspException, IOException
+    {
         WhenTag tag = new WhenTag();
+        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
         tag.setTest("true");
-        try {
-            JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
+        try
+        {
             lifecycle.invoke();
             fail("Expected JSPTagException");
         } catch (JspTagException je) {

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to