2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.shale.clay.parser.builder;
22
23 import java.util.Iterator;
24 import java.util.Map;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.shale.clay.config.beans.AttributeBean;
29 import org.apache.shale.clay.config.beans.ComponentBean;
30 import org.apache.shale.clay.config.beans.ConfigBean;
31 import org.apache.shale.clay.config.beans.ConfigBeanFactory;
32 import org.apache.shale.clay.config.beans.ElementBean;
33 import org.apache.shale.clay.config.beans.SymbolBean;
34 import org.apache.shale.clay.parser.Node;
35 import org.apache.shale.clay.parser.Token;
36 import org.apache.shale.util.Messages;
37
38 /***
39 * <p>
40 * The abstract document node converter handles building meta components,
41 * {@link org.apache.shale.clay.config.beans.ComponentBean}, for a parsed html
42 * document fragment. The {@link org.apache.shale.clay.parser.Parser} loads the
43 * HTML document into a tree structure of
44 * {@link org.apache.shale.clay.parser.Node}. Each node in the parsed document
45 * tree is mapped to a <code>Builder</code> by a a subclass of
46 * {@link org.apache.shale.clay.parser.builder.chain.BuilderRuleContext}.
47 * </p>
48 */
49 public abstract class Builder {
50
51 /***
52 * <p>
53 * Message resources for this class.
54 * </p>
55 */
56 protected static Messages messages = new Messages(
57 "org.apache.shale.clay.Bundle", Builder.class
58 .getClassLoader());
59
60 /***
61 * <p>
62 * Common Logger utility class.
63 * </p>
64 */
65 private static Log log;
66 static {
67 log = LogFactory.getLog(Builder.class);
68 }
69
70 /***
71 * <p>
72 * This method returns <code>true</code> if the faces component that it
73 * builds allows children but the default is <code>false</code>.
74 * </p>
75 *
76 * @return <code>false</code>
77 */
78 public boolean isChildrenAllowed() {
79 return false;
80 }
81
82 /***
83 * <p>
84 * Returns a <code>jsfid</code> for the component.
85 * </p>
86 *
87 * @param node markup
88 * @return jsfid
89 */
90 protected abstract String getJsfid(Node node);
91
92 /***
93 * <p>
94 * Returns the faces component type registered in the faces configuration or the
95 * fully qualified class name for <code>ActionListeners</code> and
96 * <code>ValueChangeListeners</code>.
97 * </p>
98 *
99 * @param node markup
100 * @return component type
101 */
102 protected abstract String getComponentType(Node node);
103
104 /***
105 * <p>
106 * An integer value used to order {@link ElementBean}'s in the
107 * <code>children</code> collection of a {@link ComponentBean}. When read
108 * for the configuration files by the
109 * {@link org.apache.shale.clay.config.ClayConfigureListener}, the
110 * <code>renderId</code> is the <strong>"signature"</strong> when
111 * resolving inheritance and overrides. Used here, the component structure
112 * must take on the form of the HTML document.
113 * </p>
114 */
115 private static int renderId = 0;
116
117 /***
118 * <p>
119 * Returns the next generated <code>renderId</code>.
120 * <p>
121 *
122 * @return unique id
123 */
124 protected synchronized int getRenderId() {
125 return renderId++;
126 }
127
128 /***
129 * <p>
130 * Returns <code>true</code> if the builder handles converting the node's
131 * children or <code>false</code> if it's handled by the parent. The
132 * <code>isBodyAllowed</code> attribute can also
133 * be used to override the default option. This flag allows the HTML to be
134 * mocked up but the body ignored when converted into a
135 * {@link ComponentBean} graph.
136 * <p>
137 *
138 * @param node markup
139 * @param target child config bean
140 * @return <code>false</code> if the node's children should be ignored
141 */
142 protected boolean getBuildNodeBody(Node node, ElementBean target) {
143 return (target != null && !target.getIsBodyAllowed());
144 }
145
146 /***
147 * <p>
148 * Called to being building a {@link ElementBean} from a {@link Node}. This
149 * follows the JSF pattern used by the Components and Renders.
150 * </p>
151 *
152 * @param node markup
153 * @param target child config bean
154 * @param root parent config bean
155 */
156 protected void encodeBegin(Node node, ElementBean target,
157 ComponentBean root) {
158
159 assignNode(node, target);
160 }
161
162 /***
163 * <p>
164 * Factory method that creates a {@link AttributeBean} from the <code>original</code>
165 * replacing the <code>value</code>.
166 * </p>
167 *
168 * @param original attribute being cloned
169 * @param value attribute property value override
170 * @param target owner of the attribute
171 * @return cloned original with the value overridden
172 */
173 protected AttributeBean createAttribute(AttributeBean original, String value, ComponentBean target) {
174 AttributeBean attr = new AttributeBean();
175 attr.setName(original.getName());
176 attr.setValue(value);
177 attr.setBindingType(original.getBindingType());
178 target.addAttribute(attr);
179 return attr;
180 }
181
182
183
184
185 /***
186 * <p>
187 * Recursively builds the clay meta component data from the parse
188 * document of {@link Node}'s. A similar design pattern found in JSF.
189 * </p>
190 *
191 * @param node markup
192 * @param target child config bean
193 * @param root parent config bean
194 */
195 protected void encodeChildren(Node node, ElementBean target,
196 ComponentBean root) {
197
198 if (!getBuildNodeBody(node, target)) {
199 Iterator ci = node.getChildren().iterator();
200 while (ci.hasNext()) {
201 Node child = (Node) ci.next();
202 Builder childRenderer = getBuilder(child);
203
204 ElementBean targetChild = childRenderer.createElement(child);
205 root.addChild(targetChild);
206
207
208 if (childRenderer.isChildrenAllowed()) {
209 childRenderer.encode(child, targetChild, targetChild);
210 } else {
211 childRenderer.encode(child, targetChild, root);
212 }
213 }
214 }
215
216 }
217
218 /***
219 * <p>
220 * This call is invoked for any final processing.
221 * </p>
222 *
223 * @param node markup
224 * @param target child config bean
225 * @param root parent config bean
226 */
227 protected void encodeEnd(Node node, ElementBean target,
228 ComponentBean root) {
229
230 }
231
232 /***
233 * <p>
234 * Factory method that creates a {@link ElementBean} from a {@link Node}.
235 * </p>
236 *
237 * @param node markup
238 * @return target config bean from the markup node
239 */
240 public ElementBean createElement(Node node) {
241
242 ElementBean target = new ElementBean();
243 target.setJsfid(getJsfid(node));
244 if (!node.isComment() && node.isStart()) {
245 String jsfid = (String) node.getAttributes().get("jsfid");
246 if (jsfid != null) {
247 target.setJsfid(jsfid);
248 }
249 }
250 target.setComponentType(getComponentType(node));
251 target.setRenderId(getRenderId());
252
253 return target;
254 }
255
256 /***
257 * <p>
258 * The call that begins the conversion of a {@link Node} to a
259 * {@link ComponentBean}. Each element in the html document will be
260 * converted into a Clay meta component.
261 * </p>
262 *
263 * @param node markup
264 * @param target child config bean
265 * @param root parent config bean
266 */
267 public void encode(Node node, ElementBean target, ComponentBean root) {
268
269 if (log.isDebugEnabled()) {
270 log.debug(messages.getMessage("encode.begin", new Object[] {node}));
271 }
272
273 encodeBegin(node, target, root);
274 encodeChildren(node, target, root);
275 encodeEnd(node, target, root);
276
277 if (log.isDebugEnabled()) {
278 log.debug(messages.getMessage("encode.end", new Object[] {node}));
279 }
280
281 }
282
283
284 /***
285 * <p>
286 * This method resolves the <code>jsfid</code> attribute for an HTML
287 * element to a component definition in the XML configuration files.
288 * </p>
289 *
290 * @param node markup
291 * @param target child config bean
292 */
293 protected void assignNode(Node node, ElementBean target) {
294 String id = (String) node.getAttributes().get("id");
295 if (id == null) {
296 id = (String) node.getAttributes().get("name");
297 }
298
299 target.setId(id);
300
301
302 if (target.getJsfid() != null) {
303
304 ConfigBean config = ConfigBeanFactory.findConfig(target.getJsfid());
305
306 target.setComponentType(null);
307
308
309 try {
310
311 config.assignParent(target);
312
313 config.realizingInheritance(target);
314 } catch (RuntimeException e) {
315 log.error(e);
316 throw new RuntimeException(
317 messages.getMessage("parser.unresolved",
318 new Object[] {node.getToken(), node.getToken().getRawText()}));
319 }
320
321
322 if (target.getComponentType() == null) {
323 target.setComponentType(this.getComponentType(node));
324 }
325
326 }
327
328
329 assignAttributes(node, target);
330
331 }
332
333 /***
334 * <p>
335 * This method applies the HTML attribute overrides to the declarative
336 * component, an object representation of the XML configuration.
337 * </p>
338 *
339 * @param node markup
340 * @param target child config bean
341 */
342 protected void assignAttributes(Node node, ComponentBean target) {
343
344
345 Iterator ai = node.getAttributes().entrySet().iterator();
346 next: while (ai.hasNext()) {
347 Map.Entry e = (Map.Entry) ai.next();
348
349 if (e.getKey().equals("id")
350 || e.getKey().equals("jsfid")
351 || e.getKey().equals("allowbody")
352 || e.getKey().equals("facetname")) {
353
354 continue next;
355 }
356
357 AttributeBean original = null;
358 Token valueToken = (Token) e.getValue();
359 if (valueToken != null) {
360 original = target.getAttribute((String) e.getKey());
361 if (original != null) {
362 createAttribute(original, valueToken.getRawText(), target);
363 } else {
364
365 StringBuffer identifier = new StringBuffer((String) e.getKey());
366 identifier.insert(0, '@');
367 SymbolBean symbol = new SymbolBean();
368 symbol.setName(identifier.toString());
369 symbol.setValue(valueToken.getRawText());
370 target.addSymbol(symbol);
371 }
372 }
373 }
374
375 if (node.getAttributes().containsKey("allowbody")) {
376 target.setAllowBody((String) node.getAttributes().get("allowbody"));
377 }
378
379 if (node.getAttributes().containsKey("facetname")) {
380 target.setFacetName((String) node.getAttributes().get("facetname"));
381 }
382
383
384 }
385
386 /***
387 * <p>Returns the {@link org.apache.shale.clay.parser.builder.Builder} that
388 * is assigned the task of converting the html node to a corresponding component
389 * metadata used to construct a JSF resource.</p>
390 *
391 * @param node markup node
392 * @return builder that maps markup to config beans
393 */
394 public Builder getBuilder(Node node) {
395 return BuilderFactory.getRenderer(node);
396 }
397
398 }