/** * Copyright 2010 Marko Lavikainen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package net.contextfw.web.application.component; import java.util.ArrayList; import java.util.Collection; import java.util.List; import net.contextfw.web.application.WebApplicationException; import net.contextfw.web.application.internal.component.ComponentBuilder; import net.contextfw.web.application.internal.configuration.KeyValue; import net.contextfw.web.application.serialize.AttributeSerializer; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; /** * This class is responsible for actually building the DOM-tree during rendering phase. * * <p> * DOMBuilder can be accessed by using {@link CustomBuild} on component method. * </p> */ public final class DOMBuilder { private final Document document; private final AttributeSerializer<Object> serializer; private final Element root; private final ComponentBuilder componentBuilder; public DOMBuilder(String rootName, AttributeSerializer<Object> serializer, ComponentBuilder componentBuilder, Collection<KeyValue<String, String>> namespaces) { this.serializer = serializer; root = DocumentHelper.createElement(rootName); document = DocumentHelper.createDocument(); document.setRootElement(root); for (KeyValue<String, String> namespace : namespaces) { root.add(DocumentHelper.createNamespace(namespace.getKey(), namespace.getValue())); } this.componentBuilder = componentBuilder; } private DOMBuilder(Document document, Element root, AttributeSerializer<Object> serializer, ComponentBuilder componentBuilder) { this.document = document; this.root = root; this.serializer = serializer; this.componentBuilder = componentBuilder; } // public DOMBuilder child(String elementName, CSimpleElement element) { // descend(elementName).child(element); // return this; // } /** * Adds an attribute to current element * * @param name * Name of the attribute * @param value * Value of the attribute. Is converted to String by using proper {@AttributeSerializer}. * @return * Current DOMBuilder */ public DOMBuilder attr(String name, Object value) { root.addAttribute(name, serializer.serialize(value)); return this; } /** * Adds a child to this DOM-tree. * * <p> * This method can be used to add pre-existing DOM-trees to this DOM-tree. * </p> * * @param element * The element to be added * @return * Current DOMBuilder */ public DOMBuilder child(Node element) { root.add(element); return this; } /** * Adds a child to the DOM-tree * * <p> * This method is used to add Buildable-classes and Components into the DOM-tree. * If object is not buildable, it will be run through <code>AttributeSerializer</code> * and added as text. * </p> * * <h3>Buildins</h3> * * <p> * Buildin is similar concept to mixin.It is possible to add more objects to same DOM-tree. * The attributes and elements from each buildin are added as they were part of the * original object. The wrapping classes are ignored for buildins. * </p> * * * @param object * The Object o be added * @param buildins * The buildins to be added * @return * Current DOMBuilder */ public DOMBuilder child(Object object, Object... buildins) { componentBuilder.build(this, object, buildins); return this; } /** * Gets the root element where this DOMBuilder is at. * @return * The current root element */ public Element getCurrentRoot() { return root; } /** * Finds a path from current DOM-tree and returns a new DOMBuilder for it. * * <p> * This method is useful when DOM-tree has been built into to some point * and needs to be changed. * </p> * * @param xpath * The xpath * @return * new DOMBuilder or <code>null</code> if no path is matched */ public DOMBuilder findByXPath(String xpath) { Element element = (Element) root.selectSingleNode(xpath); if (element != null) { return new DOMBuilder(document, element, serializer, componentBuilder); } else { return null; } } /** * Gets a path from current DOM-tree and returns a new DOMBuilder for it. * * <p> * See documentation from <code>findXPath</code> * </p> * * @param xpath * The xpath * @return * new DOMBuilder or throws exception if no path is matched */ public DOMBuilder getByXPath(String xpath) { DOMBuilder b = findByXPath(xpath); if (b == null) { throw new WebApplicationException("Element for xpath '"+xpath+"' was not found"); } return b; } /** * Lists all paths from current DOM-tree and returns a new DOMBuilder for it. * * <p> * See documentation from <code>findXPath</code> * </p> * * @param xpath * The xpath * @return * List of found DOMBuilders or empty list if nothing is found. */ public List<DOMBuilder> listByXPath(String xpath) { List<DOMBuilder> rv = new ArrayList<DOMBuilder>(); @SuppressWarnings("unchecked") List<Element> elements = (List<Element>) root.selectNodes(xpath); for (Element element : elements) { rv.add(new DOMBuilder(document, element, serializer, componentBuilder)); } return rv; } /** * Returns the entire document of the DOM-tree */ public Document toDocument() { return document; } /** * Adds text element to the dom-tree. * * <p> * The given argument is run through <code>AttributeSerializer</code>. * </p> * @param value * @return */ public DOMBuilder text(Object value) { if (value != null) { root.addText(serializer.serialize(value)); } return this; } /** * Adds a new child element and retuns a new DOMBuilder using the child as a root. * * @param elementName * Element name * @return * New DOMBuilder using the created element as a root. */ public DOMBuilder descend(String elementName) { return new DOMBuilder(document, root.addElement(elementName), serializer, componentBuilder); } }