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