001    package com.mockrunner.tag;
002    
003    import java.io.IOException;
004    import java.util.Iterator;
005    import java.util.List;
006    import java.util.Map;
007    
008    import javax.servlet.jsp.JspContext;
009    import javax.servlet.jsp.JspException;
010    import javax.servlet.jsp.PageContext;
011    import javax.servlet.jsp.tagext.BodyTag;
012    import javax.servlet.jsp.tagext.DynamicAttributes;
013    import javax.servlet.jsp.tagext.SimpleTag;
014    import javax.servlet.jsp.tagext.Tag;
015    
016    import org.apache.commons.beanutils.BeanUtils;
017    import org.apache.commons.beanutils.PropertyUtils;
018    import org.apache.commons.logging.Log;
019    import org.apache.commons.logging.LogFactory;
020    
021    import com.mockrunner.base.NestedApplicationException;
022    import com.mockrunner.util.common.MethodUtil;
023    import com.mockrunner.util.common.StringUtil;
024    
025    /**
026     * Util class for tag test framework.
027     * Please note, that the methods of this class take
028     * <code>Object</code> parameters where <code>JspTag</code>
029     * or <code>JspContext</code> would be suitable. The reason is,
030     * that these classes do not exist in J2EE 1.3. This class is
031     * usable with J2EE 1.3 and J2EE 1.4.
032     */
033    public class TagUtil
034    {
035        private final static Log log = LogFactory.getLog(TagUtil.class);
036        
037        /**
038         * Creates an {@link com.mockrunner.tag.NestedTag} instance wrapping the
039         * specified tag. Returns an instance of {@link com.mockrunner.tag.NestedStandardTag}
040         * resp. {@link com.mockrunner.tag.NestedBodyTag} depending on the
041         * type of specified tag.
042         * @param tag the tag class
043         * @param pageContext the corresponding <code>PageContext</code> or <code>JspContext</code>
044         * @param attributes the attribute map
045         * @return the instance of {@link com.mockrunner.tag.NestedTag}
046         * @throws IllegalArgumentException if <code>tag</code> is <code>null</code>
047         */
048        public static Object createNestedTagInstance(Class tag, Object pageContext, Map attributes)
049        {
050            if(null == tag) throw new IllegalArgumentException("tag must not be null");
051            Object tagObject;
052            try
053            {
054                tagObject = tag.newInstance();
055            }
056            catch(Exception exc)
057            {
058                log.error(exc.getMessage(), exc);
059                throw new NestedApplicationException(exc);
060            }
061            return createNestedTagInstance(tagObject, pageContext, attributes);
062        }
063        
064        /**
065         * Creates an {@link com.mockrunner.tag.NestedTag} instance wrapping the
066         * specified tag. Returns an instance of {@link com.mockrunner.tag.NestedStandardTag}
067         * resp. {@link com.mockrunner.tag.NestedBodyTag} depending on the
068         * type of specified tag.
069         * @param tag the tag
070         * @param pageContext the corresponding <code>PageContext</code> or <code>JspContext</code>
071         * @param attributes the attribute map
072         * @return the instance of {@link com.mockrunner.tag.NestedTag}
073         * @throws IllegalArgumentException if <code>tag</code> is <code>null</code>
074         */
075        public static Object createNestedTagInstance(Object tag, Object pageContext, Map attributes)
076        {
077            if(null == tag) throw new IllegalArgumentException("tag must not be null");
078            Object nestedTag = null;
079            if(tag instanceof BodyTag)
080            {
081                checkPageContext(pageContext);
082                nestedTag = new NestedBodyTag((BodyTag)tag, (PageContext)pageContext, attributes);
083            }
084            else if(tag instanceof Tag)
085            {
086                checkPageContext(pageContext);
087                nestedTag = new NestedStandardTag((Tag)tag, (PageContext)pageContext, attributes);
088            }
089            else if(tag instanceof SimpleTag)
090            {
091                checkJspContext(pageContext);
092                nestedTag = new NestedSimpleTag((SimpleTag)tag, (JspContext)pageContext, attributes);
093            }
094            else
095            {
096                throw new IllegalArgumentException("tag must be an instance of Tag or SimpleTag");
097            }
098            return nestedTag;
099        }
100        
101        private static void checkPageContext(Object pageContext)
102        {
103            if(pageContext instanceof PageContext) return;
104            throw new IllegalArgumentException("pageContext must be an instance of PageContext");
105        }
106        
107        private static void checkJspContext(Object pageContext)
108        {
109            if(pageContext instanceof JspContext) return;
110            throw new IllegalArgumentException("pageContext must be an instance of JspContext");
111        }
112        
113        /**
114         * Populates the specified attributes to the specified tag. Calls the
115         * <code>release</code> method before populating, if <i>doRelease</i> is set to
116         * <code>true</code>.
117         * @param tag the tag
118         * @param attributes the attribute map
119         * @param doRelease should release be called
120         */
121        public static void populateTag(Object tag, Map attributes, boolean doRelease)
122        {
123            if(doRelease) MethodUtil.invoke(tag, "release");
124            if(null == attributes || attributes.isEmpty()) return;
125            try
126            {
127                Iterator names = attributes.keySet().iterator();
128                while(names.hasNext()) 
129                {
130                    String currentName = (String)names.next();
131                    Object currentValue = attributes.get(currentName);
132                    if(currentValue instanceof DynamicAttribute)
133                    {
134                        populateDynamicAttribute(tag, currentName, (DynamicAttribute)currentValue);
135                        return;
136                    }
137                    if(PropertyUtils.isWriteable(tag, currentName)) 
138                    {
139                        BeanUtils.copyProperty(tag, currentName, attributes.get(currentName));
140                    }
141                    else if(tag instanceof DynamicAttributes)
142                    {
143                        populateDynamicAttribute(tag, currentName, new DynamicAttribute(null, currentValue));
144                    }
145                }
146            }
147            catch(IllegalArgumentException exc)
148            {
149                throw exc;
150            }
151            catch(Exception exc)
152            {
153                log.error(exc.getMessage(), exc);
154                throw new NestedApplicationException(exc);
155            }
156        }
157        
158        private static void populateDynamicAttribute(Object tag, String name, DynamicAttribute attribute) throws JspException
159        {
160            if(!(tag instanceof DynamicAttributes))
161            {
162                String message = "Attribute " + name + " specified as dynamic attribute but tag ";
163                message += "is not an instance of avax.servlet.jsp.tagext.DynamicAttributes.";
164                throw new IllegalArgumentException(message);
165            }
166            ((DynamicAttributes)tag).setDynamicAttribute(attribute.getUri(), name, attribute.getValue());
167        }
168        
169        /**
170         * Handles body evaluation of a tag. Iterated through the childs.
171         * If the child is an instance of {@link com.mockrunner.tag.NestedTag},
172         * the {@link com.mockrunner.tag.NestedTag#doLifecycle} method of
173         * this tag is called. If the child is an instance of 
174         * {@link com.mockrunner.tag.DynamicChild}, the 
175         * {@link com.mockrunner.tag.DynamicChild#evaluate} method is called
176         * and the result is written to the out <code>JspWriter</code> as a
177         * string. If the result is another object (usually a string) it is written
178         * to the out <code>JspWriter</code> (the <code>toString</code> method will
179         * be called).
180         * @param bodyList the list of body entries
181         * @param pageContext the corresponding <code>PageContext</code> or <code>JspContext</code>
182         */
183        public static void evalBody(List bodyList, Object pageContext) throws JspException
184        {
185            for(int ii = 0; ii < bodyList.size(); ii++)
186            {
187                Object nextChild = bodyList.get(ii);
188                if(nextChild instanceof NestedBodyTag)
189                {
190                    int result = ((NestedBodyTag)nextChild).doLifecycle();
191                    if(Tag.SKIP_PAGE == result) return;
192                }
193                else if(nextChild instanceof NestedStandardTag)
194                {
195                    int result = ((NestedStandardTag)nextChild).doLifecycle();
196                    if(Tag.SKIP_PAGE == result) return;
197                }
198                else if(nextChild instanceof NestedSimpleTag)
199                {
200                    ((NestedSimpleTag)nextChild).doLifecycle();
201                }
202                else
203                {
204                    try
205                    {
206                        if(pageContext instanceof PageContext)
207                        {
208                            ((PageContext)pageContext).getOut().print(getChildText(nextChild));
209                        }
210                        else if(pageContext instanceof JspContext)
211                        {
212                            ((JspContext)pageContext).getOut().print(getChildText(nextChild));
213                        }
214                        else
215                        {
216                            throw new IllegalArgumentException("pageContext must be an instance of JspContext");
217                        }
218                    }
219                    catch(IOException exc)
220                    {
221                        log.error(exc.getMessage(), exc);
222                        throw new NestedApplicationException(exc);
223                    }       
224                }
225            }
226        }
227        
228        private static String getChildText(Object child)
229        {
230            if(null == child) return "null";
231            if(child instanceof DynamicChild)
232            {
233                Object result = ((DynamicChild)child).evaluate();
234                if(null == result) return "null";
235                return result.toString();
236            }
237            return child.toString();
238        }
239        
240        /**
241         * Helper method to dump tags incl. child tags.
242         */
243        public static String dumpTag(NestedTag tag, StringBuffer buffer, int level)
244        {
245            StringUtil.appendTabs(buffer, level);
246            buffer.append("<" + tag.getClass().getName() + ">\n");
247            TagUtil.dumpTagTree(tag.getChilds(), buffer, level);
248            StringUtil.appendTabs(buffer, level);
249            buffer.append("</" + tag.getClass().getName() + ">");
250            return buffer.toString();
251        }
252        
253        /**
254         * Helper method to dump tags incl. child tags.
255         */
256        public static void dumpTagTree(List bodyList, StringBuffer buffer, int level)
257        {
258            for(int ii = 0; ii < bodyList.size(); ii++)
259            {
260                Object nextChild = bodyList.get(ii);
261                if(nextChild instanceof NestedTag)
262                {
263                    dumpTag((NestedTag)nextChild, buffer, level + 1);
264                }
265                else
266                {
267                    StringUtil.appendTabs(buffer, level + 1);
268                    buffer.append(bodyList.get(ii).toString());
269                }
270                buffer.append("\n");
271            }
272        }
273    }