ElementImpl.java revision f33eae7e84eb6d3b0f4e86b59605bb3de73009f3
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.Attr;
20import org.w3c.dom.DOMException;
21import org.w3c.dom.Element;
22import org.w3c.dom.NamedNodeMap;
23import org.w3c.dom.Node;
24import org.w3c.dom.NodeList;
25import org.w3c.dom.TypeInfo;
26
27import java.util.ArrayList;
28import java.util.List;
29
30/**
31 * Provides a straightforward implementation of the corresponding W3C DOM
32 * interface. The class is used internally only, thus only notable members that
33 * are not in the original interface are documented (the W3C docs are quite
34 * extensive). Hope that's ok.
35 * <p>
36 * Some of the fields may have package visibility, so other classes belonging to
37 * the DOM implementation can easily access them while maintaining the DOM tree
38 * structure.
39 */
40public class ElementImpl extends InnerNodeImpl implements Element {
41
42    boolean namespaceAware;
43    String namespaceURI;
44    String prefix;
45    String localName;
46
47    private List<AttrImpl> attributes = new ArrayList<AttrImpl>();
48
49    ElementImpl(DocumentImpl document, String namespaceURI, String qualifiedName) {
50        super(document);
51        setNameNS(this, namespaceURI, qualifiedName);
52    }
53
54    ElementImpl(DocumentImpl document, String name) {
55        super(document);
56
57        this.namespaceAware = false;
58
59        int p = name.lastIndexOf(":");
60        if (p != -1) {
61            String prefix = name.substring(0, p);
62            String localName = name.substring(p + 1);
63
64            if (!DocumentImpl.isXMLIdentifier(prefix) || !DocumentImpl.isXMLIdentifier(localName)) {
65                throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name);
66            }
67        } else {
68            if (!DocumentImpl.isXMLIdentifier(name)) {
69                throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name);
70            }
71        }
72
73        this.localName = name;
74    }
75
76    private int indexOfAttribute(String name) {
77        for (int i = 0; i < attributes.size(); i++) {
78            AttrImpl attr = attributes.get(i);
79            if (attr.matchesName(name, false)) {
80                return i;
81            }
82        }
83
84        return -1;
85    }
86
87    private int indexOfAttributeNS(String namespaceURI, String localName) {
88        for (int i = 0; i < attributes.size(); i++) {
89            AttrImpl attr = attributes.get(i);
90            if (attr.matchesNameNS(namespaceURI, localName, false)) {
91                return i;
92            }
93        }
94
95        return -1;
96    }
97
98    public String getAttribute(String name) {
99        Attr attr = getAttributeNode(name);
100
101        if (attr == null) {
102            return "";
103        }
104
105        return attr.getValue();
106    }
107
108    public String getAttributeNS(String namespaceURI, String localName) {
109        Attr attr = getAttributeNodeNS(namespaceURI, localName);
110
111        if (attr == null) {
112            return "";
113        }
114
115        return attr.getValue();
116    }
117
118    public AttrImpl getAttributeNode(String name) {
119        int i = indexOfAttribute(name);
120
121        if (i == -1) {
122            return null;
123        }
124
125        return attributes.get(i);
126    }
127
128    public AttrImpl getAttributeNodeNS(String namespaceURI, String localName) {
129        int i = indexOfAttributeNS(namespaceURI, localName);
130
131        if (i == -1) {
132            return null;
133        }
134
135        return attributes.get(i);
136    }
137
138    @Override
139    public NamedNodeMap getAttributes() {
140        return new ElementAttrNamedNodeMapImpl();
141    }
142
143    /**
144     * This implementation walks the entire document looking for an element
145     * with the given ID attribute. We should consider adding an index to speed
146     * navigation of large documents.
147     */
148    Element getElementById(String name) {
149        for (Attr attr : attributes) {
150            if (attr.isId() && name.equals(attr.getValue())) {
151                return this;
152            }
153        }
154
155        /*
156         * TODO: Remove this behavior.
157         * The spec explicitly says that this is a bad idea. From
158         * Document.getElementById(): "Attributes with the name "ID"
159         * or "id" are not of type ID unless so defined.
160         */
161        if (name.equals(getAttribute("id"))) {
162            return this;
163        }
164
165        for (NodeImpl node : children) {
166            if (node.getNodeType() == Node.ELEMENT_NODE) {
167                Element element = ((ElementImpl) node).getElementById(name);
168                if (element != null) {
169                    return element;
170                }
171            }
172        }
173
174        return null;
175    }
176
177    public NodeList getElementsByTagName(String name) {
178        NodeListImpl list = new NodeListImpl();
179        getElementsByTagName(list, name);
180        return list;
181    }
182
183    void getElementsByTagName(NodeListImpl list, String name) {
184        if (matchesName(name, true)) {
185            list.add(this);
186        }
187
188        for (NodeImpl node : children) {
189            if (node.getNodeType() == Node.ELEMENT_NODE) {
190                ((ElementImpl) node).getElementsByTagName(list, name);
191            }
192        }
193    }
194
195    public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
196        NodeListImpl list = new NodeListImpl();
197        getElementsByTagNameNS(list, namespaceURI, localName);
198        return list;
199    }
200
201    void getElementsByTagNameNS(NodeListImpl list, String namespaceURI,
202            String localName) {
203        if (matchesNameNS(namespaceURI, localName, true)) {
204            list.add(this);
205        }
206
207        for (NodeImpl node : children) {
208            if (node.getNodeType() == Node.ELEMENT_NODE) {
209                ((ElementImpl) node).getElementsByTagNameNS(list, namespaceURI,
210                        localName);
211            }
212        }
213    }
214
215    @Override
216    public String getLocalName() {
217        return namespaceAware ? localName : null;
218    }
219
220    @Override
221    public String getNamespaceURI() {
222        return namespaceURI;
223    }
224
225    @Override
226    public String getNodeName() {
227        return getTagName();
228    }
229
230    public short getNodeType() {
231        return Node.ELEMENT_NODE;
232    }
233
234    @Override
235    public String getPrefix() {
236        return prefix;
237    }
238
239    public String getTagName() {
240        return prefix != null
241                ? prefix + ":" + localName
242                : localName;
243    }
244
245    public boolean hasAttribute(String name) {
246        return indexOfAttribute(name) != -1;
247    }
248
249    public boolean hasAttributeNS(String namespaceURI, String localName) {
250        return indexOfAttributeNS(namespaceURI, localName) != -1;
251    }
252
253    @Override
254    public boolean hasAttributes() {
255        return !attributes.isEmpty();
256    }
257
258    public void removeAttribute(String name) throws DOMException {
259        int i = indexOfAttribute(name);
260
261        if (i != -1) {
262            attributes.remove(i);
263        }
264    }
265
266    public void removeAttributeNS(String namespaceURI, String localName)
267            throws DOMException {
268        int i = indexOfAttributeNS(namespaceURI, localName);
269
270        if (i != -1) {
271            attributes.remove(i);
272        }
273    }
274
275    public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
276        AttrImpl oldAttrImpl = (AttrImpl) oldAttr;
277
278        if (oldAttrImpl.getOwnerElement() != this) {
279            throw new DOMException(DOMException.NOT_FOUND_ERR, null);
280        }
281
282        attributes.remove(oldAttrImpl);
283        oldAttrImpl.ownerElement = null;
284
285        return oldAttrImpl;
286    }
287
288    public void setAttribute(String name, String value) throws DOMException {
289        Attr attr = getAttributeNode(name);
290
291        if (attr == null) {
292            attr = document.createAttribute(name);
293            setAttributeNode(attr);
294        }
295
296        attr.setValue(value);
297    }
298
299    public void setAttributeNS(String namespaceURI, String qualifiedName,
300            String value) throws DOMException {
301        Attr attr = getAttributeNodeNS(namespaceURI, qualifiedName);
302
303        if (attr == null) {
304            attr = document.createAttributeNS(namespaceURI, qualifiedName);
305            setAttributeNodeNS(attr);
306        }
307
308        attr.setValue(value);
309    }
310
311    public Attr setAttributeNode(Attr newAttr) throws DOMException {
312        AttrImpl newAttrImpl = (AttrImpl) newAttr;
313
314        if (newAttrImpl.document != this.document) {
315            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
316        }
317
318        if (newAttrImpl.getOwnerElement() != null) {
319            throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, null);
320        }
321
322        AttrImpl oldAttrImpl = null;
323
324        int i = indexOfAttribute(newAttr.getName());
325        if (i != -1) {
326            oldAttrImpl = attributes.get(i);
327            attributes.remove(i);
328        }
329
330        attributes.add(newAttrImpl);
331        newAttrImpl.ownerElement = this;
332
333        return oldAttrImpl;
334    }
335
336    public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
337        AttrImpl newAttrImpl = (AttrImpl) newAttr;
338
339        if (newAttrImpl.document != this.document) {
340            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
341        }
342
343        if (newAttrImpl.getOwnerElement() != null) {
344            throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, null);
345        }
346
347        AttrImpl oldAttrImpl = null;
348
349        int i = indexOfAttributeNS(newAttr.getNamespaceURI(), newAttr.getLocalName());
350        if (i != -1) {
351            oldAttrImpl = attributes.get(i);
352            attributes.remove(i);
353        }
354
355        attributes.add(newAttrImpl);
356        newAttrImpl.ownerElement = this;
357
358        return oldAttrImpl;
359    }
360
361    @Override
362    public void setPrefix(String prefix) {
363        this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI);
364    }
365
366    public class ElementAttrNamedNodeMapImpl implements NamedNodeMap {
367
368        public int getLength() {
369            return ElementImpl.this.attributes.size();
370        }
371
372        private int indexOfItem(String name) {
373            return ElementImpl.this.indexOfAttribute(name);
374        }
375
376        private int indexOfItemNS(String namespaceURI, String localName) {
377            return ElementImpl.this.indexOfAttributeNS(namespaceURI, localName);
378        }
379
380        public Node getNamedItem(String name) {
381            return ElementImpl.this.getAttributeNode(name);
382        }
383
384        public Node getNamedItemNS(String namespaceURI, String localName) {
385            return ElementImpl.this.getAttributeNodeNS(namespaceURI, localName);
386        }
387
388        public Node item(int index) {
389            return ElementImpl.this.attributes.get(index);
390        }
391
392        public Node removeNamedItem(String name) throws DOMException {
393            int i = indexOfItem(name);
394
395            if (i == -1) {
396                throw new DOMException(DOMException.NOT_FOUND_ERR, null);
397            }
398
399            return ElementImpl.this.attributes.remove(i);
400        }
401
402        public Node removeNamedItemNS(String namespaceURI, String localName)
403                throws DOMException {
404            int i = indexOfItemNS(namespaceURI, localName);
405
406            if (i == -1) {
407                throw new DOMException(DOMException.NOT_FOUND_ERR, null);
408            }
409
410            return ElementImpl.this.attributes.remove(i);
411        }
412
413        public Node setNamedItem(Node arg) throws DOMException {
414            if (!(arg instanceof Attr)) {
415                throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
416            }
417
418            return ElementImpl.this.setAttributeNode((Attr)arg);
419        }
420
421        public Node setNamedItemNS(Node arg) throws DOMException {
422            if (!(arg instanceof Attr)) {
423                throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
424            }
425
426            return ElementImpl.this.setAttributeNodeNS((Attr)arg);
427        }
428    }
429
430    public TypeInfo getSchemaTypeInfo() {
431        // TODO: populate this when we support XML Schema
432        return NULL_TYPE_INFO;
433    }
434
435    public void setIdAttribute(String name, boolean isId) throws DOMException {
436        AttrImpl attr = getAttributeNode(name);
437        if (attr == null) {
438            throw new DOMException(DOMException.NOT_FOUND_ERR,
439                    "No such attribute: " + name);
440        }
441        attr.isId = isId;
442    }
443
444    public void setIdAttributeNS(String namespaceURI, String localName,
445            boolean isId) throws DOMException {
446        AttrImpl attr = getAttributeNodeNS(namespaceURI, localName);
447        if (attr == null) {
448            throw new DOMException(DOMException.NOT_FOUND_ERR,
449                    "No such attribute: " + namespaceURI +  " " + localName);
450        }
451        attr.isId = isId;
452    }
453
454    public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
455        ((AttrImpl) idAttr).isId = isId;
456    }
457}
458