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.util.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    public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
168        NodeListImpl result = new NodeListImpl();
169        getElementsByTagNameNS(result, namespaceURI, localName);
170        return result;
171    }
172
173    @Override
174    public String getLocalName() {
175        return namespaceAware ? localName : null;
176    }
177
178    @Override
179    public String getNamespaceURI() {
180        return namespaceURI;
181    }
182
183    @Override
184    public String getNodeName() {
185        return getTagName();
186    }
187
188    public short getNodeType() {
189        return Node.ELEMENT_NODE;
190    }
191
192    @Override
193    public String getPrefix() {
194        return prefix;
195    }
196
197    public String getTagName() {
198        return prefix != null
199                ? prefix + ":" + localName
200                : localName;
201    }
202
203    public boolean hasAttribute(String name) {
204        return indexOfAttribute(name) != -1;
205    }
206
207    public boolean hasAttributeNS(String namespaceURI, String localName) {
208        return indexOfAttributeNS(namespaceURI, localName) != -1;
209    }
210
211    @Override
212    public boolean hasAttributes() {
213        return !attributes.isEmpty();
214    }
215
216    public void removeAttribute(String name) throws DOMException {
217        int i = indexOfAttribute(name);
218
219        if (i != -1) {
220            attributes.remove(i);
221        }
222    }
223
224    public void removeAttributeNS(String namespaceURI, String localName)
225            throws DOMException {
226        int i = indexOfAttributeNS(namespaceURI, localName);
227
228        if (i != -1) {
229            attributes.remove(i);
230        }
231    }
232
233    public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
234        AttrImpl oldAttrImpl = (AttrImpl) oldAttr;
235
236        if (oldAttrImpl.getOwnerElement() != this) {
237            throw new DOMException(DOMException.NOT_FOUND_ERR, null);
238        }
239
240        attributes.remove(oldAttrImpl);
241        oldAttrImpl.ownerElement = null;
242
243        return oldAttrImpl;
244    }
245
246    public void setAttribute(String name, String value) throws DOMException {
247        Attr attr = getAttributeNode(name);
248
249        if (attr == null) {
250            attr = document.createAttribute(name);
251            setAttributeNode(attr);
252        }
253
254        attr.setValue(value);
255    }
256
257    public void setAttributeNS(String namespaceURI, String qualifiedName,
258            String value) throws DOMException {
259        Attr attr = getAttributeNodeNS(namespaceURI, qualifiedName);
260
261        if (attr == null) {
262            attr = document.createAttributeNS(namespaceURI, qualifiedName);
263            setAttributeNodeNS(attr);
264        }
265
266        attr.setValue(value);
267    }
268
269    public Attr setAttributeNode(Attr newAttr) throws DOMException {
270        AttrImpl newAttrImpl = (AttrImpl) newAttr;
271
272        if (newAttrImpl.document != this.document) {
273            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
274        }
275
276        if (newAttrImpl.getOwnerElement() != null) {
277            throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, null);
278        }
279
280        AttrImpl oldAttrImpl = null;
281
282        int i = indexOfAttribute(newAttr.getName());
283        if (i != -1) {
284            oldAttrImpl = attributes.get(i);
285            attributes.remove(i);
286        }
287
288        attributes.add(newAttrImpl);
289        newAttrImpl.ownerElement = this;
290
291        return oldAttrImpl;
292    }
293
294    public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
295        AttrImpl newAttrImpl = (AttrImpl) newAttr;
296
297        if (newAttrImpl.document != this.document) {
298            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
299        }
300
301        if (newAttrImpl.getOwnerElement() != null) {
302            throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, null);
303        }
304
305        AttrImpl oldAttrImpl = null;
306
307        int i = indexOfAttributeNS(newAttr.getNamespaceURI(), newAttr.getLocalName());
308        if (i != -1) {
309            oldAttrImpl = attributes.get(i);
310            attributes.remove(i);
311        }
312
313        attributes.add(newAttrImpl);
314        newAttrImpl.ownerElement = this;
315
316        return oldAttrImpl;
317    }
318
319    @Override
320    public void setPrefix(String prefix) {
321        this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI);
322    }
323
324    public class ElementAttrNamedNodeMapImpl implements NamedNodeMap {
325
326        public int getLength() {
327            return ElementImpl.this.attributes.size();
328        }
329
330        private int indexOfItem(String name) {
331            return ElementImpl.this.indexOfAttribute(name);
332        }
333
334        private int indexOfItemNS(String namespaceURI, String localName) {
335            return ElementImpl.this.indexOfAttributeNS(namespaceURI, localName);
336        }
337
338        public Node getNamedItem(String name) {
339            return ElementImpl.this.getAttributeNode(name);
340        }
341
342        public Node getNamedItemNS(String namespaceURI, String localName) {
343            return ElementImpl.this.getAttributeNodeNS(namespaceURI, localName);
344        }
345
346        public Node item(int index) {
347            return ElementImpl.this.attributes.get(index);
348        }
349
350        public Node removeNamedItem(String name) throws DOMException {
351            int i = indexOfItem(name);
352
353            if (i == -1) {
354                throw new DOMException(DOMException.NOT_FOUND_ERR, null);
355            }
356
357            return ElementImpl.this.attributes.remove(i);
358        }
359
360        public Node removeNamedItemNS(String namespaceURI, String localName)
361                throws DOMException {
362            int i = indexOfItemNS(namespaceURI, localName);
363
364            if (i == -1) {
365                throw new DOMException(DOMException.NOT_FOUND_ERR, null);
366            }
367
368            return ElementImpl.this.attributes.remove(i);
369        }
370
371        public Node setNamedItem(Node arg) throws DOMException {
372            if (!(arg instanceof Attr)) {
373                throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
374            }
375
376            return ElementImpl.this.setAttributeNode((Attr)arg);
377        }
378
379        public Node setNamedItemNS(Node arg) throws DOMException {
380            if (!(arg instanceof Attr)) {
381                throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
382            }
383
384            return ElementImpl.this.setAttributeNodeNS((Attr)arg);
385        }
386    }
387
388    public TypeInfo getSchemaTypeInfo() {
389        // TODO: populate this when we support XML Schema
390        return NULL_TYPE_INFO;
391    }
392
393    public void setIdAttribute(String name, boolean isId) throws DOMException {
394        AttrImpl attr = getAttributeNode(name);
395        if (attr == null) {
396            throw new DOMException(DOMException.NOT_FOUND_ERR,
397                    "No such attribute: " + name);
398        }
399        attr.isId = isId;
400    }
401
402    public void setIdAttributeNS(String namespaceURI, String localName,
403            boolean isId) throws DOMException {
404        AttrImpl attr = getAttributeNodeNS(namespaceURI, localName);
405        if (attr == null) {
406            throw new DOMException(DOMException.NOT_FOUND_ERR,
407                    "No such attribute: " + namespaceURI +  " " + localName);
408        }
409        attr.isId = isId;
410    }
411
412    public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
413        ((AttrImpl) idAttr).isId = isId;
414    }
415}
416