001    package com.mockrunner.tag;
002    
003    import java.util.ArrayList;
004    import java.util.Enumeration;
005    import java.util.HashMap;
006    import java.util.List;
007    import java.util.Map;
008    
009    import javax.servlet.jsp.JspException;
010    import javax.servlet.jsp.JspWriter;
011    import javax.servlet.jsp.PageContext;
012    import javax.servlet.jsp.tagext.BodyContent;
013    import javax.servlet.jsp.tagext.BodyTag;
014    import javax.servlet.jsp.tagext.BodyTagSupport;
015    import javax.servlet.jsp.tagext.IterationTag;
016    import javax.servlet.jsp.tagext.JspTag;
017    import javax.servlet.jsp.tagext.SimpleTag;
018    import javax.servlet.jsp.tagext.Tag;
019    import javax.servlet.jsp.tagext.TagSupport;
020    
021    import com.mockrunner.mock.web.MockBodyContent;
022    
023    /**
024     * Implementation of {@link NestedTag} wrapping tags of
025     * type <code>BodyTag</code>. <code>NestedBodyTag</code> instances 
026     * are created with the help of {@link TagTestModule#createNestedTag}. 
027     * You do not need to create them on your own in the tests.
028     */
029    public class NestedBodyTag extends BodyTagSupport implements NestedTag
030    {
031        private BodyTag tag;
032        private PageContext pageContext;
033        private Map attributes;
034        private List childs;
035        private boolean doRelease;
036        
037        /**
038         * Constructor for a tag with an empty attribute map.
039         * If the specified tag is not an instance of <code>BodyTagSupport</code>,
040         * the methods that delegate to <code>BodyTagSupport</code> specific methods
041         * throw an exception.
042         * @param tag the tag
043         * @param pageContext the corresponding <code>PageContext</code>
044         */
045        public NestedBodyTag(BodyTag tag, PageContext pageContext)
046        {
047            this(tag, pageContext, new HashMap());
048        }
049        
050        /**
051         * Constructor for a tag with the specified attribute map.
052         * If the specified tag is not an instance of <code>BodyTagSupport</code>,
053         * the methods that delegate to <code>BodyTagSupport</code> specific methods
054         * throw an exception.
055         * @param tag the tag
056         * @param pageContext the corresponding <code>PageContext</code>
057         * @param attributes the attribute map
058         */
059        public NestedBodyTag(BodyTag tag, PageContext pageContext, Map attributes)
060        {
061            this.tag = tag;
062            this.pageContext = pageContext;
063            tag.setPageContext(pageContext);
064            childs = new ArrayList();
065            this.attributes = attributes;
066            doRelease = false;
067        }
068        
069        /**
070         * Constructor for a tag with an empty attribute map.
071         * @param tag the tag
072         * @param pageContext the corresponding <code>PageContext</code>
073         */
074        public NestedBodyTag(BodyTagSupport tag, PageContext pageContext)
075        {
076            this(tag, pageContext, new HashMap());
077        }
078        
079        /**
080         * Constructor for a tag with the specified attribute map.
081         * @param tag the tag
082         * @param pageContext the corresponding <code>PageContext</code>
083         * @param attributes the attribute map
084         */
085        public NestedBodyTag(BodyTagSupport tag, PageContext pageContext, Map attributes)
086        {
087            this((BodyTag)tag, pageContext, attributes);
088        }
089        
090        /**
091         * Implementation of {@link NestedTag#setDoRelease}.
092         */
093        public void setDoRelease(boolean doRelease)
094        {
095            this.doRelease = doRelease;
096        }
097        
098        /**
099         * Implementation of {@link NestedTag#setDoReleaseRecursive}.
100         */
101        public void setDoReleaseRecursive(boolean doRelease)
102        {
103            this.doRelease = doRelease;
104            for(int ii = 0; ii < childs.size(); ii++)
105            {
106                Object child = childs.get(ii);
107                if(child instanceof NestedTag)
108                {
109                    ((NestedTag)child).setDoReleaseRecursive(doRelease);
110                }
111            }
112        }
113        
114        /**
115         * Implementation of {@link NestedTag#populateAttributes}.
116         */
117        public void populateAttributes()
118        {
119            TagUtil.populateTag(tag, attributes, doRelease);
120        }
121        
122        /**
123         * Implementation of {@link NestedTag#doLifecycle} for body
124         * tags.
125         */
126        public int doLifecycle() throws JspException
127        {
128            populateAttributes();
129            int result = tag.doStartTag();
130            if(Tag.EVAL_BODY_INCLUDE == result)
131            {
132                TagUtil.evalBody(childs, pageContext);
133                while(IterationTag.EVAL_BODY_AGAIN == doAfterBody())
134                {
135                    TagUtil.evalBody(childs, pageContext);
136                }
137            }
138            else if(BodyTag.EVAL_BODY_BUFFERED == result)
139            {
140                MockBodyContent bodyContent = (MockBodyContent)pageContext.pushBody();
141                tag.setBodyContent(bodyContent);
142                tag.doInitBody();
143                TagUtil.evalBody(childs, pageContext);
144                while(IterationTag.EVAL_BODY_AGAIN == doAfterBody())
145                {
146                    TagUtil.evalBody(childs, pageContext);
147                }
148                pageContext.popBody();
149            }
150            return tag.doEndTag();
151        }
152        
153        /**
154         * Implementation of {@link NestedTag#getTag}.
155         * @throws <code>RuntimeException</code>, if the wrapped tag
156         *         is not an instance of <code>TagSupport</code>
157         */
158        public TagSupport getTag()
159        {
160            checkTagSupport();
161            return (TagSupport)tag;
162        }
163        
164        /**
165         * Implementation of {@link NestedTag#getWrappedTag}.
166         */
167        public JspTag getWrappedTag()
168        {
169            return tag;
170        }
171        
172        /**
173         * Implementation of {@link NestedTag#removeChilds}.
174         */
175        public void removeChilds()
176        {
177            childs.clear();
178        }
179        
180        /**
181         * Implementation of {@link NestedTag#getChilds}.
182         */
183        public List getChilds()
184        {
185            return childs;
186        }
187        
188        /**
189         * Implementation of {@link NestedTag#getChild}.
190         */
191        public Object getChild(int index)
192        {
193            return childs.get(index);
194        }
195        
196        /**
197         * Implementation of {@link NestedTag#addTextChild}.
198         */
199        public void addTextChild(String text)
200        {
201            if(null == text) text = "";
202            childs.add(text);
203        }
204        
205        /**
206         * Implementation of {@link NestedTag#addDynamicChild}.
207         */
208        public void addDynamicChild(DynamicChild child)
209        {
210            if(null == child) return;
211            childs.add(child);
212        }
213        
214        /**
215         * Implementation of {@link NestedTag#addTagChild(Class)}.
216         */
217        public NestedTag addTagChild(Class tag)
218        {
219            return addTagChild(tag, new HashMap());
220        }
221        
222        /**
223         * Implementation of {@link NestedTag#addTagChild(Class, Map)}.
224         */
225        public NestedTag addTagChild(Class tag, Map attributeMap)
226        {
227            Object childTag = TagUtil.createNestedTagInstance(tag, this.pageContext, attributeMap); 
228            return (NestedTag)addChild(childTag);
229        }
230        
231        /**
232         * Implementation of {@link NestedTag#addTagChild(TagSupport)}.
233         */
234        public NestedTag addTagChild(TagSupport tag)
235        {
236            return addTagChild(tag, new HashMap());
237        }
238        
239        /**
240         * Implementation of {@link NestedTag#addTagChild(TagSupport, Map)}.
241         */
242        public NestedTag addTagChild(TagSupport tag, Map attributeMap)
243        {
244            Object childTag = (Object)TagUtil.createNestedTagInstance(tag, this.pageContext, attributeMap);   
245            return (NestedTag)addChild(childTag);
246        }
247        
248        /**
249         * Implementation of {@link NestedTag#addTagChild(JspTag)}.
250         */
251        public NestedTag addTagChild(JspTag tag)
252        {
253            return addTagChild(tag, new HashMap());
254        }
255        
256        /**
257         * Implementation of {@link NestedTag#addTagChild(JspTag, Map)}.
258         */
259        public NestedTag addTagChild(JspTag tag, Map attributeMap)
260        {
261            Object childTag = TagUtil.createNestedTagInstance(tag, this.pageContext, attributeMap);   
262            return (NestedTag)addChild(childTag);
263        }
264        
265        /**
266         * Delegates to wrapped tag.
267         */
268        public int doAfterBody() throws JspException
269        {
270            return tag.doAfterBody();
271        }
272        
273        /**
274         * Delegates to wrapped tag.
275         */
276        public int doEndTag() throws JspException
277        {
278            return tag.doEndTag();
279        }
280        
281        /**
282         * Delegates to wrapped tag.
283         */
284        public int doStartTag() throws JspException
285        {
286            return tag.doStartTag();
287        }
288        
289        /**
290         * Delegates to wrapped tag.
291         * @throws <code>RuntimeException</code>, if the wrapped tag
292         *         is not an instance of <code>TagSupport</code>
293         */
294        public String getId()
295        {
296            checkTagSupport();
297            return ((TagSupport)tag).getId();
298        }
299        
300        /**
301         * Delegates to wrapped tag.
302         */
303        public Tag getParent()
304        {
305            return tag.getParent();
306        }
307        
308        /**
309         * Delegates to wrapped tag.
310         * @throws <code>RuntimeException</code>, if the wrapped tag
311         *         is not an instance of <code>TagSupport</code>
312         */
313        public Object getValue(String key)
314        {
315            checkTagSupport();
316            return ((TagSupport)tag).getValue(key);
317        }
318        
319        /**
320         * Delegates to wrapped tag.
321         * @throws <code>RuntimeException</code>, if the wrapped tag
322         *         is not an instance of <code>TagSupport</code>
323         */
324        public Enumeration getValues()
325        {
326            checkTagSupport();
327            return ((TagSupport)tag).getValues();
328        }
329        
330        /**
331         * Delegates to wrapped tag.
332         */
333        public void release()
334        {
335            tag.release();
336        }
337        
338        /**
339         * Delegates to wrapped tag.
340         * @throws <code>RuntimeException</code>, if the wrapped tag
341         *         is not an instance of <code>TagSupport</code>
342         */
343        public void removeValue(String value)
344        {
345            checkTagSupport();
346            ((TagSupport)tag).removeValue(value);
347        }
348        
349        /**
350         * Delegates to wrapped tag.
351         * @throws <code>RuntimeException</code>, if the wrapped tag
352         *         is not an instance of <code>TagSupport</code>
353         */
354        public void setId(String id)
355        {
356            checkTagSupport();
357            ((TagSupport)tag).setId(id);
358        }
359        
360        /**
361         * Delegates to wrapped tag. Also calls <code>setPageContext</code>
362         * for all child tags.
363         */
364        public void setPageContext(PageContext pageContext)
365        {
366            this.pageContext = pageContext;
367            tag.setPageContext(pageContext);
368            for(int ii = 0; ii < childs.size(); ii++)
369            {
370                Object child = childs.get(ii);
371                if(child instanceof Tag)
372                {
373                    ((Tag)child).setPageContext(pageContext);
374                }
375                else if(child instanceof SimpleTag)
376                {
377                    ((SimpleTag)child).setJspContext(pageContext);
378                }
379            }
380        }
381        
382        /**
383         * Delegates to wrapped tag.
384         */
385        public void setParent(Tag parent)
386        {
387            tag.setParent(parent);
388        }
389        
390        /**
391         * Delegates to wrapped tag.
392         * @throws <code>RuntimeException</code>, if the wrapped tag
393         *         is not an instance of <code>TagSupport</code>
394         */
395        public void setValue(String key, Object value)
396        {
397            checkTagSupport();
398            ((TagSupport)tag).setValue(key, value);
399        }
400        
401        /**
402         * Delegates to wrapped tag.
403         */ 
404        public void doInitBody() throws JspException
405        {
406            tag.doInitBody();
407        }
408        
409        /**
410         * Delegates to wrapped tag.
411         * @throws <code>RuntimeException</code>, if the wrapped tag
412         *         is not an instance of <code>BodyTagSupport</code>
413         */
414        public BodyContent getBodyContent()
415        {
416            checkTagSupport();
417            return ((BodyTagSupport)tag).getBodyContent();
418        }
419        
420        /**
421         * Delegates to wrapped tag.
422         * @throws <code>RuntimeException</code>, if the wrapped tag
423         *         is not an instance of <code>BodyTagSupport</code>
424         */
425        public JspWriter getPreviousOut()
426        {
427            checkTagSupport();
428            return ((BodyTagSupport)tag).getPreviousOut();
429        }
430        
431        /**
432         * Delegates to wrapped tag.
433         */
434        public void setBodyContent(BodyContent content)
435        {
436            tag.setBodyContent(content);
437        }
438        
439        /**
440         * Dumps the content of this and the nested tags.
441         */
442        public String toString()
443        {
444            return TagUtil.dumpTag(this, new StringBuffer(), 0);
445        }
446        
447        private NestedTag addChild(Object childTag)
448        {
449            if(childTag instanceof Tag)
450            {
451                ((Tag)childTag).setParent(this.tag);
452            }
453            else if(childTag instanceof SimpleTag)
454            {
455                ((SimpleTag)childTag).setParent(this.tag);
456            }
457            childs.add(childTag);
458            return (NestedTag)childTag;
459        }
460        
461        private void checkTagSupport()
462        {
463            if(!(tag instanceof BodyTagSupport))
464            {
465                throw new RuntimeException("This method can only be called if the wrapped tag is an instance of BodyTagSupport.");
466            }
467        }
468    }