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    }