Coverage Report - net.sf.jmatchparser.util.LocationAwareDOMParser
 
Classes in this File Line Coverage Branch Coverage Complexity
LocationAwareDOMParser
88%
32/36
57%
8/14
3,467
LocationAwareDOMParser$1
69%
38/55
44%
17/38
3,467
 
 1  
 /*
 2  
  * Copyright (c) 2010 - 2011 Michael Schierl
 3  
  * 
 4  
  * All rights reserved.
 5  
  * 
 6  
  * Redistribution and use in source and binary forms, with or without
 7  
  * modification, are permitted provided that the following conditions
 8  
  * are met:
 9  
  * 
 10  
  * - Redistributions of source code must retain the above copyright notice,
 11  
  *   this list of conditions and the following disclaimer.
 12  
  *   
 13  
  * - Redistributions in binary form must reproduce the above copyright
 14  
  *   notice, this list of conditions and the following disclaimer in the
 15  
  *   documentation and/or other materials provided with the distribution.
 16  
  *   
 17  
  * - Neither name of the copyright holders nor the names of its
 18  
  *   contributors may be used to endorse or promote products derived from
 19  
  *   this software without specific prior written permission.
 20  
  *   
 21  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS
 22  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 23  
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 24  
  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 25  
  * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 26  
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 27  
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 28  
  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 29  
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 30  
  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 31  
  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32  
  */
 33  
 package net.sf.jmatchparser.util;
 34  
 
 35  
 import java.io.ByteArrayInputStream;
 36  
 import java.io.ByteArrayOutputStream;
 37  
 import java.io.IOException;
 38  
 import java.io.InputStream;
 39  
 import java.io.Reader;
 40  
 import java.io.StringReader;
 41  
 import java.io.StringWriter;
 42  
 
 43  
 import javax.xml.parsers.DocumentBuilder;
 44  
 import javax.xml.parsers.DocumentBuilderFactory;
 45  
 import javax.xml.parsers.SAXParser;
 46  
 import javax.xml.parsers.SAXParserFactory;
 47  
 import javax.xml.stream.events.ProcessingInstruction;
 48  
 
 49  
 import org.w3c.dom.Document;
 50  
 import org.w3c.dom.Element;
 51  
 import org.w3c.dom.Node;
 52  
 import org.w3c.dom.Text;
 53  
 import org.xml.sax.Attributes;
 54  
 import org.xml.sax.InputSource;
 55  
 import org.xml.sax.Locator;
 56  
 import org.xml.sax.SAXException;
 57  
 import org.xml.sax.helpers.DefaultHandler;
 58  
 
 59  
 /**
 60  
  * A {@link org.w3c.dom.Document DOM} parser that annotates each
 61  
  * {@link org.w3c.dom.Node} with an approximate line/column number where it was
 62  
  * parsed.
 63  
  * 
 64  
  * Technically, this is done by parsing the document twice; therefore, this
 65  
  * class also provides a utility method to create clones of an
 66  
  * {@link InputSource}.
 67  
  */
 68  30
 public class LocationAwareDOMParser {
 69  
 
 70  1
         private static final String LINE_NUMBER = LocationAwareDOMParser.class.getName() + "#LINE_NUMBER";
 71  1
         private static final String COLUMN_NUMBER = LocationAwareDOMParser.class.getName() + "#COLUMN_NUMBER";
 72  
 
 73  
         /**
 74  
          * Create an arbitrary number of clones of an {@link InputSource}. The
 75  
          * original InputSource will be invalid after that, but each of the clone
 76  
          * can be used individually to parse the same document in multiple ways.
 77  
          * 
 78  
          * @param input
 79  
          *            The InputSource to clone
 80  
          * @param count
 81  
          *            The desired number of clones
 82  
          * @return An array that contains the clones
 83  
          */
 84  
         public static InputSource[] createClones(InputSource input, int count) throws IOException {
 85  1
                 byte[] byteBuf = null;
 86  1
                 InputStream byteStream = input.getByteStream();
 87  1
                 if (byteStream != null) {
 88  0
                         ByteArrayOutputStream baos = new ByteArrayOutputStream(byteStream.available());
 89  0
                         StreamForwarder.forward(byteStream, baos);
 90  0
                         byteBuf = baos.toByteArray();
 91  
                 }
 92  1
                 String charBuf = null;
 93  1
                 Reader charStream = input.getCharacterStream();
 94  1
                 if (charStream != null) {
 95  1
                         StringWriter sw = new StringWriter();
 96  1
                         StreamForwarder.forward(charStream, sw);
 97  1
                         charBuf = sw.toString();
 98  
                 }
 99  1
                 InputSource[] result = new InputSource[count];
 100  3
                 for (int i = 0; i < result.length; i++) {
 101  2
                         result[i] = new InputSource();
 102  2
                         if (byteBuf != null)
 103  0
                                 result[i].setByteStream(new ByteArrayInputStream(byteBuf));
 104  2
                         if (charBuf != null)
 105  2
                                 result[i].setCharacterStream(new StringReader(charBuf));
 106  2
                         result[i].setEncoding(input.getEncoding());
 107  2
                         result[i].setPublicId(input.getPublicId());
 108  2
                         result[i].setSystemId(input.getSystemId());
 109  
                 }
 110  1
                 return result;
 111  
         }
 112  
 
 113  
         /**
 114  
          * Parse the given {@link InputSource} to a DOM document that contains line
 115  
          * number information, using the default {@link DocumentBuilder}.
 116  
          */
 117  
         public static Document parse(InputSource source) throws Exception {
 118  1
                 return parse(DocumentBuilderFactory.newInstance().newDocumentBuilder(), source);
 119  
         }
 120  
 
 121  
         /**
 122  
          * Parse the given {@link InputSource} to a DOM document that contains line
 123  
          * number information, using a custom {@link DocumentBuilder}.
 124  
          */
 125  
         public static Document parse(DocumentBuilder documentBuilder, InputSource source) throws Exception {
 126  1
                 InputSource[] sources = createClones(source, 2);
 127  1
                 final Document doc = documentBuilder.parse(sources[0]);
 128  1
                 SAXParser p = SAXParserFactory.newInstance().newSAXParser();
 129  1
                 p.parse(sources[1], new DefaultHandler() {
 130  
 
 131  1
                         private int nextLine = 1, nextCol = 1;
 132  
                         private Locator locator;
 133  
                         private Node nextNode, parentNode;
 134  
 
 135  
                         @Override
 136  
                         public void setDocumentLocator(Locator locator) {
 137  1
                                 this.locator = locator;
 138  1
                         }
 139  
 
 140  
                         @Override
 141  
                         public void startDocument() throws SAXException {
 142  1
                                 parentNode = null;
 143  1
                                 updateLocation(doc);
 144  1
                                 nextNode = doc.getDocumentElement();
 145  1
                         }
 146  
 
 147  
                         @Override
 148  
                         public void endDocument() throws SAXException {
 149  1
                                 if (nextNode != null || parentNode != doc)
 150  0
                                         throw new RuntimeException("Invalid state!");
 151  1
                         }
 152  
 
 153  
                         @Override
 154  
                         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 155  9
                                 if (!validateNode(nextNode, uri, localName, qName, attributes))
 156  0
                                         throw new RuntimeException("Nodes out of sync");
 157  9
                                 updateLocation(nextNode);
 158  9
                                 parentNode = nextNode;
 159  9
                                 nextNode = parentNode.getFirstChild();
 160  9
                         }
 161  
 
 162  
                         @Override
 163  
                         public void endElement(String uri, String localName, String qName) throws SAXException {
 164  9
                                 if (nextNode != null)
 165  0
                                         throw new RuntimeException("Nodes out of sync");
 166  9
                                 if (!validateNode(parentNode, uri, localName, qName, null))
 167  0
                                         throw new RuntimeException("Nodes out of sync");
 168  9
                                 Node currentNode = parentNode;
 169  9
                                 updateLocation(null);
 170  9
                                 parentNode = currentNode.getParentNode();
 171  9
                                 nextNode = currentNode.getNextSibling();
 172  9
                         }
 173  
 
 174  
                         @Override
 175  
                         public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
 176  0
                                 updateLocation(null);
 177  0
                         }
 178  
 
 179  
                         @Override
 180  
                         public void characters(char[] ch, int start, int length) throws SAXException {
 181  5
                                 if (!(nextNode instanceof Text && nextNode.getNodeValue().equals(new String(ch, start, length)))) {
 182  0
                                         throw new RuntimeException("Nodes out of sync");
 183  
                                 }
 184  5
                                 updateLocation(nextNode);
 185  5
                                 nextNode = nextNode.getNextSibling();
 186  5
                         }
 187  
 
 188  
                         @Override
 189  
                         public void processingInstruction(String target, String data) throws SAXException {
 190  0
                                 if (!(nextNode instanceof ProcessingInstruction && nextNode.getNodeName().equals(target) && nextNode.getNodeValue().equals(data))) {
 191  0
                                         throw new RuntimeException("Nodes out of sync");
 192  
                                 }
 193  0
                                 updateLocation(nextNode);
 194  0
                                 nextNode = nextNode.getNextSibling();
 195  0
                         }
 196  
 
 197  
                         private void updateLocation(Node node) {
 198  24
                                 if (node != null) {
 199  15
                                         node.setUserData(LINE_NUMBER, new Integer(nextLine), null);
 200  15
                                         node.setUserData(COLUMN_NUMBER, new Integer(nextCol), null);
 201  
                                 }
 202  24
                                 nextLine = locator.getLineNumber();
 203  24
                                 nextCol = locator.getColumnNumber();
 204  24
                         }
 205  
 
 206  
                         private boolean validateNode(Node node, String uri, String localName, String qName, Attributes attributes) {
 207  18
                                 if (!(node instanceof Element))
 208  0
                                         return false;
 209  18
                                 if (!node.getNodeName().equals(qName))
 210  0
                                         return false;
 211  18
                                 else if (node.getNamespaceURI() == null && !uri.equals(""))
 212  0
                                         return false;
 213  18
                                 else if (node.getNamespaceURI() != null && !node.getNamespaceURI().equals(uri))
 214  0
                                         return false;
 215  18
                                 else if (attributes != null && node.getAttributes().getLength() != attributes.getLength())
 216  0
                                         return false;
 217  
                                 else
 218  18
                                         return true;
 219  
                         }
 220  
                 });
 221  1
                 return doc;
 222  
         }
 223  
 
 224  
         /**
 225  
          * Get the line number embedded in a node.
 226  
          * 
 227  
          * @param node
 228  
          *            the node
 229  
          * @return the line number, or -1 if none was embedded
 230  
          */
 231  
         public static int getLineNumber(Node node) {
 232  7
                 Integer line = (Integer) node.getUserData(LocationAwareDOMParser.LINE_NUMBER);
 233  7
                 return line == null ? -1 : line;
 234  
         }
 235  
 
 236  
         /**
 237  
          * Get the column number embedded in a node.
 238  
          * 
 239  
          * @param node
 240  
          *            the node
 241  
          * @return the column number, or -1 if none was embedded
 242  
          */
 243  
         public static int getColumnNumber(Node node) {
 244  7
                 Integer col = (Integer) node.getUserData(LocationAwareDOMParser.COLUMN_NUMBER);
 245  7
                 return col == null ? -1 : col;
 246  
         }
 247  
 }