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