View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.custom.tree2;
21  
22  
23  import org.apache.myfaces.component.html.ext.HtmlGraphicImage;
24  import org.apache.myfaces.renderkit.html.util.AddResource;
25  import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
26  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
27  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
28  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
29  
30  import javax.faces.component.NamingContainer;
31  import javax.faces.component.UIComponent;
32  import javax.faces.component.UICommand;
33  import javax.faces.component.UIGraphic;
34  import javax.faces.component.UIViewRoot;
35  import javax.faces.component.UIParameter;
36  import javax.faces.context.FacesContext;
37  import javax.faces.context.ResponseWriter;
38  import javax.faces.render.Renderer;
39  
40  import java.io.IOException;
41  import java.io.UnsupportedEncodingException;
42  import java.util.Map;
43  import java.util.Iterator;
44  import java.net.URLDecoder;
45  import javax.servlet.http.Cookie;
46  import java.util.HashMap;
47  
48  /**
49   * @JSFRenderer
50   *   renderKitId = "HTML_BASIC" 
51   *   family = "org.apache.myfaces.HtmlTree2"
52   *   type = "org.apache.myfaces.HtmlTree2"
53   *   
54   * @author Sean Schofield
55   * @author Chris Barlow
56   * @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
57   * @version $Revision: 987802 $ $Date: 2010-08-21 12:41:17 -0500 (Sat, 21 Aug 2010) $
58   */
59  public class HtmlTreeRenderer extends Renderer
60  {
61      protected static final String TOGGLE_SPAN = "org.apache.myfaces.tree.TOGGLE_SPAN";
62      protected static final String ROOT_NODE_ID = "0";
63  
64      private static final String NAV_COMMAND = "org.apache.myfaces.tree.NAV_COMMAND";
65      private static final String ENCODING = "UTF-8";
66      private static final String ATTRIB_DELIM = ";";
67      private static final String ATTRIB_KEYVAL = "=";
68      private static final String NODE_STATE_EXPANDED = "x";
69      private static final String NODE_STATE_CLOSED = "c";
70      private static final String SEPARATOR = String.valueOf(NamingContainer.SEPARATOR_CHAR);
71      private static final String IMAGE_PREFIX = "t2";
72      private static final String TOGGLE_ID = "t2g";
73  
74      private static final int NOTHING = 0;
75      private static final int CHILDREN = 1;
76      private static final int EXPANDED = 2;
77      private static final int LINES = 4;
78      private static final int LAST = 8;
79      private int counter = 0;
80  
81      // see superclass for documentation
82      public boolean getRendersChildren()
83      {
84          return true;
85      }
86  
87      private void restoreStateFromCookies(FacesContext context, UIComponent component) {
88          String nodeId = null;
89          HtmlTree tree = (HtmlTree)component;
90          TreeState state = tree.getDataModel().getTreeState();
91  
92          Map cookieMap = context.getExternalContext().getRequestCookieMap();
93          Cookie treeCookie = (Cookie)cookieMap.get(component.getId());
94          if (treeCookie == null || treeCookie.getValue() == null)
95          {
96              return;
97          }
98  
99          String nodeState = null;
100         Map attrMap = getCookieAttr(treeCookie);
101         Iterator i = attrMap.keySet().iterator();
102         while (i.hasNext())
103         {
104             nodeId = (String)i.next();
105             nodeState = (String)attrMap.get(nodeId);
106 
107             if (NODE_STATE_EXPANDED.equals(nodeState))
108             {
109                 if (!state.isNodeExpanded(nodeId))
110                 {
111                     state.toggleExpanded(nodeId);
112                 }
113             }
114             else if (NODE_STATE_CLOSED.equals(nodeState))
115             {
116                 if (state.isNodeExpanded(nodeId))
117                 {
118                     state.toggleExpanded(nodeId);
119                 }
120             }
121         }
122     }
123 
124 
125     public void decode(FacesContext context, UIComponent component)
126     {
127         super.decode(context, component);
128 
129         // see if one of the nav nodes was clicked, if so, then toggle appropriate node
130         String nodeId = null;
131         HtmlTree tree = (HtmlTree)component;
132 
133         if (tree.isClientSideToggle() && tree.isPreserveToggle())
134         {
135             restoreStateFromCookies(context, component);
136         }
137         else
138         {
139             nodeId = (String)context.getExternalContext().getRequestParameterMap().get(tree.getId() + SEPARATOR + NAV_COMMAND);
140 
141             if (nodeId == null || nodeId.equals(""))
142             {
143                 return;
144             }
145 
146             component.queueEvent(new ToggleExpandedEvent(component, nodeId));
147         }
148     }
149 
150     public void encodeBegin(FacesContext context, UIComponent component) throws IOException
151     {
152         // Fixes TOMAHAWK-437
153         //HtmlTree tree = (HtmlTree)component;
154         //if (!tree.getDataModel().getTreeState().isTransient()
155         //        && tree.isClientSideToggle()
156         //        && tree.isPreserveToggle())
157         //{
158         //    restoreStateFromCookies(context, component);
159         //}
160 
161         // write javascript functions
162         encodeJavascript(context, component);
163     }
164 
165     /**
166      * Renders the whole tree.  It generates a <code>&lt;span></code> element with an <code>id</code>
167      * attribute if the component has been given an explicit ID.  The model nodes are rendered
168      * recursively by the private <code>encodeNodes</code> method.
169      *
170      * @param context FacesContext
171      * @param component The component whose children are to be rendered
172      * @throws IOException
173      */
174     public void encodeChildren(FacesContext context, UIComponent component) throws IOException
175     {
176         HtmlTree tree = (HtmlTree)component;
177 
178         if (!component.isRendered()) return;
179         if (tree.getValue() == null) return;
180 
181         ResponseWriter out = context.getResponseWriter();
182         String clientId = null;
183 
184         if (component.getId() != null && !component.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
185         {
186             clientId = component.getClientId(context);
187         }
188 
189         boolean isOuterSpanUsed = false;
190 
191         if (clientId != null)
192         {
193             isOuterSpanUsed = true;
194             out.startElement("span", component);
195             out.writeAttribute("id", clientId, "id");
196         }
197 
198         boolean clientSideToggle = tree.isClientSideToggle();
199         boolean showRootNode = tree.isShowRootNode();
200 
201         TreeState state = tree.getDataModel().getTreeState();
202         TreeWalker walker = tree.getDataModel().getTreeWalker();
203         walker.reset();
204         walker.setTree(tree);
205 
206         walker.setCheckState(!clientSideToggle); // walk all nodes in client mode
207 
208         if (showRootNode)
209         {
210             // encode the tree (starting with the root node)
211             if (walker.next())
212             {
213                 encodeTree(context, out, tree, walker);
214             }
215         }
216         else
217         {
218             // skip the root node
219             walker.next();
220             TreeNode rootNode = tree.getNode();
221 
222             // now mark the root as expanded (so we don't stop there)
223             String rootNodeId = tree.getNodeId();
224             if(!state.isNodeExpanded(rootNodeId))
225             {
226                 state.toggleExpanded(rootNodeId);
227             }
228 
229             // now encode each of the nodes in the level immediately below the root
230             for (int i=0; i < rootNode.getChildCount(); i++)
231             {
232                 if (walker.next())
233                 {
234                     encodeTree(context, out, tree, walker);
235                 }
236             }
237         }
238 
239         // reset the current node id once we're done encoding everything
240         tree.setNodeId(null);
241 
242         if (isOuterSpanUsed)
243         {
244             out.endElement("span");
245         }
246     }
247 
248     /**
249      * Encodes the tree and its children.
250      *
251      * @param context FacesContext
252      * @param out ResponseWriter
253      * @param tree HtmlTree
254      * @param walker TreeWalker
255      * @throws IOException
256      */
257     protected void encodeTree(FacesContext context, ResponseWriter out, HtmlTree tree, TreeWalker walker)
258         throws IOException
259     {
260         boolean clientSideToggle = tree.isClientSideToggle();
261 
262         // encode the current node
263         HtmlRendererUtils.writePrettyLineSeparator(context);
264         beforeNodeEncode(context, out, tree);
265         encodeCurrentNode(context, out, tree);
266         afterNodeEncode(context, out);
267 
268         // if client side toggling is on, add a span to be used for displaying/hiding children
269         if (clientSideToggle)
270         {
271             String spanId = TOGGLE_SPAN + ":" + tree.getClientId(context) + ":" + tree.getNodeId();
272 
273             out.startElement(HTML.SPAN_ELEM, tree);
274             out.writeAttribute(HTML.ID_ATTR, spanId, null);
275 
276             if (tree.isNodeExpanded())
277             {
278                 out.writeAttribute(HTML.STYLE_ATTR, "display:block", null);
279             }
280             else
281             {
282                 out.writeAttribute(HTML.STYLE_ATTR, "display:none", null);
283             }
284         }
285 
286         TreeNode node = tree.getNode();
287 
288         for (int i=0; i < node.getChildCount(); i++)
289         {
290             if (walker.next())
291             {
292                 encodeTree(context, out, tree, walker);
293             }
294         }
295 
296         if (clientSideToggle)
297         {
298             out.endElement(HTML.SPAN_ELEM);
299         }
300     }
301 
302     /**
303      * Encodes the current node.  It is protected so that custom {@link Renderer}s can extend it.  That might be useful
304      * if you would like to render additional per node information besides the tree node.
305      *
306      * @param context FacesContext
307      * @param out ResponseWriter
308      * @param tree HtmlTree
309      * @throws IOException
310      */
311     protected void encodeCurrentNode(FacesContext context, ResponseWriter out, HtmlTree tree)
312         throws IOException
313     {
314         TreeNode node = tree.getNode();
315 
316         // set configurable values
317         boolean showRootNode = tree.isShowRootNode();
318         boolean showNav = tree.isShowNav();
319         boolean showLines = tree.isShowLines();
320         boolean clientSideToggle = tree.isClientSideToggle();
321 
322         if (clientSideToggle)
323         {
324             // we must show the nav icons if client side toggle is enabled (regardless of what user says)
325             showNav = true;
326         }
327 
328         UIComponent nodeTypeFacet = tree.getFacet(node.getType());
329         UIComponent nodeImgFacet = null;
330 
331         if (nodeTypeFacet == null)
332         {
333             throw new IllegalArgumentException("Unable to locate facet with the name: " + node.getType());
334         }
335 
336         // render node padding
337         String[] pathInfo = tree.getPathInformation(tree.getNodeId());
338         int paddingLevel = pathInfo.length - 1;
339 
340         for (int i = (showRootNode ? 0 : 1); i < paddingLevel; i++)
341         {
342             boolean lastChild = tree.isLastChild((String)pathInfo[i]);
343             String lineSrc = (!lastChild && showLines)
344                              ? getImageSrc(context, tree, "line-trunk.gif", true)
345                              : getImageSrc(context, tree, "spacer.gif", true);
346             String altString = (!lastChild && showLines)
347                              ? "line trunk"
348                              : "spacer";             
349             
350 
351             out.startElement(HTML.TD_ELEM, tree);
352             out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
353             out.writeAttribute(HTML.HEIGHT_ATTR, "100%", null);
354             out.writeURIAttribute(HTML.STYLE_ATTR, "background-image:url('" + lineSrc + "');", null); //we use "style" because "background" is no valid xhtml attribute for td
355             out.startElement(HTML.IMG_ELEM, tree);
356             out.writeURIAttribute(HTML.SRC_ATTR, lineSrc, null);
357             out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
358             out.writeAttribute(HTML.HEIGHT_ATTR, "18", null);
359             out.writeAttribute(HTML.BORDER_ATTR, "0", null);
360             out.writeAttribute(HTML.ALT_ATTR, altString, null); // placing a suitable description for the alt.
361             out.endElement(HTML.IMG_ELEM);
362             out.endElement(HTML.TD_ELEM);
363         }
364 
365         if (showNav)
366         {
367             nodeImgFacet = encodeNavigation(context, out, tree);
368         }
369 
370         // render node
371         out.startElement(HTML.TD_ELEM, tree);
372         if (nodeImgFacet != null)
373         {
374             RendererUtils.renderChild(context, nodeImgFacet);
375         }
376         RendererUtils.renderChild(context, nodeTypeFacet);
377         out.endElement(HTML.TD_ELEM);
378     }
379 
380     protected void beforeNodeEncode(FacesContext context, ResponseWriter out, HtmlTree tree)
381         throws IOException
382     {
383         out.startElement(HTML.TABLE_ELEM, tree);
384         out.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
385         out.writeAttribute(HTML.CELLSPACING_ATTR, "0", null);
386         out.writeAttribute(HTML.BORDER_ATTR, "0", null);
387         out.startElement(HTML.TR_ELEM, tree);
388     }
389 
390     protected void afterNodeEncode(FacesContext context, ResponseWriter out)
391         throws IOException
392     {
393         out.endElement(HTML.TR_ELEM);
394         out.endElement(HTML.TABLE_ELEM);
395     }
396 
397     /**
398      * Handles the encoding related to the navigation functionality.
399      *
400      * @param context FacesContext
401      * @param out ResponseWriter
402      * @param tree HtmlTree
403      * @return The additional navigation image to display inside the node (if any).  Only used with client-side toggle.
404      * @throws IOException
405      */
406     private UIComponent encodeNavigation(FacesContext context, ResponseWriter out, HtmlTree tree)
407         throws IOException
408     {
409         TreeNode node = tree.getNode();
410         String nodeId = tree.getNodeId();
411         String spanId = TOGGLE_SPAN + ":" + tree.getClientId(context) + ":" + nodeId;//TOGGLE_SPAN + nodeId;
412         boolean showLines = tree.isShowLines();
413         boolean clientSideToggle = tree.isClientSideToggle();
414         UIComponent nodeTypeFacet = tree.getFacet(node.getType());
415         String navSrc = null;
416         String altSrc = null;
417         UIComponent nodeImgFacet = null;
418 
419         int bitMask = NOTHING;
420         bitMask += (node.isLeaf()) ? NOTHING : CHILDREN;
421         if (bitMask == CHILDREN) // if there are no children, ignore expand state -> more flexible with dynamic tree-structures
422             bitMask += (tree.isNodeExpanded()) ? EXPANDED : NOTHING;
423         bitMask += (tree.isLastChild(tree.getNodeId())) ? LAST : NOTHING;
424         bitMask += (showLines) ? LINES : NOTHING;
425 
426         switch (bitMask)
427         {
428             case (NOTHING):
429 
430             case (LAST):
431                 navSrc = "spacer.gif";
432                 break;
433 
434             case (LINES):
435                 navSrc = "line-middle.gif";
436                 break;
437 
438             case (LINES + LAST):
439                 navSrc = "line-last.gif";
440                 break;
441 
442             case (CHILDREN):
443 
444             case (CHILDREN + LAST):
445                 navSrc = "nav-plus.gif";
446                 altSrc = "nav-minus.gif";
447                 break;
448 
449             case (CHILDREN + LINES):
450 
451                 navSrc = "nav-plus-line-middle.gif";
452                 altSrc = "nav-minus-line-middle.gif";
453                 break;
454 
455             case (CHILDREN + LINES + LAST):
456 
457                 navSrc = "nav-plus-line-last.gif";
458                 altSrc = "nav-minus-line-last.gif";
459                 break;
460 
461             case (CHILDREN + EXPANDED):
462 
463             case (CHILDREN + EXPANDED + LAST):
464                 navSrc = "nav-minus.gif";
465                 altSrc = "nav-plus.gif";
466                 break;
467 
468             case (CHILDREN + EXPANDED + LINES):
469                 navSrc = "nav-minus-line-middle.gif";
470                 altSrc = "nav-plus-line-middle.gif";
471                 break;
472 
473             case (CHILDREN + EXPANDED + LINES + LAST):
474                 navSrc = "nav-minus-line-last.gif";
475                 altSrc = "nav-plus-line-last.gif";
476                 break;
477 
478             // unacceptable bitmask combinations
479 
480             case (EXPANDED + LINES):
481             case (EXPANDED + LINES + LAST):
482             case (EXPANDED):
483             case (EXPANDED + LAST):
484 
485                 throw new IllegalStateException("Encountered a node ["+ nodeId + "] + with an illogical state.  " +
486                                                 "Node is expanded but it is also considered a leaf (a leaf cannot be considered expanded.");
487 
488             default:
489                 // catch all for any other combinations
490                 throw new IllegalArgumentException("Invalid bit mask of " + bitMask);
491         }
492 
493         // adjust navSrc and altSrc so that the images can be retrieved using the extensions filter
494         String navSrcUrl = getImageSrc(context, tree, navSrc, false);
495         navSrc = getImageSrc(context, tree, navSrc, true);
496         altSrc = getImageSrc(context, tree, altSrc, true);
497 
498         // render nav cell
499         out.startElement(HTML.TD_ELEM, tree);
500         out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
501         out.writeAttribute(HTML.HEIGHT_ATTR, "100%", null);
502         out.writeAttribute("valign", "top", null);
503 
504         if ((bitMask & LINES)!=0 && (bitMask & LAST)==0)
505         {
506             //out.writeURIAttribute("background", getImageSrc(context, tree, "line-trunk.gif", true), null);
507             out.writeURIAttribute(HTML.STYLE_ATTR, "background-image:url('" + getImageSrc(context, tree, "line-trunk.gif", true) + "');", null); 
508         }
509 
510 //      add the appropriate image for the nav control
511         UIGraphic image = new HtmlGraphicImage();
512         String imageId = IMAGE_PREFIX+(counter++);
513         image.setId(imageId);
514         image.setUrl(navSrcUrl);
515         
516         Map imageAttrs = image.getAttributes();
517         imageAttrs.put(HTML.WIDTH_ATTR, "19");
518         imageAttrs.put(HTML.HEIGHT_ATTR, "18");
519         imageAttrs.put(HTML.BORDER_ATTR, "0");
520         imageAttrs.put(HTML.ALT_ATTR, "Nav control image"); // placing a suitable description for the alt.
521 
522         if (clientSideToggle)
523         {
524             /**
525              * With client side toggle, user has the option to specify open/closed images for the node (in addition to
526              * the navigtion ones provided by the component.)
527              */
528             String expandImgSrc = "";
529             String collapseImgSrc = "";
530             String nodeImageId = "";
531 
532             UIComponent expandFacet = nodeTypeFacet.getFacet("expand");
533             if (expandFacet != null)
534             {
535                 UIGraphic expandImg = (UIGraphic)expandFacet;
536                 expandImgSrc = context.getApplication().getViewHandler().getResourceURL(context, expandImg.getUrl());
537                 if (expandImg.isRendered())
538                 {
539                     expandImg.setId(imageId + NODE_STATE_EXPANDED);
540                     expandImg.setParent(tree);
541                     nodeImageId = expandImg.getClientId(context);
542                     nodeImgFacet = expandFacet;
543                 }
544             }
545 
546             UIComponent collapseFacet = nodeTypeFacet.getFacet("collapse");
547             if (collapseFacet != null)
548             {
549                 UIGraphic collapseImg = (UIGraphic)collapseFacet;
550                 collapseImgSrc = context.getApplication().getViewHandler().getResourceURL(context, collapseImg.getUrl());
551                 if (collapseImg.isRendered())
552                 {
553                     collapseImg.setId(imageId + NODE_STATE_CLOSED);
554                     collapseImg.setParent(tree);
555                     nodeImageId = collapseImg.getClientId(context);
556                     nodeImgFacet = collapseFacet;
557                 }
558             }
559 
560             image.setParent(tree);
561             if (node.getChildCount() > 0)
562             {
563                 String onClick = new StringBuffer()
564                     .append("treeNavClick('")
565                     .append(spanId)
566                     .append("', '")
567                     .append(image.getClientId(context))
568                     .append("', '")
569                     .append(navSrc)
570                     .append("', '")
571                     .append(altSrc)
572                     .append("', '")
573                     .append(nodeImageId)
574                     .append("', '")
575                     .append(expandImgSrc)
576                     .append("', '")
577                     .append(collapseImgSrc)
578                     .append("', '")
579                     .append(tree.getId())
580                     .append("', '")
581                     .append(nodeId)
582                     .append("');")
583                     .toString();
584 
585                 imageAttrs.put(HTML.ONCLICK_ATTR, onClick);
586                 imageAttrs.put(HTML.STYLE_ATTR, "cursor:pointer;cursor:hand");
587             }
588             RendererUtils.renderChild(context, image);
589 
590         }
591         else
592         {
593             if (node.getChildCount() > 0)
594             {
595                 // set up the expand control and remove whatever children (if any) this control had previously
596                 UICommand expandControl = tree.getExpandControl();
597                 expandControl.getChildren().clear();
598                 expandControl.setId(TOGGLE_ID);
599 
600                 UIParameter param = new UIParameter();
601                 param.setName(tree.getId() + NamingContainer.SEPARATOR_CHAR + NAV_COMMAND);
602                 param.setValue(tree.getNodeId());
603                 expandControl.getChildren().add(param);
604                 expandControl.getChildren().add(image);
605 
606                 RendererUtils.renderChild(context, expandControl);
607             }
608             else
609             {
610                 RendererUtils.renderChild(context, image);
611             }
612         }
613         out.endElement(HTML.TD_ELEM);
614 
615         return nodeImgFacet;
616     }
617 
618     /**
619      * Encodes any stand-alone javascript functions that are needed.  Uses either the extension filter, or a
620      * user-supplied location for the javascript files.
621      *
622      * @param context FacesContext
623      * @param component UIComponent
624      * @throws IOException
625      */
626     private void encodeJavascript(FacesContext context, UIComponent component) throws IOException
627     {
628         // render javascript function for client-side toggle (it won't be used if user has opted for server-side toggle)
629         String javascriptLocation = ((HtmlTree)component).getJavascriptLocation();
630         AddResource addResource = AddResourceFactory.getInstance(context);
631         if (javascriptLocation == null)
632         {
633             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlTreeRenderer.class, "javascript/tree.js");
634             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlTreeRenderer.class, "javascript/cookielib.js");
635         }
636         else
637         {
638             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/tree.js");
639             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/cookielib.js");
640         }
641     }
642 
643     /**
644      * Get the appropriate image src location.  Uses the extension filter mechanism for images if no image
645      * location has been specified.
646      *
647      * @param context The {@link FacesContext}.  NOTE: If <code>null</code> then context path information
648      *    will not be used with extensions filter (assuming no imageLocation specified.)
649      * @param component UIComponent
650      * @param imageName The name of the image file to use.
651      * @return The image src information.
652      */
653     private String getImageSrc(FacesContext context, UIComponent component, String imageName, boolean withContextPath)
654     {
655         String imageLocation = ((HtmlTree)component).getImageLocation();
656         AddResource addResource = AddResourceFactory.getInstance(context);
657         if (imageLocation == null)
658         {
659             return addResource.getResourceUri(context, HtmlTreeRenderer.class,
660                                               "images/" + imageName, withContextPath);
661         }
662         else
663         {
664             return addResource.getResourceUri(context, imageLocation + "/" + imageName, withContextPath);
665         }
666     }
667 
668     /**
669      * Helper method for getting the boolean value of an attribute.  If the attribute is not specified,
670      * then return the default value.
671      *
672      * @param component The component for which the attributes are to be checked.
673      * @param attributeName The name of the boolean attribute.
674      * @param defaultValue The default value of the attribute (to be returned if no value found).
675      * @return boolean
676      */
677     protected boolean getBoolean(UIComponent component, String attributeName, boolean defaultValue)
678     {
679         Boolean booleanAttr = (Boolean)component.getAttributes().get(attributeName);
680 
681         if (booleanAttr == null)
682         {
683             return defaultValue;
684         }
685         else
686         {
687             return booleanAttr.booleanValue();
688         }
689     }
690 
691     private Map getCookieAttr(Cookie cookie)
692     {
693         Map attribMap = new HashMap();
694         try
695         {
696             String cookieValue = URLDecoder.decode(cookie.getValue(),ENCODING);
697             String[] attribArray = cookieValue.split(ATTRIB_DELIM);
698             for (int j = 0; j < attribArray.length; j++)
699             {
700                 int index = attribArray[j].indexOf(ATTRIB_KEYVAL);
701                 String name = attribArray[j].substring(0, index);
702                 String value = attribArray[j].substring(index + 1);
703                 attribMap.put(name, value);
704             }
705         }
706         catch (UnsupportedEncodingException e)
707         {
708             throw new RuntimeException("Error parsing tree cookies", e);
709         }
710         return attribMap;
711     }
712 }