KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > beehive > netui > tags > tree > TreeRenderer


1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Header:$
17  */

18 package org.apache.beehive.netui.tags.tree;
19
20 import org.apache.beehive.netui.util.internal.InternalStringBuilder;
21
22 import org.apache.beehive.netui.core.URLCodec;
23 import org.apache.beehive.netui.pageflow.PageFlowUtils;
24 import org.apache.beehive.netui.pageflow.internal.AdapterManager;
25 import org.apache.beehive.netui.pageflow.internal.InternalUtils;
26 import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
27 import org.apache.beehive.netui.tags.HtmlUtils;
28 import org.apache.beehive.netui.tags.html.HtmlConstants;
29 import org.apache.beehive.netui.tags.internal.PageFlowTagUtils;
30 import org.apache.beehive.netui.tags.javascript.ScriptRequestState;
31 import org.apache.beehive.netui.tags.rendering.*;
32 import org.apache.beehive.netui.util.Bundle;
33
34 import javax.servlet.ServletContext JavaDoc;
35 import javax.servlet.http.HttpServletRequest JavaDoc;
36 import javax.servlet.http.HttpServletResponse JavaDoc;
37 import javax.servlet.jsp.JspException JavaDoc;
38 import java.io.IOException JavaDoc;
39 import java.net.URISyntaxException JavaDoc;
40 import java.util.HashMap JavaDoc;
41
42 abstract public class TreeRenderer implements HtmlConstants
43 {
44     private TagRenderingBase _imageRenderer;
45     private TagRenderingBase _anchorRenderer;
46     private TagRenderingBase _divRenderer;
47     private TagRenderingBase _spanRenderer;
48
49     private TreeRenderState _trs;
50
51     private ImageTag.State _imgState = new ImageTag.State();
52     private AnchorTag.State _anchorState = new AnchorTag.State();
53     private DivTag.State _divState = new DivTag.State();
54     private SpanTag.State _spanState = new SpanTag.State();
55
56     private ServletContext JavaDoc _servletContext;
57     private HttpServletRequest JavaDoc _req;
58     private HttpServletResponse JavaDoc _res;
59
60     TreeRenderer(TreeRenderState trs, HttpServletRequest JavaDoc request,
61                  HttpServletResponse JavaDoc response, ServletContext JavaDoc servletContext)
62     {
63         _trs = trs;
64         _imageRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.IMAGE_TAG, request);
65         _anchorRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.ANCHOR_TAG, request);
66         _divRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.DIV_TAG, request);
67         _spanRenderer = TagRenderingBase.Factory.getRendering(TagRenderingBase.SPAN_TAG, request);
68         _servletContext = servletContext;
69         _req = request;
70         _res = response;
71     }
72
73     abstract protected void registerTagError(String JavaDoc message, Throwable JavaDoc e)
74             throws JspException JavaDoc;
75
76     abstract protected String JavaDoc renderTagId(HttpServletRequest JavaDoc request, String JavaDoc tagId, AbstractHtmlState state);
77
78     protected void renderBeforeNode(AbstractRenderAppender writer, TreeElement node)
79     {
80     }
81
82     protected void renderAfterNode(AbstractRenderAppender writer, TreeElement node)
83     {
84     }
85
86     /**
87      * This is a recursive method which generates the markup for the tree.
88      * @param sb
89      * @param node
90      * @param level
91      * @param attrs
92      * @param state
93      * @throws javax.servlet.jsp.JspException
94      */

95     protected void render(InternalStringBuilder sb, TreeElement node, int level, AttributeRenderer attrs,
96                           InheritableState state)
97             throws JspException JavaDoc
98     {
99         // assert the values...
100
assert(sb != null);
101         assert(node != null);
102
103         String JavaDoc encoding = _res.getCharacterEncoding();
104         String JavaDoc nodeName = node.getName();
105
106         assert(nodeName != null);
107
108         // HACK to take into account special characters like = and &
109
// in the node name, could remove this code if encode URL
110
// and later request.getParameter() could deal with = and &
111
// character in parameter values.
112
String JavaDoc encodedNodeName = null;
113         try {
114             encodedNodeName = URLCodec.encode(nodeName, encoding);
115             assert(encodedNodeName != null);
116         }
117         catch (IOException JavaDoc e) {
118             // report the exception and return.
119
String JavaDoc s = Bundle.getString("Tags_TreeEncodingError", null);
120             registerTagError(s, e);
121             return;
122         }
123
124         // add any attributes to the renderer
125
AttributeRenderer.RemoveInfo removes = attrs.addElement(node);
126
127         // get the renderers for this tree...
128
InternalStringBuilder img = new InternalStringBuilder(32);
129
130
131         // Render the beginning of this node
132
_divState.clear();
133         String JavaDoc tagId = node.getTagId();
134         String JavaDoc script = null;
135         if (tagId != null) {
136             script = renderTagId(_req, tagId, _divState);
137         }
138         attrs.renderDiv(_divState, node);
139         if (_trs.runAtClient) {
140             _divState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_LEVEL, Integer.toString(level));
141         }
142         sb.append(" ");
143         StringBuilderRenderAppender writer = new StringBuilderRenderAppender(sb);
144         renderBeforeNode(writer, node);
145         _divRenderer.doStartTag(writer, _divState);
146         sb.append("\n");
147         if (script != null)
148             sb.append(script);
149
150         // In devMode we will verify the structure of the tree. This will not run in
151
// production mode.
152
ServletContext JavaDoc servletContext = InternalUtils.getServletContext(_req);
153         boolean devMode = !AdapterManager.getServletContainerAdapter(servletContext).isInProductionMode();
154         if (devMode) {
155             boolean error = false;
156             InternalStringBuilder errorText = new InternalStringBuilder(64);
157             if (node.getName() == null) {
158                 errorText.append("name");
159                 error = true;
160             }
161             if (node.getParent() == null) {
162                 if (error)
163                     errorText.append(", ");
164                 errorText.append("parent");
165             }
166
167             if (error)
168                 registerTagError(Bundle.getString("Tags_TreeStructureError", errorText.toString()), null);
169         }
170
171         // check for tree override properties, the second
172
// case here is because the root runs through this an by definitions
173
// has InheritableState == state
174
InheritableState is = node.getInheritableState();
175         if (is != null && is != state) {
176             is.setParent(state);
177             state = is;
178         }
179
180         // Create the appropriate number of indents
181
// These are either the spacer.gif if the parent is the last in the line or the
182
// vertical line gif if the parent is not the last child.
183
_imgState.clear();
184         _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, WIDTH, "16px");
185         _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, BORDER, "0");
186         _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ALT, "", false);
187         for (int i = 0; i < level; i++) {
188             int levels = level - i;
189             TreeElement parent = node;
190             for (int j = 1; j <= levels; j++)
191                 parent = parent.getParent();
192
193             img.setLength(0);
194             img.append(state.getImageRoot());
195             img.append('/');
196             if (parent.isLast()) {
197                 img.append(state.getImageSpacer());
198                 _imgState.style = null;
199             }
200             else {
201                 img.append(state.getVerticalLineImage());
202                 _imgState.style = "vertical-align:bottom;";
203             }
204             sb.append(" ");
205             _imgState.src = img.toString();
206             _imageRenderer.doStartTag(writer, _imgState);
207             _imageRenderer.doEndTag(writer);
208             sb.append("\n");
209         }
210
211         // boolean flag that will indicate if there is an open anchor created
212
boolean closeAnchor = false;
213         if (!_trs.runAtClient)
214             closeAnchor = renderExpansionAnchor(sb, _anchorRenderer, node, nodeName, state);
215         else {
216             // Render client expansion and initialize the tree JavaScript support
217
closeAnchor = renderClientExpansionAnchor(sb, _anchorRenderer, node, encodedNodeName, state);
218         }
219
220         // place the image into the anchor....
221
// The type of the image depends upon the position and the type of the node.
222
String JavaDoc alt = "";
223         img.setLength(0);
224         img.append(state.getImageRoot());
225         img.append('/');
226         if (node.isLeaf()) { // leaf node either last or middle
227
if (node.isLast())
228                 img.append(state.getLastLineJoinImage());
229             else
230                 img.append(state.getLineJoinImage());
231         }
232         else if (node.isExpanded()) { // interior node that is expanded
233
alt = Bundle.getString("Tags_TreeAltTextCollapse", null);
234             String JavaDoc rImg = null;
235             if (node.getParent() == null && node instanceof ITreeRootElement) {
236                 rImg = ((ITreeRootElement) node).getRootNodeExpandedImage();
237                 if (rImg != null) {
238                     img.append(rImg);
239                 }
240             }
241             if (rImg == null) {
242                 if (node.isLast())
243                     img.append(state.getLastNodeExpandedImage());
244                 else
245                     img.append(state.getNodeExpandedImage());
246             }
247         }
248         else { // interior not expanded
249
alt = Bundle.getString("Tags_TreeAltTextExpand", null);
250             String JavaDoc rImg = null;
251             if (node.getParent() == null && node instanceof ITreeRootElement) {
252                 rImg = ((ITreeRootElement) node).getRootNodeCollapsedImage();
253                 if (rImg != null) {
254                     img.append(rImg);
255                 }
256             }
257             if (rImg == null) {
258                 if (node.isLast())
259                     img.append(state.getLastNodeCollapsedImage());
260                 else
261                     img.append(state.getNodeCollapsedImage());
262             }
263         }
264
265         // write out the image which occurs next to the icon
266
if (!closeAnchor)
267             sb.append(" ");
268
269         _imgState.clear();
270         _imgState.src = img.toString();
271         _imgState.style = "vertical-align:bottom;";
272         _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, BORDER, "0");
273         _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ALT, alt, false);
274
275         _imageRenderer.doStartTag(writer, _imgState);
276         _imageRenderer.doEndTag(writer);
277
278         // close the anchor if one was openned...
279
if (closeAnchor)
280             _anchorRenderer.doEndTag(writer);
281         sb.append("\n");
282
283         // Calculate the selection link for this node, if the node is disabled, we can skip
284
// this because a disabled node may not be selected.
285
String JavaDoc selectionLink = null;
286         if (!node.isDisabled()) {
287
288             // The action on the node overrides all. Otherwise, check to see if there
289
// is either an href or clientAction. If neither is set, then we inherit the
290
// action defined on the trees inheritable state.
291
String JavaDoc action = node.getAction();
292             if (action == null) {
293                 selectionLink = node.getHref();
294                 if (selectionLink == null && node.getClientAction() != null)
295                     selectionLink = "";
296                 if (selectionLink == null)
297                     action = state.getSelectionAction();
298             }
299
300             // create the selection link
301
if (action != null && selectionLink == null) {
302
303                 HashMap JavaDoc params = null;
304                 boolean remove = false;
305                 params = node.getParams();
306                 if (params == null) {
307                     params = new HashMap JavaDoc();
308                     remove = true;
309                 }
310                 params.put(TreeElement.SELECTED_NODE, nodeName);
311                 if (_trs.tagId != null) {
312                     params.put(TreeElement.TREE_ID, _trs.tagId);
313                 }
314
315                 // Add the jpf ScopeID param if necessary.
316
String JavaDoc scope = node.getScope();
317                 if (scope != null) {
318                     params.put(ScopedServletUtils.SCOPE_ID_PARAM, scope);
319                 }
320
321                 String JavaDoc uri = null;
322                 try {
323                     boolean xml = TagRenderingBase.Factory.isXHTML(_req);
324                     uri = PageFlowUtils.getRewrittenActionURI(_servletContext, _req, _res, action, params, null, xml);
325                 }
326                 catch (URISyntaxException JavaDoc e) {
327                     // report the error...
328
String JavaDoc s = Bundle.getString("Tags_Tree_Node_URLException",
329                             new Object JavaDoc[]{action, e.getMessage()});
330                     registerTagError(s, e);
331                 }
332
333                 if (remove) {
334                     params.remove(TreeElement.SELECTED_NODE);
335                     if (_trs.tagId != null) {
336                         params.remove(TreeElement.TREE_ID);
337                     }
338
339                     if (scope != null) {
340                         params.remove(ScopedServletUtils.SCOPE_ID_PARAM);
341                     }
342                 }
343
344                 if (uri != null) {
345                     selectionLink = _res.encodeURL(uri);
346                 }
347             }
348         }
349
350         TagRenderingBase endRender = null;
351         // if there is a selection link we need to put an anchor out.
352
if (selectionLink != null) {
353             _anchorState.clear();
354             _anchorState.href = selectionLink;
355             String JavaDoc target = node.getTarget();
356             if (target == null) {
357                 target = state.getSelectionTarget();
358             }
359             _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TARGET, target);
360             String JavaDoc title = node.getTitle();
361             _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TITLE, title);
362
363             // set the selection styles
364
if (node.isSelected()) {
365                 _anchorState.style = _trs.selectedStyle;
366                 _anchorState.styleClass = _trs.selectedStyleClass;
367             }
368             else {
369                 _anchorState.style = _trs.unselectedStyle;
370                 _anchorState.styleClass = _trs.unselectedStyleClass;
371             }
372             if (_anchorState.style == null && _anchorState.styleClass == null) {
373                 _anchorState.style = "text-decoration: none";
374             }
375
376             // render any attributes applied to the HTML
377
attrs.renderSelectionLink(_anchorState, node);
378
379             // render the runAtClient attributes
380
if (_trs.runAtClient) {
381                 String JavaDoc action = node.getClientAction();
382                 if (action != null) {
383                     action = HtmlUtils.escapeEscapes(action);
384                     action = ScriptRequestState.getString("netuiAction", new Object JavaDoc[]{action});
385                 }
386                 _anchorState.registerAttribute(AbstractHtmlState.ATTR_JAVASCRIPT, ONCLICK, action);
387                 // Jira 299
388
//_anchorState.onClick = action;
389
}
390
391             // actually render the anchor.
392
sb.append(" ");
393             _anchorRenderer.doStartTag(writer, _anchorState);
394             endRender = _anchorRenderer;
395         }
396         else {
397             // This node doesn's support selection. This means we consider it disabled. We will
398
// put a span around it and set the style/class to indicate that it is disabled.
399
_spanState.clear();
400             _spanState.styleClass = _trs.disabledStyleClass;
401             _spanState.style = _trs.disabledStyle;
402             sb.append(" ");
403             _spanRenderer.doStartTag(writer, _spanState);
404             endRender = _spanRenderer;
405         }
406         sb.append("&nbsp;");
407
408         // Render the icon for this node, there will always unless the tree turns off default
409
// icons by setting the useDefaultIcons attribute to false.
410
String JavaDoc icon = node.getIcon();
411         if (icon == null) {
412             icon = state.getIconRoot() + "/" + state.getItemIcon();
413         }
414         else {
415             icon = state.getIconRoot() + "/" + icon;
416         }
417
418         // write out the icon
419
String JavaDoc label = node.getLabel();
420         if (icon != null) {
421             _imgState.clear();
422             _imgState.src = icon;
423             _imgState.style = "vertical-align:text-top";
424             alt = null;
425             if (label != null && node.isLabelLegalAsAlt())
426                 alt = label;
427             else
428                 alt = node.getTitle();
429             if (alt == null)
430                 alt = Bundle.getString("Tags_TreeAltText", null);
431             _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, ALT, alt, false);
432             _imgState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, BORDER, "0");
433
434             // set the inheritted attributes
435
attrs.renderIconImage(_imgState, node);
436             _imageRenderer.doStartTag(writer, _imgState);
437             _imageRenderer.doEndTag(writer);
438             sb.append("&nbsp;");
439         }
440
441         // Render the label for this node (if any)
442
if (label != null) {
443             if (_trs.escapeContent) {
444                 InternalStringBuilder s = new InternalStringBuilder(label.length() + 16);
445                 StringBuilderRenderAppender sbAppend = new StringBuilderRenderAppender(sb);
446                 HtmlUtils.filter(label, sbAppend);
447                 label = s.toString();
448             }
449             sb.append(label);
450             sb.append("&nbsp;");
451         }
452         endRender.doEndTag(writer);
453
454         // if there is content then we should render that here...
455
String JavaDoc ctnt = node.getContent();
456         if (ctnt != null) {
457             if (_trs.escapeContent) {
458                 InternalStringBuilder s = new InternalStringBuilder(ctnt.length() + 16);
459                 StringBuilderRenderAppender sbAppend = new StringBuilderRenderAppender(sb);
460                 HtmlUtils.filter(ctnt, sbAppend);
461                 ctnt = s.toString();
462             }
463             sb.append("\n ");
464             sb.append(ctnt);
465         }
466
467         // Render the end of this node
468
sb.append("\n ");
469         _divRenderer.doEndTag(writer);
470         sb.append("\n");
471         renderAfterNode(writer, node);
472
473         // now remove all of the attributes scoped with this...
474

475         attrs.removeElementScoped(node, removes);
476         // Render the children of this node
477
// If the node is expanded we render it
478
// If we are runAtClient and the node is Not expandOnServer then render it
479
if (node.isExpanded() || (_trs.runAtClient && !node.isExpandOnServer())) {
480             TreeElement children[] = node.getChildren();
481             int newLevel = level + 1;
482             for (int i = 0; i < children.length; i++) {
483                 render(sb, children[i], newLevel, attrs, state);
484             }
485         }
486         attrs.removeElement(node, removes);
487     }
488
489     private boolean renderExpansionAnchor(InternalStringBuilder sb, TagRenderingBase anchorRenderer,
490                                           TreeElement node, String JavaDoc nodeName, InheritableState state)
491             throws JspException JavaDoc
492     {
493         // Render the tree state image for this node
494
String JavaDoc action = state.getExpansionAction();
495         if (action == null) {
496             action = state.getSelectionAction();
497         }
498         boolean isAction = PageFlowTagUtils.isAction(_req, action);
499         if (!isAction) {
500             registerTagError(Bundle.getString("Tags_BadAction", action), null);
501             return false;
502         }
503
504         // encode the tree parameters into the action.
505
HashMap JavaDoc params = new HashMap JavaDoc();
506         params.put(TreeElement.EXPAND_NODE, nodeName);
507         assert (_trs.tagId != null);
508         params.put(TreeElement.TREE_ID, _trs.tagId);
509         String JavaDoc uri = null;
510         try {
511             boolean xml = TagRenderingBase.Factory.isXHTML(_req);
512             uri = PageFlowUtils.getRewrittenActionURI(_servletContext, _req, _res, action, params, null, xml);
513         }
514         catch (URISyntaxException JavaDoc e) {
515             // report the error...
516
String JavaDoc s = Bundle.getString("Tags_Tree_Node_URLException",
517                     new Object JavaDoc[]{action, e.getMessage()});
518             registerTagError(s, e);
519         }
520
521         boolean ret = false;
522         if ((uri != null) && !node.isLeaf()) {
523             _anchorState.clear();
524             _anchorState.href = _res.encodeURL(uri);
525             sb.append(" ");
526             StringBuilderRenderAppender writer = new StringBuilderRenderAppender(sb);
527             anchorRenderer.doStartTag(writer, _anchorState);
528             ret = true;
529         }
530         return ret;
531     }
532
533     /**
534      * @param sb
535      * @param node
536      * @param encodedNodeName
537      * @return
538      */

539     private boolean renderClientExpansionAnchor(InternalStringBuilder sb, TagRenderingBase anchorRenderer,
540                                                 TreeElement node, String JavaDoc encodedNodeName,
541                                                 InheritableState state)
542     {
543         boolean imgOverride = (state != state.getParent() && state.getParent() != null) ||
544                 (node.getParent() == null);
545
546         if (!node.isLeaf()) {
547             boolean expanded = node.isExpanded();
548             _anchorState.clear();
549             _anchorState.href = "";
550             _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_ANCHOR,
551                     (expanded ? TreeElement.TREE_EXPAND_STATE : TreeElement.TREE_COLLAPSE_STATE));
552             _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_ANCHOR_INIT, "true");
553             _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_ANCHOR_ID, encodedNodeName);
554             if (node.isLast()) {
555                 _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_NODE_LAST, "true");
556             }
557
558             // Does this node have it's images being overridden?
559
if (imgOverride) {
560                 if (node.getParent() == null) {
561                     String JavaDoc rootImg = ((ITreeRootElement) node).getRootNodeCollapsedImage();
562                     if (rootImg != null)
563                         _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_COLLAPSE_IMAGE,
564                                 state.getImageRoot() + "/" + rootImg);
565                     else
566                         _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_COLLAPSE_IMAGE,
567                                 state.getImageRoot() + "/" + state.getLastNodeCollapsedImage());
568                     rootImg = ((ITreeRootElement) node).getRootNodeExpandedImage();
569                     if (rootImg != null)
570                         _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_EXPAND_IMAGE,
571                                 state.getImageRoot() + "/" + rootImg);
572                     else
573                         _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_EXPAND_IMAGE,
574                                 state.getImageRoot() + "/" + state.getLastNodeExpandedImage());
575                 }
576                 else if (node.isLast()) {
577                     _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_COLLAPSE_IMAGE,
578                             state.getImageRoot() + "/" + state.getLastNodeCollapsedImage());
579                     _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_EXPAND_IMAGE,
580                             state.getImageRoot() + "/" + state.getLastNodeExpandedImage());
581                 }
582                 else {
583                     _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_COLLAPSE_IMAGE,
584                             state.getImageRoot() + "/" + state.getNodeCollapsedImage());
585                     _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_EXPAND_IMAGE,
586                             state.getImageRoot() + "/" + state.getNodeExpandedImage());
587                 }
588             }
589
590             if (node.isExpandOnServer() && !node.isExpanded()) {
591                 String JavaDoc path = _req.getServletPath();
592                 int idx = path.lastIndexOf('/');
593                 if (idx != -1) {
594                     path = path.substring(1, idx);
595                 }
596                 _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_EXPAND, "true");
597                 _anchorState.registerAttribute(AbstractHtmlState.ATTR_GENERAL, TreeElement.TREE_EXPAND_PATH, path);
598             }
599
600             sb.append(" ");
601             StringBuilderRenderAppender writer = new StringBuilderRenderAppender(sb);
602             anchorRenderer.doStartTag(writer, _anchorState);
603             return true;
604         }
605         return false;
606     }
607 }
608
Popular Tags