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