1 /*
2  * Copyright (C) 2007 The Android Open Source Project
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 
17 package org.apache.harmony.xml.dom;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Objects;
22 import org.w3c.dom.DOMException;
23 import org.w3c.dom.DocumentFragment;
24 import org.w3c.dom.Node;
25 import org.w3c.dom.NodeList;
26 
27 /**
28  * Provides a straightforward implementation of the corresponding W3C DOM
29  * interface. The class is used internally only, thus only notable members that
30  * are not in the original interface are documented (the W3C docs are quite
31  * extensive).
32  *
33  * <p>Some of the fields may have package visibility, so other classes belonging
34  * to the DOM implementation can easily access them while maintaining the DOM
35  * tree structure.
36  *
37  * <p>This class represents a Node that has a parent Node as well as
38  * (potentially) a number of children.
39  *
40  * <p>Some code was adapted from Apache Xerces.
41  */
42 public abstract class InnerNodeImpl extends LeafNodeImpl {
43 
44     // Maintained by LeafNodeImpl and ElementImpl.
45     List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>();
46 
InnerNodeImpl(DocumentImpl document)47     protected InnerNodeImpl(DocumentImpl document) {
48         super(document);
49     }
50 
appendChild(Node newChild)51     public Node appendChild(Node newChild) throws DOMException {
52         return insertChildAt(newChild, children.size());
53     }
54 
getChildNodes()55     public NodeList getChildNodes() {
56         NodeListImpl list = new NodeListImpl();
57 
58         for (NodeImpl node : children) {
59             list.add(node);
60         }
61 
62         return list;
63     }
64 
getFirstChild()65     public Node getFirstChild() {
66         return (!children.isEmpty() ? children.get(0) : null);
67     }
68 
getLastChild()69     public Node getLastChild() {
70         return (!children.isEmpty() ? children.get(children.size() - 1) : null);
71     }
72 
getNextSibling()73     public Node getNextSibling() {
74         if (parent == null || index + 1 >= parent.children.size()) {
75             return null;
76         }
77 
78         return parent.children.get(index + 1);
79     }
80 
hasChildNodes()81     public boolean hasChildNodes() {
82         return children.size() != 0;
83     }
84 
insertBefore(Node newChild, Node refChild)85     public Node insertBefore(Node newChild, Node refChild) throws DOMException {
86         LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild;
87 
88         if (refChildImpl == null) {
89             return appendChild(newChild);
90         }
91 
92         if (refChildImpl.document != document) {
93             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
94         }
95 
96         if (refChildImpl.parent != this) {
97             throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
98         }
99 
100         return insertChildAt(newChild, refChildImpl.index);
101     }
102 
103     /**
104      * Inserts {@code newChild} at {@code index}. If it is already child of
105      * another node, it is removed from there.
106      */
insertChildAt(Node newChild, int index)107     Node insertChildAt(Node newChild, int index) throws DOMException {
108         if (newChild instanceof DocumentFragment) {
109             NodeList toAdd = newChild.getChildNodes();
110             for (int i = 0; i < toAdd.getLength(); i++) {
111                 insertChildAt(toAdd.item(i), index + i);
112             }
113             return newChild;
114         }
115 
116         LeafNodeImpl toInsert = (LeafNodeImpl) newChild;
117         if (toInsert.document != null && document != null && toInsert.document != document) {
118             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
119         }
120         if (toInsert.isParentOf(this)) {
121             throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
122         }
123 
124         if (toInsert.parent != null) {
125             int oldIndex = toInsert.index;
126             toInsert.parent.children.remove(oldIndex);
127             toInsert.parent.refreshIndices(oldIndex);
128         }
129 
130         children.add(index, toInsert);
131         toInsert.parent = this;
132         refreshIndices(index);
133 
134         return newChild;
135     }
136 
isParentOf(Node node)137     public boolean isParentOf(Node node) {
138         LeafNodeImpl nodeImpl = (LeafNodeImpl) node;
139 
140         while (nodeImpl != null) {
141             if (nodeImpl == this) {
142                 return true;
143             }
144 
145             nodeImpl = nodeImpl.parent;
146         }
147 
148         return false;
149     }
150 
151     /**
152      * Normalize the text nodes within this subtree. Although named similarly,
153      * this method is unrelated to Document.normalize.
154      */
155     @Override
normalize()156     public final void normalize() {
157         Node next;
158         for (Node node = getFirstChild(); node != null; node = next) {
159             next = node.getNextSibling();
160             node.normalize();
161 
162             if (node.getNodeType() == Node.TEXT_NODE) {
163                 ((TextImpl) node).minimize();
164             }
165         }
166     }
167 
refreshIndices(int fromIndex)168     private void refreshIndices(int fromIndex) {
169         for (int i = fromIndex; i < children.size(); i++) {
170             children.get(i).index = i;
171         }
172     }
173 
removeChild(Node oldChild)174     public Node removeChild(Node oldChild) throws DOMException {
175         LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild;
176 
177         if (oldChildImpl.document != document) {
178             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
179         }
180         if (oldChildImpl.parent != this) {
181             throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
182         }
183 
184         int index = oldChildImpl.index;
185         children.remove(index);
186         oldChildImpl.parent = null;
187         refreshIndices(index);
188 
189         return oldChild;
190     }
191 
192     /**
193      * Removes {@code oldChild} and adds {@code newChild} in its place. This
194      * is not atomic.
195      */
replaceChild(Node newChild, Node oldChild)196     public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
197         int index = ((LeafNodeImpl) oldChild).index;
198         removeChild(oldChild);
199         insertChildAt(newChild, index);
200         return oldChild;
201     }
202 
getTextContent()203     public String getTextContent() throws DOMException {
204         Node child = getFirstChild();
205         if (child == null) {
206             return "";
207         }
208 
209         Node next = child.getNextSibling();
210         if (next == null) {
211             return hasTextContent(child) ? child.getTextContent() : "";
212         }
213 
214         StringBuilder buf = new StringBuilder();
215         getTextContent(buf);
216         return buf.toString();
217     }
218 
getTextContent(StringBuilder buf)219     void getTextContent(StringBuilder buf) throws DOMException {
220         Node child = getFirstChild();
221         while (child != null) {
222             if (hasTextContent(child)) {
223                 ((NodeImpl) child).getTextContent(buf);
224             }
225             child = child.getNextSibling();
226         }
227     }
228 
hasTextContent(Node child)229     final boolean hasTextContent(Node child) {
230         // TODO: skip text nodes with ignorable whitespace?
231         return child.getNodeType() != Node.COMMENT_NODE
232                 && child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
233     }
234 
getElementsByTagName(NodeListImpl out, String name)235     void getElementsByTagName(NodeListImpl out, String name) {
236         for (NodeImpl node : children) {
237             if (node.getNodeType() == Node.ELEMENT_NODE) {
238                 ElementImpl element = (ElementImpl) node;
239                 if (matchesNameOrWildcard(name, element.getNodeName())) {
240                     out.add(element);
241                 }
242                 element.getElementsByTagName(out, name);
243             }
244         }
245     }
246 
getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName)247     void getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName) {
248         for (NodeImpl node : children) {
249             if (node.getNodeType() == Node.ELEMENT_NODE) {
250                 ElementImpl element = (ElementImpl) node;
251                 if (matchesNameOrWildcard(namespaceURI, element.getNamespaceURI())
252                         && matchesNameOrWildcard(localName, element.getLocalName())) {
253                     out.add(element);
254                 }
255                 element.getElementsByTagNameNS(out, namespaceURI, localName);
256             }
257         }
258     }
259 
260     /**
261      * Returns true if {@code pattern} equals either "*" or {@code s}. Pattern
262      * may be {@code null}.
263      */
matchesNameOrWildcard(String pattern, String s)264     private static boolean matchesNameOrWildcard(String pattern, String s) {
265         return "*".equals(pattern) || Objects.equals(pattern, s);
266     }
267 }
268