InnerNodeImpl.java revision 09c4640423dbe85c606c5b46312cd5c0e5c94eeb
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
17package org.apache.harmony.xml.dom;
18
19import java.util.ArrayList;
20import java.util.List;
21import libcore.base.Objects;
22import org.w3c.dom.DOMException;
23import org.w3c.dom.DocumentFragment;
24import org.w3c.dom.Node;
25import 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 */
42public abstract class InnerNodeImpl extends LeafNodeImpl {
43
44    // Maintained by LeafNodeImpl and ElementImpl.
45    List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>();
46
47    protected InnerNodeImpl(DocumentImpl document) {
48        super(document);
49    }
50
51    public Node appendChild(Node newChild) throws DOMException {
52        return insertChildAt(newChild, children.size());
53    }
54
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
65    public Node getFirstChild() {
66        return (!children.isEmpty() ? children.get(0) : null);
67    }
68
69    public Node getLastChild() {
70        return (!children.isEmpty() ? children.get(children.size() - 1) : null);
71    }
72
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
81    public boolean hasChildNodes() {
82        return children.size() != 0;
83    }
84
85    public Node insertBefore(Node newChild, Node refChild) throws DOMException {
86        LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild;
87
88        if (refChildImpl.document != document) {
89            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
90        }
91
92        if (refChildImpl.parent != this) {
93            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
94        }
95
96        return insertChildAt(newChild, refChildImpl.index);
97    }
98
99    /**
100     * Inserts {@code newChild} at {@code index}. If it is already child of
101     * another node, it is removed from there.
102     */
103    Node insertChildAt(Node newChild, int index) throws DOMException {
104        if (newChild instanceof DocumentFragment) {
105            NodeList toAdd = newChild.getChildNodes();
106            for (int i = 0; i < toAdd.getLength(); i++) {
107                insertChildAt(toAdd.item(i), index + i);
108            }
109            return newChild;
110        }
111
112        LeafNodeImpl toInsert = (LeafNodeImpl) newChild;
113        if (toInsert.document != null && document != null && toInsert.document != document) {
114            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
115        }
116        if (toInsert.isParentOf(this)) {
117            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
118        }
119
120        if (toInsert.parent != null) {
121            int oldIndex = toInsert.index;
122            toInsert.parent.children.remove(oldIndex);
123            toInsert.parent.refreshIndices(oldIndex);
124        }
125
126        children.add(index, toInsert);
127        toInsert.parent = this;
128        refreshIndices(index);
129
130        return newChild;
131    }
132
133    public boolean isParentOf(Node node) {
134        LeafNodeImpl nodeImpl = (LeafNodeImpl) node;
135
136        while (nodeImpl != null) {
137            if (nodeImpl == this) {
138                return true;
139            }
140
141            nodeImpl = nodeImpl.parent;
142        }
143
144        return false;
145    }
146
147    /**
148     * Normalize the text nodes within this subtree. Although named similarly,
149     * this method is unrelated to Document.normalize.
150     */
151    @Override
152    public final void normalize() {
153        Node next;
154        for (Node node = getFirstChild(); node != null; node = next) {
155            next = node.getNextSibling();
156            node.normalize();
157
158            if (node.getNodeType() == Node.TEXT_NODE) {
159                ((TextImpl) node).minimize();
160            }
161        }
162    }
163
164    private void refreshIndices(int fromIndex) {
165        for (int i = fromIndex; i < children.size(); i++) {
166            children.get(i).index = i;
167        }
168    }
169
170    public Node removeChild(Node oldChild) throws DOMException {
171        LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild;
172
173        if (oldChildImpl.document != document) {
174            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
175        }
176        if (oldChildImpl.parent != this) {
177            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
178        }
179
180        int index = oldChildImpl.index;
181        children.remove(index);
182        oldChildImpl.parent = null;
183        refreshIndices(index);
184
185        return oldChild;
186    }
187
188    /**
189     * Removes {@code oldChild} and adds {@code newChild} in its place. This
190     * is not atomic.
191     */
192    public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
193        int index = ((LeafNodeImpl) oldChild).index;
194        removeChild(oldChild);
195        insertChildAt(newChild, index);
196        return oldChild;
197    }
198
199    public String getTextContent() throws DOMException {
200        Node child = getFirstChild();
201        if (child == null) {
202            return "";
203        }
204
205        Node next = child.getNextSibling();
206        if (next == null) {
207            return hasTextContent(child) ? child.getTextContent() : "";
208        }
209
210        StringBuilder buf = new StringBuilder();
211        getTextContent(buf);
212        return buf.toString();
213    }
214
215    void getTextContent(StringBuilder buf) throws DOMException {
216        Node child = getFirstChild();
217        while (child != null) {
218            if (hasTextContent(child)) {
219                ((NodeImpl) child).getTextContent(buf);
220            }
221            child = child.getNextSibling();
222        }
223    }
224
225    final boolean hasTextContent(Node child) {
226        // TODO: skip text nodes with ignorable whitespace?
227        return child.getNodeType() != Node.COMMENT_NODE
228                && child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
229    }
230
231    void getElementsByTagName(NodeListImpl out, String name) {
232        for (NodeImpl node : children) {
233            if (node.getNodeType() == Node.ELEMENT_NODE) {
234                ElementImpl element = (ElementImpl) node;
235                if (matchesNameOrWildcard(name, element.getNodeName())) {
236                    out.add(element);
237                }
238                element.getElementsByTagName(out, name);
239            }
240        }
241    }
242
243    void getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName) {
244        for (NodeImpl node : children) {
245            if (node.getNodeType() == Node.ELEMENT_NODE) {
246                ElementImpl element = (ElementImpl) node;
247                if (matchesNameOrWildcard(namespaceURI, element.getNamespaceURI())
248                        && matchesNameOrWildcard(localName, element.getLocalName())) {
249                    out.add(element);
250                }
251                element.getElementsByTagNameNS(out, namespaceURI, localName);
252            }
253        }
254    }
255
256    /**
257     * Returns true if {@code pattern} equals either "*" or {@code s}. Pattern
258     * may be {@code null}.
259     */
260    private static boolean matchesNameOrWildcard(String pattern, String s) {
261        return "*".equals(pattern) || Objects.equal(pattern, s);
262    }
263}
264