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