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