Coverage Report - org.codehaus.plexus.util.xml.Xpp3Dom
 
Classes in this File Line Coverage Branch Coverage Complexity
Xpp3Dom
81%
125/154
71%
80/112
3.357
 
 1  
 package org.codehaus.plexus.util.xml;
 2  
 
 3  
 /*
 4  
  * Copyright The Codehaus Foundation.
 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 implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import org.codehaus.plexus.util.xml.pull.XmlSerializer;
 20  
 
 21  
 import java.io.IOException;
 22  
 import java.io.Serializable;
 23  
 import java.io.StringWriter;
 24  
 import java.util.ArrayList;
 25  
 import java.util.Arrays;
 26  
 import java.util.HashMap;
 27  
 import java.util.Iterator;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 
 31  
 /**
 32  
  * @version $Id$ NOTE: remove all the util code in here when separated, this class should be pure data.
 33  
  */
 34  
 public class Xpp3Dom
 35  
     implements Serializable
 36  
 {
 37  
     private static final long serialVersionUID = 2567894443061173996L;
 38  
 
 39  
     protected String name;
 40  
 
 41  
     protected String value;
 42  
 
 43  
     protected Map<String, String> attributes;
 44  
 
 45  
     protected final List<Xpp3Dom> childList;
 46  
 
 47  
     protected final Map<String, Xpp3Dom> childMap;
 48  
 
 49  
     protected Xpp3Dom parent;
 50  
 
 51  1
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 52  
 
 53  1
     private static final Xpp3Dom[] EMPTY_DOM_ARRAY = new Xpp3Dom[0];
 54  
 
 55  
     public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
 56  
 
 57  
     public static final String CHILDREN_COMBINATION_MERGE = "merge";
 58  
 
 59  
     public static final String CHILDREN_COMBINATION_APPEND = "append";
 60  
 
 61  
     /**
 62  
      * This default mode for combining children DOMs during merge means that where element names match, the process will
 63  
      * try to merge the element data, rather than putting the dominant and recessive elements (which share the same
 64  
      * element name) as siblings in the resulting DOM.
 65  
      */
 66  
     public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
 67  
 
 68  
     public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
 69  
 
 70  
     public static final String SELF_COMBINATION_OVERRIDE = "override";
 71  
 
 72  
     public static final String SELF_COMBINATION_MERGE = "merge";
 73  
 
 74  
     /**
 75  
      * This default mode for combining a DOM node during merge means that where element names match, the process will
 76  
      * try to merge the element attributes and values, rather than overriding the recessive element completely with the
 77  
      * dominant one. This means that wherever the dominant element doesn't provide the value or a particular attribute,
 78  
      * that value or attribute will be set from the recessive DOM node.
 79  
      */
 80  
     public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
 81  
 
 82  
     public Xpp3Dom( String name )
 83  142
     {
 84  142
         this.name = name;
 85  142
         childList = new ArrayList<Xpp3Dom>();
 86  142
         childMap = new HashMap<String, Xpp3Dom>();
 87  142
     }
 88  
 
 89  
     /**
 90  
      * Copy constructor.
 91  
      */
 92  
     public Xpp3Dom( Xpp3Dom src )
 93  
     {
 94  7
         this( src, src.getName() );
 95  7
     }
 96  
 
 97  
     /**
 98  
      * Copy constructor with alternative name.
 99  
      */
 100  
     public Xpp3Dom( Xpp3Dom src, String name )
 101  7
     {
 102  7
         this.name = name;
 103  
 
 104  7
         int childCount = src.getChildCount();
 105  
 
 106  7
         childList = new ArrayList<Xpp3Dom>( childCount );
 107  7
         childMap = new HashMap<String, Xpp3Dom>( childCount << 1 );
 108  
 
 109  7
         setValue( src.getValue() );
 110  
 
 111  7
         String[] attributeNames = src.getAttributeNames();
 112  8
         for ( String attributeName : attributeNames )
 113  
         {
 114  1
             setAttribute( attributeName, src.getAttribute( attributeName ) );
 115  
         }
 116  
 
 117  9
         for ( int i = 0; i < childCount; i++ )
 118  
         {
 119  2
             addChild( new Xpp3Dom( src.getChild( i ) ) );
 120  
         }
 121  7
     }
 122  
 
 123  
     // ----------------------------------------------------------------------
 124  
     // Name handling
 125  
     // ----------------------------------------------------------------------
 126  
 
 127  
     public String getName()
 128  
     {
 129  190
         return name;
 130  
     }
 131  
 
 132  
     // ----------------------------------------------------------------------
 133  
     // Value handling
 134  
     // ----------------------------------------------------------------------
 135  
 
 136  
     public String getValue()
 137  
     {
 138  92
         return value;
 139  
     }
 140  
 
 141  
     public void setValue( String value )
 142  
     {
 143  102
         this.value = value;
 144  102
     }
 145  
 
 146  
     // ----------------------------------------------------------------------
 147  
     // Attribute handling
 148  
     // ----------------------------------------------------------------------
 149  
 
 150  
     public String[] getAttributeNames()
 151  
     {
 152  54
         if ( null == attributes || attributes.isEmpty() )
 153  
         {
 154  40
             return EMPTY_STRING_ARRAY;
 155  
         }
 156  
         else
 157  
         {
 158  14
             return (String[]) attributes.keySet().toArray( new String[attributes.size()] );
 159  
         }
 160  
     }
 161  
 
 162  
     public String getAttribute( String name )
 163  
     {
 164  52
         return ( null != attributes ) ? (String) attributes.get( name ) : null;
 165  
     }
 166  
 
 167  
     /**
 168  
      * Set the attribute value
 169  
      * 
 170  
      * @param name String not null
 171  
      * @param value String not null
 172  
      */
 173  
     public void setAttribute( String name, String value )
 174  
     {
 175  50
         if ( null == value )
 176  
         {
 177  1
             throw new NullPointerException( "Attribute value can not be null" );
 178  
         }
 179  49
         if ( null == name )
 180  
         {
 181  1
             throw new NullPointerException( "Attribute name can not be null" );
 182  
         }
 183  48
         if ( null == attributes )
 184  
         {
 185  44
             attributes = new HashMap<String, String>();
 186  
         }
 187  
 
 188  48
         attributes.put( name, value );
 189  48
     }
 190  
 
 191  
     // ----------------------------------------------------------------------
 192  
     // Child handling
 193  
     // ----------------------------------------------------------------------
 194  
 
 195  
     public Xpp3Dom getChild( int i )
 196  
     {
 197  19
         return (Xpp3Dom) childList.get( i );
 198  
     }
 199  
 
 200  
     public Xpp3Dom getChild( String name )
 201  
     {
 202  22
         return (Xpp3Dom) childMap.get( name );
 203  
     }
 204  
 
 205  
     public void addChild( Xpp3Dom xpp3Dom )
 206  
     {
 207  114
         xpp3Dom.setParent( this );
 208  114
         childList.add( xpp3Dom );
 209  114
         childMap.put( xpp3Dom.getName(), xpp3Dom );
 210  114
     }
 211  
 
 212  
     public Xpp3Dom[] getChildren()
 213  
     {
 214  33
         if ( null == childList || childList.isEmpty() )
 215  
         {
 216  18
             return EMPTY_DOM_ARRAY;
 217  
         }
 218  
         else
 219  
         {
 220  15
             return (Xpp3Dom[]) childList.toArray( new Xpp3Dom[childList.size()] );
 221  
         }
 222  
     }
 223  
 
 224  
     public Xpp3Dom[] getChildren( String name )
 225  
     {
 226  16
         if ( null == childList )
 227  
         {
 228  0
             return EMPTY_DOM_ARRAY;
 229  
         }
 230  
         else
 231  
         {
 232  16
             ArrayList<Xpp3Dom> children = new ArrayList<Xpp3Dom>();
 233  16
             int size = childList.size();
 234  
 
 235  16
             for ( Xpp3Dom aChildList : childList )
 236  
             {
 237  33
                 Xpp3Dom configuration = (Xpp3Dom) aChildList;
 238  33
                 if ( name.equals( configuration.getName() ) )
 239  
                 {
 240  32
                     children.add( configuration );
 241  
                 }
 242  33
             }
 243  
 
 244  16
             return (Xpp3Dom[]) children.toArray( new Xpp3Dom[children.size()] );
 245  
         }
 246  
     }
 247  
 
 248  
     public int getChildCount()
 249  
     {
 250  130
         if ( null == childList )
 251  
         {
 252  0
             return 0;
 253  
         }
 254  
 
 255  130
         return childList.size();
 256  
     }
 257  
 
 258  
     public void removeChild( int i )
 259  
     {
 260  0
         Xpp3Dom child = getChild( i );
 261  0
         childMap.values().remove( child );
 262  0
         childList.remove( i );
 263  
         // In case of any dangling references
 264  0
         child.setParent( null );
 265  0
     }
 266  
 
 267  
     // ----------------------------------------------------------------------
 268  
     // Parent handling
 269  
     // ----------------------------------------------------------------------
 270  
 
 271  
     public Xpp3Dom getParent()
 272  
     {
 273  0
         return parent;
 274  
     }
 275  
 
 276  
     public void setParent( Xpp3Dom parent )
 277  
     {
 278  114
         this.parent = parent;
 279  114
     }
 280  
 
 281  
     // ----------------------------------------------------------------------
 282  
     // Helpers
 283  
     // ----------------------------------------------------------------------
 284  
 
 285  
     public void writeToSerializer( String namespace, XmlSerializer serializer )
 286  
         throws IOException
 287  
     {
 288  
         // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new
 289  
         // document - not the desired behaviour!
 290  0
         SerializerXMLWriter xmlWriter = new SerializerXMLWriter( namespace, serializer );
 291  0
         Xpp3DomWriter.write( xmlWriter, this );
 292  0
         if ( xmlWriter.getExceptions().size() > 0 )
 293  
         {
 294  0
             throw (IOException) xmlWriter.getExceptions().get( 0 );
 295  
         }
 296  0
     }
 297  
 
 298  
     /**
 299  
      * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm. The
 300  
      * algorithm is as follows: 1. if the recessive DOM is null, there is nothing to do...return. 2. Determine whether
 301  
      * the dominant node will suppress the recessive one (flag=mergeSelf). A. retrieve the 'combine.self' attribute on
 302  
      * the dominant node, and try to match against 'override'... if it matches 'override', then set mergeSelf ==
 303  
      * false...the dominant node suppresses the recessive one completely. B. otherwise, use the default value for
 304  
      * mergeSelf, which is true...this is the same as specifying 'combine.self' == 'merge' as an attribute of the
 305  
      * dominant root node. 3. If mergeSelf == true A. if the dominant root node's value is empty, set it to the
 306  
      * recessive root node's value B. For each attribute in the recessive root node which is not set in the dominant
 307  
      * root node, set it. C. Determine whether children from the recessive DOM will be merged or appended to the
 308  
      * dominant DOM as siblings (flag=mergeChildren). i. if childMergeOverride is set (non-null), use that value
 309  
      * (true/false) ii. retrieve the 'combine.children' attribute on the dominant node, and try to match against
 310  
      * 'append'...if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
 311  
      * siblings of the dominant children. iii. otherwise, use the default value for mergeChildren, which is true...this
 312  
      * is the same as specifying 'combine.children' == 'merge' as an attribute on the dominant root node. D. Iterate
 313  
      * through the recessive children, and: i. if mergeChildren == true and there is a corresponding dominant child
 314  
      * (matched by element name), merge the two. ii. otherwise, add the recessive child as a new child on the dominant
 315  
      * root node.
 316  
      */
 317  
     private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
 318  
     {
 319  
         // TODO: share this as some sort of assembler, implement a walk interface?
 320  17
         if ( recessive == null )
 321  
         {
 322  0
             return;
 323  
         }
 324  
 
 325  17
         boolean mergeSelf = true;
 326  
 
 327  17
         String selfMergeMode = dominant.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
 328  
 
 329  17
         if ( SELF_COMBINATION_OVERRIDE.equals( selfMergeMode ) )
 330  
         {
 331  1
             mergeSelf = false;
 332  
         }
 333  
 
 334  17
         if ( mergeSelf )
 335  
         {
 336  16
             if ( isEmpty( dominant.getValue() ) )
 337  
             {
 338  13
                 dominant.setValue( recessive.getValue() );
 339  
             }
 340  
 
 341  16
             String[] recessiveAttrs = recessive.getAttributeNames();
 342  18
             for ( String attr : recessiveAttrs )
 343  
             {
 344  2
                 if ( isEmpty( dominant.getAttribute( attr ) ) )
 345  
                 {
 346  2
                     dominant.setAttribute( attr, recessive.getAttribute( attr ) );
 347  
                 }
 348  
             }
 349  
 
 350  16
             if ( recessive.getChildCount() > 0 )
 351  
             {
 352  9
                 boolean mergeChildren = true;
 353  
 
 354  9
                 if ( childMergeOverride != null )
 355  
                 {
 356  1
                     mergeChildren = childMergeOverride;
 357  
                 }
 358  
                 else
 359  
                 {
 360  8
                     String childMergeMode = dominant.getAttribute( CHILDREN_COMBINATION_MODE_ATTRIBUTE );
 361  
 
 362  8
                     if ( CHILDREN_COMBINATION_APPEND.equals( childMergeMode ) )
 363  
                     {
 364  2
                         mergeChildren = false;
 365  
                     }
 366  
                 }
 367  
 
 368  9
                 if ( !mergeChildren )
 369  
                 {
 370  2
                     Xpp3Dom[] dominantChildren = dominant.getChildren();
 371  
                     // remove these now, so we can append them to the recessive list later.
 372  2
                     dominant.childList.clear();
 373  
 
 374  5
                     for ( int i = 0, recessiveChildCount = recessive.getChildCount(); i < recessiveChildCount; i++ )
 375  
                     {
 376  3
                         Xpp3Dom recessiveChild = recessive.getChild( i );
 377  3
                         dominant.addChild( new Xpp3Dom( recessiveChild ) );
 378  
                     }
 379  
 
 380  
                     // now, re-add these children so they'll be appended to the recessive list.
 381  4
                     for ( Xpp3Dom aDominantChildren : dominantChildren )
 382  
                     {
 383  2
                         dominant.addChild( aDominantChildren );
 384  
                     }
 385  2
                 }
 386  
                 else
 387  
                 {
 388  7
                     Map<String, Iterator<Xpp3Dom>> commonChildren = new HashMap<String, Iterator<Xpp3Dom>>();
 389  
 
 390  7
                     for ( String childName : recessive.childMap.keySet() )
 391  
                     {
 392  7
                         Xpp3Dom[] dominantChildren = dominant.getChildren( childName );
 393  7
                         if ( dominantChildren.length > 0 )
 394  
                         {
 395  6
                             commonChildren.put( childName, Arrays.asList( dominantChildren ).iterator() );
 396  
                         }
 397  7
                     }
 398  
 
 399  17
                     for ( int i = 0, recessiveChildCount = recessive.getChildCount(); i < recessiveChildCount; i++ )
 400  
                     {
 401  10
                         Xpp3Dom recessiveChild = recessive.getChild( i );
 402  10
                         Iterator<Xpp3Dom> it = commonChildren.get( recessiveChild.getName() );
 403  10
                         if ( it == null )
 404  
                         {
 405  1
                             dominant.addChild( new Xpp3Dom( recessiveChild ) );
 406  
                         }
 407  9
                         else if ( it.hasNext() )
 408  
                         {
 409  8
                             Xpp3Dom dominantChild = it.next();
 410  8
                             mergeIntoXpp3Dom( dominantChild, recessiveChild, childMergeOverride );
 411  
                         }
 412  
                     }
 413  
                 }
 414  
             }
 415  
         }
 416  17
     }
 417  
 
 418  
     /**
 419  
      * Merge two DOMs, with one having dominance in the case of collision.
 420  
      *
 421  
      * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
 422  
      * @see #SELF_COMBINATION_MODE_ATTRIBUTE
 423  
      * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
 424  
      * @param recessive The recessive DOM, which will be merged into the dominant DOM
 425  
      * @param childMergeOverride Overrides attribute flags to force merging or appending of child elements into the
 426  
      *            dominant DOM
 427  
      */
 428  
     public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
 429  
     {
 430  1
         if ( dominant != null )
 431  
         {
 432  1
             mergeIntoXpp3Dom( dominant, recessive, childMergeOverride );
 433  1
             return dominant;
 434  
         }
 435  0
         return recessive;
 436  
     }
 437  
 
 438  
     /**
 439  
      * Merge two DOMs, with one having dominance in the case of collision. Merge mechanisms (vs. override for nodes, or
 440  
      * vs. append for children) is determined by attributes of the dominant root node.
 441  
      *
 442  
      * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
 443  
      * @see #SELF_COMBINATION_MODE_ATTRIBUTE
 444  
      * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
 445  
      * @param recessive The recessive DOM, which will be merged into the dominant DOM
 446  
      */
 447  
     public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
 448  
     {
 449  8
         if ( dominant != null )
 450  
         {
 451  8
             mergeIntoXpp3Dom( dominant, recessive, null );
 452  8
             return dominant;
 453  
         }
 454  0
         return recessive;
 455  
     }
 456  
 
 457  
     // ----------------------------------------------------------------------
 458  
     // Standard object handling
 459  
     // ----------------------------------------------------------------------
 460  
 
 461  
     public boolean equals( Object obj )
 462  
     {
 463  19
         if ( obj == this )
 464  
         {
 465  1
             return true;
 466  
         }
 467  
 
 468  18
         if ( !( obj instanceof Xpp3Dom ) )
 469  
         {
 470  1
             return false;
 471  
         }
 472  
 
 473  17
         Xpp3Dom dom = (Xpp3Dom) obj;
 474  
 
 475  17
         if ( name == null ? dom.name != null : !name.equals( dom.name ) )
 476  
         {
 477  1
             return false;
 478  
         }
 479  16
         else if ( value == null ? dom.value != null : !value.equals( dom.value ) )
 480  
         {
 481  0
             return false;
 482  
         }
 483  16
         else if ( attributes == null ? dom.attributes != null : !attributes.equals( dom.attributes ) )
 484  
         {
 485  2
             return false;
 486  
         }
 487  14
         else if ( childList == null ? dom.childList != null : !childList.equals( dom.childList ) )
 488  
         {
 489  0
             return false;
 490  
         }
 491  
         else
 492  
         {
 493  14
             return true;
 494  
         }
 495  
     }
 496  
 
 497  
     public int hashCode()
 498  
     {
 499  0
         int result = 17;
 500  0
         result = 37 * result + ( name != null ? name.hashCode() : 0 );
 501  0
         result = 37 * result + ( value != null ? value.hashCode() : 0 );
 502  0
         result = 37 * result + ( attributes != null ? attributes.hashCode() : 0 );
 503  0
         result = 37 * result + ( childList != null ? childList.hashCode() : 0 );
 504  0
         return result;
 505  
     }
 506  
 
 507  
     public String toString()
 508  
     {
 509  
         // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new
 510  
         // document - not the desired behaviour!
 511  2
         StringWriter writer = new StringWriter();
 512  2
         XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
 513  2
         Xpp3DomWriter.write( xmlWriter, this );
 514  2
         return writer.toString();
 515  
     }
 516  
 
 517  
     public String toUnescapedString()
 518  
     {
 519  
         // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new
 520  
         // document - not the desired behaviour!
 521  0
         StringWriter writer = new StringWriter();
 522  0
         XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
 523  0
         Xpp3DomWriter.write( xmlWriter, this, false );
 524  0
         return writer.toString();
 525  
     }
 526  
 
 527  
     public static boolean isNotEmpty( String str )
 528  
     {
 529  0
         return ( ( str != null ) && ( str.length() > 0 ) );
 530  
     }
 531  
 
 532  
     public static boolean isEmpty( String str )
 533  
     {
 534  18
         return ( ( str == null ) || ( str.trim().length() == 0 ) );
 535  
     }
 536  
 
 537  
 }