Coverage Report - org.simpleframework.xml.stream.NodeWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
NodeWriter
95%
89/93
84%
37/44
2.412
 
 1  
 /*
 2  
  * NodeWriter.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  
 import java.util.HashSet;
 22  
 import java.util.Set;
 23  
 import java.io.Writer;
 24  
 
 25  
 /**
 26  
  * The <code>NodeWriter</code> object is used to create a writer that
 27  
  * will write well formed indented XML for a given output node. This
 28  
  * is used in the serialization process to convert an object into an
 29  
  * XML document.
 30  
  * <p>
 31  
  * This keeps a stack of all the active output nodes so that if an
 32  
  * output node has been committed it cannot write any further data to
 33  
  * the XML document. This allows all output nodes to be independent
 34  
  * of each other as the node write organizes the write access.
 35  
  *
 36  
  * @author Niall Gallagher
 37  
  */  
 38  
 class NodeWriter {
 39  
 
 40  
    /**
 41  
     * Represents the stack of output nodes that are not yet ended.
 42  
     */          
 43  
    private final OutputStack stack;
 44  
    
 45  
    /**
 46  
     * Formatter used to indent the XML elements and escape text.
 47  
     */ 
 48  
    private final Formatter writer;
 49  
    
 50  
    /**
 51  
     * Contains the set of as yet uncommitted elements blocks.
 52  
     */ 
 53  
    private final Set active;
 54  
    
 55  
    /**
 56  
     * This determines if we expand the namespace prefixes.
 57  
     */
 58  
    private final boolean verbose;
 59  
 
 60  
    /**
 61  
     * Constructor for the <code>NodeWriter</code> object. This will
 62  
     * create the object that is used to control an output elements
 63  
     * access to the generated XML document. This keeps a stack of
 64  
     * active and uncommitted elements.
 65  
     *
 66  
     * @param result this is the output for the resulting document
 67  
     */ 
 68  
    public NodeWriter(Writer result) {
 69  0
       this(result, new Format());
 70  0
    }  
 71  
    
 72  
    /**
 73  
     * Constructor for the <code>NodeWriter</code> object. This will
 74  
     * create the object that is used to control an output elements
 75  
     * access to the generated XML document. This keeps a stack of
 76  
     * active and uncommitted elements.
 77  
     *
 78  
     * @param result this is the output for the resulting document
 79  
     * @param format this is used to format the generated document
 80  
     */ 
 81  
    public NodeWriter(Writer result, Format format) {
 82  24420
       this(result, format, false);
 83  24420
    }  
 84  
    
 85  
    /**
 86  
     * Constructor for the <code>NodeWriter</code> object. This will
 87  
     * create the object that is used to control an output elements
 88  
     * access to the generated XML document. This keeps a stack of
 89  
     * active and uncommitted elements.
 90  
     *
 91  
     * @param result this is the output for the resulting document
 92  
     * @param format this is used to format the generated document
 93  
     * @param verbose this determines if we expand the namespaces
 94  
     */ 
 95  24420
    private NodeWriter(Writer result, Format format, boolean verbose) {
 96  24420
       this.writer = new Formatter(result, format);
 97  24420
       this.active = new HashSet();
 98  24420
       this.stack = new OutputStack(active);    
 99  24420
       this.verbose = verbose;
 100  24420
    }  
 101  
    /**
 102  
     * This is used to acquire the root output node for the document.
 103  
     * This will create an empty node that can be used to generate
 104  
     * the root document element as a child to the document.
 105  
     * <p>
 106  
     * Depending on whether or not an encoding has been specified 
 107  
     * this method will write a prolog to the generated XML document.
 108  
     * Each prolog written uses an XML version of "1.0".
 109  
     *
 110  
     * @return this returns an output element for the document
 111  
     */ 
 112  
    public OutputNode writeRoot() throws Exception {
 113  24420
       OutputDocument root = new OutputDocument(this, stack);
 114  
 
 115  24420
       if(stack.isEmpty()) {
 116  24420
          writer.writeProlog();              
 117  
       }
 118  24420
       return root;
 119  
    }
 120  
    
 121  
    /**
 122  
     * This method is used to determine if the node is the root 
 123  
     * node for the XML document. The root node is the first node
 124  
     * in the document and has no sibling nodes. This is false
 125  
     * if the node has a parent node or a sibling node.
 126  
     *
 127  
     * @param node this is the node that is check as the root 
 128  
     *
 129  
     * @return true if the node is the root node for the document
 130  
     */
 131  
    public boolean isRoot(OutputNode node) {
 132  26290
       return stack.bottom() == node;        
 133  
    }
 134  
    
 135  
    /**
 136  
     * This is used to determine if the specified node has been 
 137  
     * committed. If this returns tre then the node is committed
 138  
     * and cannot be used to add further child elements.
 139  
     *
 140  
     * @param node this is the node to check for commit status
 141  
     * 
 142  
     * @return this returns true if the node has been committed
 143  
     */ 
 144  
    public boolean isCommitted(OutputNode node) {
 145  677673
       return !active.contains(node);
 146  
    }
 147  
  
 148  
    /**
 149  
     * This method is used to commit all nodes on the stack up to and
 150  
     * including the specified node. This will effectively create end 
 151  
     * tags for any nodes that are currently open up to the specified
 152  
     * element. Once committed the output node can no longer be used
 153  
     * to create child elements, nor can any of its child elements.
 154  
     *
 155  
     * @param parent this is the node that is to be committed
 156  
     */ 
 157  
    public void commit(OutputNode parent) throws Exception {
 158  139168
       if(stack.contains(parent)) {
 159  139168
          OutputNode top = stack.top();
 160  
          
 161  139168
          if(!isCommitted(top)) {
 162  118467
             writeStart(top);
 163  
          }         
 164  275286
          while(stack.top() != parent) {
 165  136118
             writeEnd(stack.pop());
 166  
          }
 167  139168
          writeEnd(parent);
 168  139168
          stack.pop();
 169  
       }
 170  139168
    } 
 171  
    
 172  
    /**
 173  
     * This method is used to remove the output node from the output
 174  
     * buffer if that node has not yet been committed. This allows a
 175  
     * node that has been created to be deleted, ensuring that it
 176  
     * will not affect the resulting XML document structure.
 177  
     * 
 178  
     * @param node this is the output node that is to be removed    
 179  
     */
 180  
    public void remove(OutputNode node) throws Exception {
 181  418
       if(stack.top() != node) {
 182  0
          throw new NodeException("Cannot remove node");
 183  
       }      
 184  418
       stack.pop();
 185  418
    }
 186  
    
 187  
    /**
 188  
     * This is used to create a new element under the specified node.
 189  
     * This will effectively commit all nodes that are open until this
 190  
     * node is encountered. Once the specified node is encountered on
 191  
     * the stack a new element is created with the specified name.
 192  
     *
 193  
     * @param parent this is the node that is to be committed
 194  
     * @param name this is the name of the start element to create
 195  
     *
 196  
     * @return this will return a child node for the given parent
 197  
     */ 
 198  
    public OutputNode writeElement(OutputNode parent, String name) throws Exception {
 199  562122
       if(stack.isEmpty()) {
 200  24418
          return writeStart(parent, name);       
 201  
       }
 202  537704
       if(stack.contains(parent)) {
 203  537698
          OutputNode top = stack.top();
 204  
       
 205  537698
          if(!isCommitted(top)) {
 206  443224
             writeStart(top);
 207  
          }
 208  824097
          while(stack.top() != parent) {
 209  286399
             writeEnd(stack.pop());
 210  
          }       
 211  537698
          if(!stack.isEmpty()) {
 212  537698
             writeValue(parent);
 213  
          }
 214  537698
          return writeStart(parent, name);
 215  
       }
 216  6
       return null;
 217  
    }
 218  
 
 219  
     /**
 220  
      * This is used to begin writing on a new XML element. This is
 221  
      * typically done by writing any comments required. This will 
 222  
      * create an output node of the specified name before writing 
 223  
      * the comment, if any exists. Once the comment has been written
 224  
      * the node is pushed on to the head of the output node stack.
 225  
      *
 226  
      * @param parent this is the parent node to the next output node
 227  
      * @param name this is the name of the node that is to be created
 228  
      *
 229  
      * @return this returns an output node used for writing content
 230  
      */       
 231  
    private OutputNode writeStart(OutputNode parent, String name) throws Exception {
 232  562116
       OutputNode node = new OutputElement(parent, this, name);
 233  
 
 234  562116
       if(name == null) {
 235  0
          throw new NodeException("Can not have a null name");
 236  
       }          
 237  562116
       return stack.push(node);
 238  
    }
 239  
   
 240  
    /**
 241  
     * This is used to write the XML element to the underlying buffer.
 242  
     * The element is written in the order of element prefix and name
 243  
     * followed by the attributes an finally the namespaces for the
 244  
     * element. Once this is finished the element is committed to 
 245  
     *
 246  
     * @param node this is the node that is to be fully written
 247  
     */ 
 248  
    private void writeStart(OutputNode node) throws Exception {
 249  561691
       writeComment(node);
 250  561691
       writeName(node);
 251  561691
       writeAttributes(node);
 252  561691
       writeNamespaces(node);
 253  561691
    }
 254  
    
 255  
    /**
 256  
     * This is used to write a comment to the document. Comments
 257  
     * appear just before the element name, this allows an logical
 258  
     * association between the comment and the node to be made.
 259  
     *
 260  
     * @param node this is the node that is to have its name written
 261  
     */   
 262  
    private void writeComment(OutputNode node) throws Exception {
 263  561691
       String comment = node.getComment();
 264  
       
 265  561691
       if(comment != null) {
 266  25450
          writer.writeComment(comment);
 267  
       }
 268  561691
    }
 269  
 
 270  
    /**
 271  
     * This is used to write a new start element to the resulting XML
 272  
     * document. This will create an output node of the specified
 273  
     * name before writing the start tag. Once the tag is written 
 274  
     * the node is pushed on to the head of the output node stack.
 275  
     *
 276  
     * @param node this is the node that is to have its name written
 277  
     */   
 278  
    private void writeName(OutputNode node) throws Exception {
 279  561691
       String prefix = node.getPrefix(verbose);
 280  561691
       String name = node.getName();
 281  
       
 282  561691
       if(name != null) {
 283  561691
          writer.writeStart(name, prefix);
 284  
       }
 285  561691
    }
 286  
  
 287  
    /**
 288  
     * This is used to write an element value to the resulting XML
 289  
     * document. This will search the nodes parents for the write 
 290  
     * mode, if the mode is CDATA then that is what is used to write
 291  
     * the data, otherwise the value is written as plain text. 
 292  
     * <p>
 293  
     * One side effect of this method is that it clears the value
 294  
     * of the output node once it has been written to the XML. This
 295  
     * is needed, it can however cause confusion within the API.
 296  
     *
 297  
     * @param node this is the node to write the value of
 298  
     */  
 299  
    private void writeValue(OutputNode node) throws Exception {
 300  854698
       Mode mode = node.getMode();
 301  854698
       String value = node.getValue();
 302  
   
 303  854698
       if(value != null) {
 304  317059
          for(OutputNode next : stack) {         
 305  319066
             if(mode != Mode.INHERIT) {
 306  316945
                break; 
 307  
             }
 308  2121
             mode = next.getMode();
 309  
          }
 310  317059
          writer.writeText(value, mode);
 311  
       }
 312  854698
       node.setValue(null);
 313  854698
    }
 314  
    
 315  
    /**
 316  
     * This is used to write a new end element to the resulting XML
 317  
     * document. This will acquire the name and value of the given
 318  
     * node, if the node has a value that is written. Finally a new
 319  
     * end tag is written to the document and the output is flushed.
 320  
     *
 321  
     * @param node this is the node that is to have an end tag
 322  
     */  
 323  
    private void writeEnd(OutputNode node) throws Exception {
 324  561685
       String name = node.getName();
 325  561685
       String prefix = node.getPrefix(verbose);
 326  561685
       String value = node.getValue();
 327  
       
 328  561685
       if(value != null) {
 329  317000
          writeValue(node);
 330  
       }
 331  561685
       if(name != null) {
 332  561685
          writer.writeEnd(name, prefix);
 333  561685
          writer.flush();
 334  
       }
 335  561685
    }
 336  
    
 337  
    /**
 338  
     * This is used to write the attributes of the specified node to
 339  
     * the output. This will iterate over each node entered on to
 340  
     * the node. Once written the node is considered inactive.
 341  
     *
 342  
     * @param node this is the node to have is attributes written
 343  
     */ 
 344  
    private void writeAttributes(OutputNode node) throws Exception {
 345  561691
       NodeMap<OutputNode> map = node.getAttributes();
 346  
       
 347  561691
       for(String name : map) {
 348  271196
          OutputNode entry = map.get(name);
 349  271196
          String value = entry.getValue();
 350  271196
          String prefix = entry.getPrefix(verbose);
 351  
          
 352  271196
          writer.writeAttribute(name, value, prefix);
 353  271196
       }
 354  561691
       active.remove(node);
 355  561691
    }
 356  
    
 357  
    /**
 358  
     * This is used to write the namespaces of the specified node to
 359  
     * the output. This will iterate over each namespace entered on 
 360  
     * to the node. Once written the node is considered qualified.
 361  
     *
 362  
     * @param node this is the node to have is attributes written
 363  
     */ 
 364  
    private void writeNamespaces(OutputNode node) throws Exception {
 365  561691
       NamespaceMap map = node.getNamespaces();
 366  
       
 367  561691
       for(String name : map) {
 368  308
          String prefix = map.getPrefix(name);
 369  
          
 370  308
          writer.writeNamespace(name, prefix);
 371  308
       }
 372  561691
    }
 373  
 }
 374