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 }