001 /*--------------------------------------------------------------------------+
002 $Id: XMLReader.java 26268 2010-02-18 10:44:30Z juergens $
003 | |
004 | Copyright 2005-2010 Technische Universitaet Muenchen |
005 | |
006 | Licensed under the Apache License, Version 2.0 (the "License"); |
007 | you may not use this file except in compliance with the License. |
008 | You may obtain a copy of the License at |
009 | |
010 | http://www.apache.org/licenses/LICENSE-2.0 |
011 | |
012 | Unless required by applicable law or agreed to in writing, software |
013 | distributed under the License is distributed on an "AS IS" BASIS, |
014 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015 | See the License for the specific language governing permissions and |
016 | limitations under the License. |
017 +--------------------------------------------------------------------------*/
018 package edu.tum.cs.commons.xml;
019
020 import java.io.File;
021 import java.io.FileInputStream;
022 import java.io.IOException;
023 import java.net.URL;
024 import java.util.ArrayList;
025 import java.util.List;
026
027 import org.w3c.dom.Document;
028 import org.w3c.dom.Element;
029 import org.w3c.dom.Node;
030 import org.w3c.dom.NodeList;
031 import org.xml.sax.InputSource;
032 import org.xml.sax.SAXException;
033
034 import edu.tum.cs.commons.assertion.CCSMPre;
035
036 /**
037 * Utility class for reading XML documents. Please consult test case
038 * {@link XMLReaderTest} to see how this class is intended to be used.
039 *
040 * @author Florian Deissenboeck
041 * @author $Author: juergens $
042 * @version $Rev: 26268 $
043 * @levd.rating GREEN Hash: 069D289898A054917CFA09EFB65AAE80
044 */
045 public abstract class XMLReader<E extends Enum<E>, A extends Enum<A>, X extends Exception> {
046
047 /** The current DOM element. */
048 private Element currentDOMElement;
049
050 /** The schema URL to use or <code>null</code> if no schema is used. */
051 private final URL schemaURL;
052
053 /** Resolver used by the writer. */
054 private final IXMLResolver<E, A> xmlResolver;
055
056 /** File to parse. */
057 private final File file;
058
059 /** XML encoding of the file. */
060 private final String encoding;
061
062 /**
063 * Create new reader.
064 *
065 * @param file
066 * the file to be read
067 * @param xmlResolver
068 * resolvers used by this reader
069 */
070 public XMLReader(File file, IXMLResolver<E, A> xmlResolver) {
071 this(file, null, null, xmlResolver);
072 }
073
074 /**
075 * Create reader.
076 *
077 * @param file
078 * the file to be read
079 * @param encoding
080 * XML encoding of the file. No encoding is set if
081 * <code>null</code>.
082 * @param xmlResolver
083 * resolvers used by this reader
084 */
085 public XMLReader(File file, String encoding, IXMLResolver<E, A> xmlResolver) {
086 this(file, encoding, null, xmlResolver);
087 }
088
089 /**
090 * Create reader.
091 *
092 * @param file
093 * the file to be read
094 * @param schemaURL
095 * the URL pointing to the schema that is used for validation. No
096 * validation will be performed if <code>null</code>.
097 * @param xmlResolver
098 * resolvers used by this reader
099 */
100 public XMLReader(File file, URL schemaURL, IXMLResolver<E, A> xmlResolver) {
101 this(file, null, schemaURL, xmlResolver);
102 }
103
104 /**
105 * Create reader.
106 *
107 * @param file
108 * the file to be read
109 * @param encoding
110 * XML encoding of the file. No encoding is set if
111 * <code>null</code>.
112 * @param schemaURL
113 * the URL pointing to the schema that is used for validation. No
114 * validation will be performed if <code>null</code>.
115 * @param xmlResolver
116 * resolvers used by this reader
117 */
118 public XMLReader(File file, String encoding, URL schemaURL,
119 IXMLResolver<E, A> xmlResolver) {
120 CCSMPre.isFalse(file == null, "File may not be null.");
121 CCSMPre.isFalse(xmlResolver == null, "XML resolver may not be null.");
122 this.file = file;
123 this.encoding = encoding;
124 this.schemaURL = schemaURL;
125 this.xmlResolver = xmlResolver;
126 }
127
128 /**
129 * Get <code>boolean</code> value of an attribute.
130 *
131 * @return the boolean value, semantics for non-translatable or
132 * <code>null</code> values is defined by
133 * {@link Boolean#valueOf(String)}.
134 */
135 protected boolean getBooleanAttribute(A attribute) {
136 String value = getStringAttribute(attribute);
137 return Boolean.valueOf(value);
138 }
139
140 /**
141 * Get the text content of a child element of the current element.
142 *
143 * @param childElement
144 * the child element
145 * @return the text or <code>null</code> if the current element doesn't have
146 * the requested child element
147 */
148 protected String getChildText(E childElement) {
149
150 Element domElement = getChildElement(childElement);
151 if (domElement == null) {
152 return null;
153 }
154
155 return domElement.getTextContent();
156 }
157
158 /**
159 * Translate attribute value to an enumeration element.
160 *
161 * @param attribute
162 * the attribute
163 * @param enumClass
164 * the enumeration class
165 *
166 * @return the enum value, semantics for non-translatable or
167 * <code>null</code> values is defined by
168 * {@link Enum#valueOf(Class, String)}.
169 */
170 protected <T extends Enum<T>> T getEnumAttribute(A attribute,
171 Class<T> enumClass) {
172 String value = getStringAttribute(attribute);
173 return Enum.valueOf(enumClass, value);
174 }
175
176 /**
177 * Get <code>int</code> value of an attribute.
178 *
179 * @return the int value, semantics for non-translatable or
180 * <code>null</code> values is defined by
181 * {@link Integer#valueOf(String)}.
182 */
183 protected int getIntAttribute(A attribute) {
184 String value = getStringAttribute(attribute);
185 return Integer.valueOf(value);
186 }
187
188 /**
189 * Get <code>long</code> value of an attribute.
190 *
191 * @return the int value, semantics for non-translatable or
192 * <code>null</code> values is defined by
193 * {@link Integer#valueOf(String)}.
194 */
195 protected long getLongAttribute(A attribute) {
196 String value = getStringAttribute(attribute);
197 return Long.valueOf(value);
198 }
199
200 /**
201 * Get attribute value.
202 *
203 *
204 * @return the attribute value or <code>null</code> if attribute is
205 * undefined.
206 */
207 protected String getStringAttribute(A attribute) {
208 return currentDOMElement.getAttribute(xmlResolver
209 .resolveAttributeName(attribute));
210 }
211
212 /**
213 * Get text content of current node.
214 */
215 protected String getText() {
216 return currentDOMElement.getTextContent();
217 }
218
219 /**
220 * Parse file. This sets the current element focus to the document root
221 * element. If schema URL was set the document is validated against the
222 * schema.
223 * <p>
224 * Sub classes should typically wrap this method with a proper error
225 * handling mechanism.
226 *
227 * @throws SAXException
228 * if a parsing exceptions occurs
229 * @throws IOException
230 * if an IO exception occurs.
231 */
232 protected void parseFile() throws SAXException, IOException {
233
234 FileInputStream stream = new FileInputStream(file);
235
236 try {
237
238 InputSource input = new InputSource(stream);
239 if (encoding != null) {
240 input.setEncoding(encoding);
241 }
242
243 Document document;
244 if (schemaURL == null) {
245 document = XMLUtils.parse(input);
246 } else {
247 document = XMLUtils.parse(input, schemaURL);
248 }
249 currentDOMElement = document.getDocumentElement();
250 } finally {
251 stream.close();
252 }
253 }
254
255 /**
256 * Process the child elements of the current element with a given processor.
257 * Target elements are specified by
258 * {@link IXMLElementProcessor#getTargetElement()}.
259 *
260 * @param processor
261 * the processor used to process the elements
262 * @throws X
263 * if the processor throws an exception
264 */
265 protected void processChildElements(IXMLElementProcessor<E, X> processor)
266 throws X {
267 String targetElementName = xmlResolver.resolveElementName(processor
268 .getTargetElement());
269 processElementList(processor, getChildElements(targetElementName));
270 }
271
272 /**
273 * Process all descendant elements of the current element with a given
274 * processor. In contrast to
275 * {@link #processChildElements(IXMLElementProcessor)}, not only direct
276 * child elements are processed. Descendant elements are processed in the
277 * sequence they are found during a top-down, left-right traversal of the
278 * XML document.
279 * <p>
280 * Target elements are specified by
281 * {@link IXMLElementProcessor#getTargetElement()}.
282 *
283 * @param processor
284 * the processor used to process the elements
285 * @throws X
286 * if the processor throws an exception
287 */
288 protected void processDecendantElements(IXMLElementProcessor<E, X> processor)
289 throws X {
290 String targetElementName = xmlResolver.resolveElementName(processor
291 .getTargetElement());
292
293 NodeList descendantNodes = currentDOMElement
294 .getElementsByTagName(targetElementName);
295
296 processElementList(processor, XMLUtils.elementNodes(descendantNodes));
297 }
298
299 /**
300 * Processes the elements in the list with the given processor
301 *
302 * @param processor
303 * the processor used to process the elements
304 * @param elements
305 * list of elements that get processed
306 * @throws X
307 * if the processor throws an exception
308 */
309 private void processElementList(IXMLElementProcessor<E, X> processor,
310 List<Element> elements) throws X {
311 Element oldElement = currentDOMElement;
312
313 for (Element child : elements) {
314 currentDOMElement = child;
315 processor.process();
316 }
317
318 currentDOMElement = oldElement;
319 }
320
321 /**
322 * Get the first child element of the the specified type of the current DOM
323 * element.
324 *
325 * @param elementType
326 * the desired element type
327 * @return the child element or <code>null</code> if not present.
328 */
329 private Element getChildElement(E elementType) {
330
331 NodeList nodeList = currentDOMElement.getChildNodes();
332 String elementName = xmlResolver.resolveElementName(elementType);
333
334 for (int i = 0; i < nodeList.getLength(); i++) {
335 Node child = nodeList.item(i);
336 if (isTargetElement(child, elementName)) {
337 return (Element) child;
338 }
339 }
340
341 return null;
342 }
343
344 /**
345 * Get all child elements of the current element with the given name.
346 *
347 * @param targetElementName
348 * the name of the target element
349 * @return list of elements
350 */
351 private List<Element> getChildElements(String targetElementName) {
352 NodeList nodeList = currentDOMElement.getChildNodes();
353 ArrayList<Element> list = new ArrayList<Element>();
354
355 for (int i = 0; i < nodeList.getLength(); i++) {
356 Node child = nodeList.item(i);
357
358 if (isTargetElement(child, targetElementName)) {
359 list.add((Element) nodeList.item(i));
360 }
361 }
362
363 return list;
364 }
365
366 /** Checks if a node is a target elements */
367 private boolean isTargetElement(Node child, String targetElementName) {
368 return child.getNodeType() == Node.ELEMENT_NODE
369 && child.getLocalName().equals(targetElementName);
370 }
371 }