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 android.sax;
18
19import org.xml.sax.Locator;
20import org.xml.sax.SAXParseException;
21
22import java.util.ArrayList;
23
24import android.util.Log;
25
26/**
27 * An XML element. Provides access to child elements and hooks to listen
28 * for events related to this element.
29 *
30 * @see RootElement
31 */
32public class Element {
33
34    final String uri;
35    final String localName;
36    final int depth;
37    final Element parent;
38
39    Children children;
40    ArrayList<Element> requiredChilden;
41
42    boolean visited;
43
44    StartElementListener startElementListener;
45    EndElementListener endElementListener;
46    EndTextElementListener endTextElementListener;
47
48    Element(Element parent, String uri, String localName, int depth) {
49        this.parent = parent;
50        this.uri = uri;
51        this.localName = localName;
52        this.depth = depth;
53    }
54
55    /**
56     * Gets the child element with the given name. Uses an empty string as the
57     * namespace.
58     */
59    public Element getChild(String localName) {
60        return getChild("", localName);
61    }
62
63    /**
64     * Gets the child element with the given name.
65     */
66    public Element getChild(String uri, String localName) {
67        if (endTextElementListener != null) {
68            throw new IllegalStateException("This element already has an end"
69                    + " text element listener. It cannot have children.");
70        }
71
72        if (children == null) {
73            children = new Children();
74        }
75
76        return children.getOrCreate(this, uri, localName);
77    }
78
79    /**
80     * Gets the child element with the given name. Uses an empty string as the
81     * namespace. We will throw a {@link org.xml.sax.SAXException} at parsing
82     * time if the specified child is missing. This helps you ensure that your
83     * listeners are called.
84     */
85    public Element requireChild(String localName) {
86        return requireChild("", localName);
87    }
88
89    /**
90     * Gets the child element with the given name. We will throw a
91     * {@link org.xml.sax.SAXException} at parsing time if the specified child
92     * is missing. This helps you ensure that your listeners are called.
93     */
94    public Element requireChild(String uri, String localName) {
95        Element child = getChild(uri, localName);
96
97        if (requiredChilden == null) {
98            requiredChilden = new ArrayList<Element>();
99            requiredChilden.add(child);
100        } else {
101            if (!requiredChilden.contains(child)) {
102                requiredChilden.add(child);
103            }
104        }
105
106        return child;
107    }
108
109    /**
110     * Sets start and end element listeners at the same time.
111     */
112    public void setElementListener(ElementListener elementListener) {
113        setStartElementListener(elementListener);
114        setEndElementListener(elementListener);
115    }
116
117    /**
118     * Sets start and end text element listeners at the same time.
119     */
120    public void setTextElementListener(TextElementListener elementListener) {
121        setStartElementListener(elementListener);
122        setEndTextElementListener(elementListener);
123    }
124
125    /**
126     * Sets a listener for the start of this element.
127     */
128    public void setStartElementListener(
129            StartElementListener startElementListener) {
130        if (this.startElementListener != null) {
131            throw new IllegalStateException(
132                    "Start element listener has already been set.");
133        }
134        this.startElementListener = startElementListener;
135    }
136
137    /**
138     * Sets a listener for the end of this element.
139     */
140    public void setEndElementListener(EndElementListener endElementListener) {
141        if (this.endElementListener != null) {
142            throw new IllegalStateException(
143                    "End element listener has already been set.");
144        }
145        this.endElementListener = endElementListener;
146    }
147
148    /**
149     * Sets a listener for the end of this text element.
150     */
151    public void setEndTextElementListener(
152            EndTextElementListener endTextElementListener) {
153        if (this.endTextElementListener != null) {
154            throw new IllegalStateException(
155                    "End text element listener has already been set.");
156        }
157
158        if (children != null) {
159            throw new IllegalStateException("This element already has children."
160                    + " It cannot have an end text element listener.");
161        }
162
163        this.endTextElementListener = endTextElementListener;
164    }
165
166    @Override
167    public String toString() {
168        return toString(uri, localName);
169    }
170
171    static String toString(String uri, String localName) {
172        return "'" + (uri.equals("") ? localName : uri + ":" + localName) + "'";
173    }
174
175    /**
176     * Clears flags on required children.
177     */
178    void resetRequiredChildren() {
179        ArrayList<Element> requiredChildren = this.requiredChilden;
180        if (requiredChildren != null) {
181            for (int i = requiredChildren.size() - 1; i >= 0; i--) {
182                requiredChildren.get(i).visited = false;
183            }
184        }
185    }
186
187    /**
188     * Throws an exception if a required child was not present.
189     */
190    void checkRequiredChildren(Locator locator) throws SAXParseException {
191        ArrayList<Element> requiredChildren = this.requiredChilden;
192        if (requiredChildren != null) {
193            for (int i = requiredChildren.size() - 1; i >= 0; i--) {
194                Element child = requiredChildren.get(i);
195                if (!child.visited) {
196                    throw new BadXmlException(
197                            "Element named " + this + " is missing required"
198                                    + " child element named "
199                                    + child + ".", locator);
200                }
201            }
202        }
203    }
204}
205