001// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.dom; 016 017import java.io.PrintWriter; 018import java.util.Map; 019 020import org.apache.tapestry5.internal.util.PrintOutCollector; 021 022/** 023 * A node within the DOM. 024 */ 025public abstract class Node 026{ 027 Element container; 028 029 /** 030 * Next node within containing element. 031 */ 032 Node nextSibling; 033 034 /** 035 * Creates a new node, setting its container to the provided value. Container may also be null, but that is only 036 * used for Document nodes (the topmost node of a DOM). 037 * 038 * @param container element containing this node 039 */ 040 protected Node(Element container) 041 { 042 this.container = container; 043 } 044 045 /** 046 * Returns the containing {@link org.apache.tapestry5.dom.Element} for this node, or null if this node is the root 047 * element of the document. 048 */ 049 public Element getContainer() 050 { 051 return container; 052 } 053 054 public Document getDocument() 055 { 056 return container.getDocument(); 057 } 058 059 060 /** 061 * Invokes {@link #toMarkup(PrintWriter)}, collecting output in a string, which is returned. 062 */ 063 @Override 064 public String toString() 065 { 066 PrintOutCollector collector = new PrintOutCollector(); 067 068 toMarkup(collector.getPrintWriter()); 069 070 return collector.getPrintOut(); 071 } 072 073 074 /** 075 * Writes the markup for this node to the writer. 076 */ 077 public void toMarkup(PrintWriter writer) 078 { 079 toMarkup(getDocument(), writer, getNamespaceURIToPrefix()); 080 } 081 082 protected Map<String, String> getNamespaceURIToPrefix() 083 { 084 // For non-Elements, the container (which should be an Element) will provide the mapping. 085 086 return container.getNamespaceURIToPrefix(); 087 } 088 089 /** 090 * Implemented by each subclass, with the document passed in for efficiency. 091 */ 092 abstract void toMarkup(Document document, PrintWriter writer, Map<String, String> namespaceURIToPrefix); 093 094 /** 095 * Moves this node so that it becomes a sibling of the element, ordered just before the element. 096 * 097 * @param element to move the node before 098 * @return the node for further modification 099 */ 100 public Node moveBefore(Element element) 101 { 102 validateElement(element); 103 104 remove(); 105 106 element.container.insertChildBefore(element, this); 107 108 return this; 109 } 110 111 112 /** 113 * Moves this node so that it becomes a sibling of the element, ordered just after the element. 114 * 115 * @param element to move the node after 116 * @return the node for further modification 117 */ 118 public Node moveAfter(Element element) 119 { 120 validateElement(element); 121 122 remove(); 123 124 element.container.insertChildAfter(element, this); 125 126 return this; 127 } 128 129 /** 130 * Moves this node so that it becomes this first child of the element, shifting existing elements forward. 131 * 132 * @param element to move the node inside 133 * @return the node for further modification 134 */ 135 public Node moveToTop(Element element) 136 { 137 validateElement(element); 138 139 remove(); 140 141 element.insertChildAt(0, this); 142 143 return this; 144 } 145 146 /** 147 * Moves this node so that it the last child of the element. 148 * 149 * @param element to move the node inside 150 * @return the node for further modification 151 */ 152 public Node moveToBottom(Element element) 153 { 154 validateElement(element); 155 156 remove(); 157 158 element.addChild(this); 159 160 return this; 161 } 162 163 private void validateElement(Element element) 164 { 165 assert element != null; 166 167 Node search = element; 168 while (search != null) 169 { 170 if (search.equals(this)) 171 { 172 throw new IllegalArgumentException("Unable to move a node relative to itself."); 173 } 174 175 search = search.getContainer(); 176 } 177 } 178 179 /** 180 * Removes a node from its container, setting its container property to null, and removing it from its container's 181 * list of children. 182 */ 183 public void remove() 184 { 185 container.remove(this); 186 187 container = null; 188 } 189 190 /** 191 * Wraps a node inside a new element. The new element is created before the node, then the node is moved inside the 192 * new element. 193 * 194 * @param elementName name of new element to create 195 * @param namesAndValues to set attributes of new element 196 * @return the created element 197 */ 198 public Element wrap(String elementName, String... namesAndValues) 199 { 200 int index = container.indexOfNode(this); 201 202 // Insert the new element just before this node. 203 Element element = container.elementAt(index, elementName, namesAndValues); 204 205 // Move this node inside the new element. 206 moveToTop(element); 207 208 return element; 209 } 210}