Coverage Report - org.simpleframework.xml.stream.NodeReader
 
Classes in this File Line Coverage Branch Coverage Complexity
NodeReader
91%
78/85
81%
47/58
4.231
 
 1  
 /*
 2  
  * NodeReader.java July 2006
 3  
  *
 4  
  * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 15  
  * implied. See the License for the specific language governing 
 16  
  * permissions and limitations under the License.
 17  
  */
 18  
 
 19  
 package org.simpleframework.xml.stream;
 20  
 
 21  
 /**
 22  
  * The <code>NodeReader</code> object is used to read elements from
 23  
  * the specified XML event reader. This reads input node objects
 24  
  * that represent elements within the source XML document. This will
 25  
  * allow details to be read using input node objects, as long as
 26  
  * the end elements for those input nodes have not been ended.
 27  
  * <p>
 28  
  * For example, if an input node represented the root element of a
 29  
  * document then that input node could read all elements within the
 30  
  * document. However, if the input node represented a child element
 31  
  * then it would only be able to read its children.
 32  
  *
 33  
  * @author Niall Gallagher
 34  
  */ 
 35  
 class NodeReader {
 36  
 
 37  
    /**
 38  
     * This is used to collect the text between the element tags.
 39  
     */
 40  
    private final StringBuilder text;
 41  
    
 42  
    /**
 43  
     * Represents the XML event reader used to read all elements.
 44  
     */ 
 45  
    private final EventReader reader;     
 46  
    
 47  
    /**
 48  
     * This stack enables the reader to keep track of elements.
 49  
     */ 
 50  
    private final InputStack stack;
 51  
    
 52  
    /**
 53  
     * Constructor for the <code>NodeReader</code> object. This is used
 54  
     * to read XML events a input node objects from the event reader.
 55  
     *
 56  
     * @param reader this is the event reader for the XML document
 57  
     */ 
 58  74073
    public NodeReader(EventReader reader) {
 59  74073
       this.text = new StringBuilder();
 60  74073
       this.stack = new InputStack();
 61  74073
       this.reader = reader;            
 62  74073
    }        
 63  
    
 64  
    /**
 65  
     * This method is used to determine if this node is the root 
 66  
     * node for the XML document. The root node is the first node
 67  
     * in the document and has no sibling nodes. This is false
 68  
     * if the node has a parent node or a sibling node.
 69  
     * 
 70  
     * @return true if this is the root node within the document
 71  
     */
 72  
    public boolean isRoot(InputNode node) {
 73  13402
       return stack.bottom() == node;        
 74  
    }
 75  
    
 76  
    /**
 77  
     * Returns the root input node for the document. This is returned
 78  
     * only if no other elements have been read. Once the root element
 79  
     * has been read from the event reader this will return null.
 80  
     *
 81  
     * @return this returns the root input node for the document
 82  
     */ 
 83  
    public InputNode readRoot() throws Exception {
 84  74073
       if(stack.isEmpty()) {
 85  74073
          InputNode node = readElement(null);
 86  
          
 87  74073
          if(node == null) {
 88  0
             throw new NodeException("Document has no root element"); 
 89  
          }
 90  74073
          return node;
 91  
       }
 92  0
       return null;
 93  
    }
 94  
    
 95  
    /**
 96  
     * Returns the next input node from the XML document, if it is a
 97  
     * child element of the specified input node. This essentially
 98  
     * determines whether the end tag has been read for the specified
 99  
     * node, if so then null is returned. If however the specified
 100  
     * node has not had its end tag read then this returns the next
 101  
     * element, if that element is a child of the that node.
 102  
     *
 103  
     * @param from this is the input node to read with 
 104  
     *
 105  
     * @return this returns the next input node from the document
 106  
     */ 
 107  
    public InputNode readElement(InputNode from) throws Exception {
 108  2590092
       if(!stack.isRelevant(from)) {         
 109  6
          return null; 
 110  
       }
 111  2590086
       EventNode event = reader.next();
 112  
       
 113  6129012
       while(event != null) {
 114  6129012
          if(event.isEnd()) {
 115  1802460
             if(stack.pop() == from) {
 116  786793
                return null;
 117  
             }               
 118  4326552
          } else if(event.isStart()) {
 119  1803293
             return readStart(from, event);                 
 120  
          }
 121  3538926
          event = reader.next();
 122  
       }
 123  0
       return null;
 124  
    }
 125  
    
 126  
    /**
 127  
     * Returns the next input node from the XML document, if it is a
 128  
     * child element of the specified input node. This essentially
 129  
     * the same as the <code>readElement(InputNode)</code> object 
 130  
     * except that this will not read the element if it does not have
 131  
     * the name specified. This essentially acts as a peak function.
 132  
     *
 133  
     * @param from this is the input node to read with 
 134  
     * @param name this is the name expected from the next element
 135  
     *
 136  
     * @return this returns the next input node from the document
 137  
     */ 
 138  
    public InputNode readElement(InputNode from, String name) throws Exception {
 139  5791
       if(!stack.isRelevant(from)) {        
 140  0
          return null; 
 141  
      }
 142  5791
      EventNode event = reader.peek();
 143  
           
 144  14161
      while(event != null) {
 145  14161
         if(event.isText()) {
 146  7642
            fillText(from);
 147  6519
         } else if(event.isEnd()) { 
 148  993
            if(stack.top() == from) {
 149  265
               return null;
 150  
            } else {
 151  728
               stack.pop();
 152  
            }
 153  5526
         } else if(event.isStart()) {
 154  5526
            if(isName(event, name)) {
 155  4286
               return readElement(from);
 156  
            }   
 157  
            break;
 158  
         }
 159  8370
         event = reader.next();
 160  8370
         event = reader.peek();
 161  
      }
 162  1240
      return null;
 163  
    }
 164  
    
 165  
    /**
 166  
     * This is used to convert the start element to an input node.
 167  
     * This will push the created input node on to the stack. The
 168  
     * input node created contains a reference to this reader. so
 169  
     * that it can be used to read child elements and values.
 170  
     * 
 171  
     * @param from this is the parent element for the start event
 172  
     * @param event this is the start element to be wrapped
 173  
     *
 174  
     * @return this returns an input node for the given element
 175  
     */    
 176  
    private InputNode readStart(InputNode from, EventNode event) throws Exception {
 177  1803293
       InputElement input = new InputElement(from, this, event);
 178  
        
 179  1803293
       if(text.length() > 0) {
 180  4517
          text.setLength(0);
 181  
       }
 182  1803293
       if(event.isStart()) {
 183  1803293
          return stack.push(input);
 184  
       }
 185  0
       return input;
 186  
    }
 187  
 
 188  
    /**
 189  
     * This is used to determine the name of the node specified. The
 190  
     * name of the node is determined to be the name of the element
 191  
     * if that element is converts to a valid StAX start element.
 192  
     * 
 193  
     * @param node this is the StAX node to acquire the name from
 194  
     * @param name this is the name of the node to check against
 195  
     * 
 196  
     * @return true if the specified node has the given local name
 197  
     */
 198  
    private boolean isName(EventNode node, String name) {
 199  5526
       String local = node.getName();
 200  
       
 201  5526
       if(local == null) {
 202  0
          return false;
 203  
       }
 204  5526
       return local.equals(name);
 205  
    }
 206  
    
 207  
    /**
 208  
     * Read the contents of the characters between the specified XML
 209  
     * element tags, if the read is currently at that element. This 
 210  
     * allows characters associated with the element to be used. If
 211  
     * the specified node is not the current node, null is returned.
 212  
     *
 213  
     * @param from this is the input node to read the value from
 214  
     *
 215  
     * @return this returns the characters from the specified node
 216  
     */ 
 217  
    public String readValue(InputNode from) throws Exception {
 218  1007176
       if(!stack.isRelevant(from)) { 
 219  0
          return null;
 220  
       }
 221  1007176
       int length = text.length();
 222  
       
 223  1007176
       if(length <= 0) {
 224  1007149
          EventNode event = reader.peek();
 225  
          
 226  1007149
          if(event.isEnd()) { 
 227  63
             if(stack.top() == from) {
 228  60
                return null;
 229  
             } else {
 230  3
                stack.pop();
 231  
             }
 232  3
             event = reader.next();
 233  
          }
 234  
       }
 235  1007116
       return readText(from);
 236  
    } 
 237  
    
 238  
    /**
 239  
     * Read the contents of the characters between the specified XML
 240  
     * element tags, if the read is currently at that element. This 
 241  
     * allows characters associated with the element to be used. If
 242  
     * the specified node is not the current node, null is returned.
 243  
     *
 244  
     * @param from this is the input node to read the value from
 245  
     *
 246  
     * @return this returns the characters from the specified node
 247  
     */ 
 248  
    private String readText(InputNode from) throws Exception {
 249  1007116
       EventNode event = reader.peek();
 250  
       
 251  2014224
       while(stack.top() == from) {   
 252  2014223
          if(event.isText()) {
 253  1007108
             fillText(from);
 254  
          } else {
 255  
             break;
 256  
          }
 257  1007108
          event = reader.next();
 258  1007108
          event = reader.peek();
 259  
       }
 260  1007116
       return readBuffer(from);
 261  
    }
 262  
    
 263  
    /**
 264  
     * This is used to read the text between element tags. If there
 265  
     * is any text held in the buffer then this will return that
 266  
     * text and clear the buffer. Clearing the buffer in this
 267  
     * way means that the text can only ever be read once.
 268  
     * 
 269  
     * @param from this is the node to read the text from
 270  
     * 
 271  
     * @return this returns the string within the buffer if any
 272  
     */
 273  
    private String readBuffer(InputNode from) throws Exception {
 274  1007116
       int length = text.length();
 275  
       
 276  1007116
       if(length > 0) {
 277  1007115
          String value = text.toString();
 278  
          
 279  1007115
          text.setLength(0);
 280  1007115
          return value;
 281  
       }
 282  1
       return null;
 283  
    }
 284  
    
 285  
    /**
 286  
     * Read the contents of the characters between the specified XML
 287  
     * element tags, if the read is currently at that element. This 
 288  
     * allows characters associated with the element to be used. If
 289  
     * the specified node is not the current node, null is returned.
 290  
     *
 291  
     * @param from this is the input node to read the value from
 292  
     *
 293  
     * @return this returns the characters from the specified node
 294  
     */ 
 295  
    private void fillText(InputNode from) throws Exception {      
 296  1014750
       EventNode event = reader.peek();
 297  
       
 298  1014750
       if(event.isText()) {
 299  1014750
          String data = event.getValue();
 300  
          
 301  1014750
          text.append(data); 
 302  
       }
 303  1014750
    }  
 304  
    
 305  
    /**
 306  
     * This is used to determine if this input node is empty. An
 307  
     * empty node is one with no attributes or children. This can
 308  
     * be used to determine if a given node represents an empty
 309  
     * entity, with which no extra data can be extracted.
 310  
     * 
 311  
     * @param from this is the input node to read the value from
 312  
     * 
 313  
     * @return this returns true if the node is an empty element
 314  
     * 
 315  
     * @throws Exception thrown if there was a parse error
 316  
     */
 317  
    public boolean isEmpty(InputNode from) throws Exception {
 318  256
       if(stack.top() == from) {         
 319  256
          EventNode event = reader.peek();
 320  
 
 321  256
          if(event.isEnd()) {
 322  78
             return true;
 323  
          }
 324  
       }
 325  178
       return false;
 326  
    }
 327  
 
 328  
    /**
 329  
     * This method is used to skip an element within the XML document.
 330  
     * This will simply read each element from the document until
 331  
     * the specified element is at the top of the stack. When the
 332  
     * specified element is at the top of the stack this returns.
 333  
     *
 334  
     * @param from this is the element to skip from the XML document
 335  
     */ 
 336  
    public void skipElement(InputNode from) throws Exception {
 337  28
       while(readElement(from) != null);           
 338  16
    }  
 339  
 }
 340