View Javadoc
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      private static final String[] EMPTY_STRING_ARRAY = new String[0];
52  
53      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      {
84          this.name = name;
85          childList = new ArrayList<Xpp3Dom>();
86          childMap = new HashMap<String, Xpp3Dom>();
87      }
88  
89      /**
90       * Copy constructor.
91       */
92      public Xpp3Dom( Xpp3Dom src )
93      {
94          this( src, src.getName() );
95      }
96  
97      /**
98       * Copy constructor with alternative name.
99       */
100     public Xpp3Dom( Xpp3Dom src, String name )
101     {
102         this.name = name;
103 
104         int childCount = src.getChildCount();
105 
106         childList = new ArrayList<Xpp3Dom>( childCount );
107         childMap = new HashMap<String, Xpp3Dom>( childCount << 1 );
108 
109         setValue( src.getValue() );
110 
111         String[] attributeNames = src.getAttributeNames();
112         for ( String attributeName : attributeNames )
113         {
114             setAttribute( attributeName, src.getAttribute( attributeName ) );
115         }
116 
117         for ( int i = 0; i < childCount; i++ )
118         {
119             addChild( new Xpp3Dom( src.getChild( i ) ) );
120         }
121     }
122 
123     // ----------------------------------------------------------------------
124     // Name handling
125     // ----------------------------------------------------------------------
126 
127     public String getName()
128     {
129         return name;
130     }
131 
132     // ----------------------------------------------------------------------
133     // Value handling
134     // ----------------------------------------------------------------------
135 
136     public String getValue()
137     {
138         return value;
139     }
140 
141     public void setValue( String value )
142     {
143         this.value = value;
144     }
145 
146     // ----------------------------------------------------------------------
147     // Attribute handling
148     // ----------------------------------------------------------------------
149 
150     public String[] getAttributeNames()
151     {
152         if ( null == attributes || attributes.isEmpty() )
153         {
154             return EMPTY_STRING_ARRAY;
155         }
156         else
157         {
158             return (String[]) attributes.keySet().toArray( new String[attributes.size()] );
159         }
160     }
161 
162     public String getAttribute( String name )
163     {
164         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         if ( null == value )
176         {
177             throw new NullPointerException( "Attribute value can not be null" );
178         }
179         if ( null == name )
180         {
181             throw new NullPointerException( "Attribute name can not be null" );
182         }
183         if ( null == attributes )
184         {
185             attributes = new HashMap<String, String>();
186         }
187 
188         attributes.put( name, value );
189     }
190 
191     // ----------------------------------------------------------------------
192     // Child handling
193     // ----------------------------------------------------------------------
194 
195     public Xpp3Dom getChild( int i )
196     {
197         return (Xpp3Dom) childList.get( i );
198     }
199 
200     public Xpp3Dom getChild( String name )
201     {
202         return (Xpp3Dom) childMap.get( name );
203     }
204 
205     public void addChild( Xpp3Dom xpp3Dom )
206     {
207         xpp3Dom.setParent( this );
208         childList.add( xpp3Dom );
209         childMap.put( xpp3Dom.getName(), xpp3Dom );
210     }
211 
212     public Xpp3Dom[] getChildren()
213     {
214         if ( null == childList || childList.isEmpty() )
215         {
216             return EMPTY_DOM_ARRAY;
217         }
218         else
219         {
220             return (Xpp3Dom[]) childList.toArray( new Xpp3Dom[childList.size()] );
221         }
222     }
223 
224     public Xpp3Dom[] getChildren( String name )
225     {
226         if ( null == childList )
227         {
228             return EMPTY_DOM_ARRAY;
229         }
230         else
231         {
232             ArrayList<Xpp3Dom> children = new ArrayList<Xpp3Dom>();
233             int size = childList.size();
234 
235             for ( Xpp3Dom aChildList : childList )
236             {
237                 Xpp3Dom configuration = (Xpp3Dom) aChildList;
238                 if ( name.equals( configuration.getName() ) )
239                 {
240                     children.add( configuration );
241                 }
242             }
243 
244             return (Xpp3Dom[]) children.toArray( new Xpp3Dom[children.size()] );
245         }
246     }
247 
248     public int getChildCount()
249     {
250         if ( null == childList )
251         {
252             return 0;
253         }
254 
255         return childList.size();
256     }
257 
258     public void removeChild( int i )
259     {
260         Xpp3Dom child = getChild( i );
261         childMap.values().remove( child );
262         childList.remove( i );
263         // In case of any dangling references
264         child.setParent( null );
265     }
266 
267     // ----------------------------------------------------------------------
268     // Parent handling
269     // ----------------------------------------------------------------------
270 
271     public Xpp3Dom getParent()
272     {
273         return parent;
274     }
275 
276     public void setParent( Xpp3Dom parent )
277     {
278         this.parent = parent;
279     }
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         SerializerXMLWriter xmlWriter = new SerializerXMLWriter( namespace, serializer );
291         Xpp3DomWriter.write( xmlWriter, this );
292         if ( xmlWriter.getExceptions().size() > 0 )
293         {
294             throw (IOException) xmlWriter.getExceptions().get( 0 );
295         }
296     }
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         if ( recessive == null )
321         {
322             return;
323         }
324 
325         boolean mergeSelf = true;
326 
327         String selfMergeMode = dominant.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
328 
329         if ( SELF_COMBINATION_OVERRIDE.equals( selfMergeMode ) )
330         {
331             mergeSelf = false;
332         }
333 
334         if ( mergeSelf )
335         {
336             if ( isEmpty( dominant.getValue() ) )
337             {
338                 dominant.setValue( recessive.getValue() );
339             }
340 
341             String[] recessiveAttrs = recessive.getAttributeNames();
342             for ( String attr : recessiveAttrs )
343             {
344                 if ( isEmpty( dominant.getAttribute( attr ) ) )
345                 {
346                     dominant.setAttribute( attr, recessive.getAttribute( attr ) );
347                 }
348             }
349 
350             if ( recessive.getChildCount() > 0 )
351             {
352                 boolean mergeChildren = true;
353 
354                 if ( childMergeOverride != null )
355                 {
356                     mergeChildren = childMergeOverride;
357                 }
358                 else
359                 {
360                     String childMergeMode = dominant.getAttribute( CHILDREN_COMBINATION_MODE_ATTRIBUTE );
361 
362                     if ( CHILDREN_COMBINATION_APPEND.equals( childMergeMode ) )
363                     {
364                         mergeChildren = false;
365                     }
366                 }
367 
368                 if ( !mergeChildren )
369                 {
370                     Xpp3Dom[] dominantChildren = dominant.getChildren();
371                     // remove these now, so we can append them to the recessive list later.
372                     dominant.childList.clear();
373 
374                     for ( int i = 0, recessiveChildCount = recessive.getChildCount(); i < recessiveChildCount; i++ )
375                     {
376                         Xpp3Dom recessiveChild = recessive.getChild( i );
377                         dominant.addChild( new Xpp3Dom( recessiveChild ) );
378                     }
379 
380                     // now, re-add these children so they'll be appended to the recessive list.
381                     for ( Xpp3Dom aDominantChildren : dominantChildren )
382                     {
383                         dominant.addChild( aDominantChildren );
384                     }
385                 }
386                 else
387                 {
388                     Map<String, Iterator<Xpp3Dom>> commonChildren = new HashMap<String, Iterator<Xpp3Dom>>();
389 
390                     for ( String childName : recessive.childMap.keySet() )
391                     {
392                         Xpp3Dom[] dominantChildren = dominant.getChildren( childName );
393                         if ( dominantChildren.length > 0 )
394                         {
395                             commonChildren.put( childName, Arrays.asList( dominantChildren ).iterator() );
396                         }
397                     }
398 
399                     for ( int i = 0, recessiveChildCount = recessive.getChildCount(); i < recessiveChildCount; i++ )
400                     {
401                         Xpp3Dom recessiveChild = recessive.getChild( i );
402                         Iterator<Xpp3Dom> it = commonChildren.get( recessiveChild.getName() );
403                         if ( it == null )
404                         {
405                             dominant.addChild( new Xpp3Dom( recessiveChild ) );
406                         }
407                         else if ( it.hasNext() )
408                         {
409                             Xpp3Dom dominantChild = it.next();
410                             mergeIntoXpp3Dom( dominantChild, recessiveChild, childMergeOverride );
411                         }
412                     }
413                 }
414             }
415         }
416     }
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         if ( dominant != null )
431         {
432             mergeIntoXpp3Dom( dominant, recessive, childMergeOverride );
433             return dominant;
434         }
435         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         if ( dominant != null )
450         {
451             mergeIntoXpp3Dom( dominant, recessive, null );
452             return dominant;
453         }
454         return recessive;
455     }
456 
457     // ----------------------------------------------------------------------
458     // Standard object handling
459     // ----------------------------------------------------------------------
460 
461     public boolean equals( Object obj )
462     {
463         if ( obj == this )
464         {
465             return true;
466         }
467 
468         if ( !( obj instanceof Xpp3Dom ) )
469         {
470             return false;
471         }
472 
473         Xpp3Dom dom = (Xpp3Dom) obj;
474 
475         if ( name == null ? dom.name != null : !name.equals( dom.name ) )
476         {
477             return false;
478         }
479         else if ( value == null ? dom.value != null : !value.equals( dom.value ) )
480         {
481             return false;
482         }
483         else if ( attributes == null ? dom.attributes != null : !attributes.equals( dom.attributes ) )
484         {
485             return false;
486         }
487         else if ( childList == null ? dom.childList != null : !childList.equals( dom.childList ) )
488         {
489             return false;
490         }
491         else
492         {
493             return true;
494         }
495     }
496 
497     public int hashCode()
498     {
499         int result = 17;
500         result = 37 * result + ( name != null ? name.hashCode() : 0 );
501         result = 37 * result + ( value != null ? value.hashCode() : 0 );
502         result = 37 * result + ( attributes != null ? attributes.hashCode() : 0 );
503         result = 37 * result + ( childList != null ? childList.hashCode() : 0 );
504         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         StringWriter writer = new StringWriter();
512         XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
513         Xpp3DomWriter.write( xmlWriter, this );
514         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         StringWriter writer = new StringWriter();
522         XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
523         Xpp3DomWriter.write( xmlWriter, this, false );
524         return writer.toString();
525     }
526 
527     public static boolean isNotEmpty( String str )
528     {
529         return ( ( str != null ) && ( str.length() > 0 ) );
530     }
531 
532     public static boolean isEmpty( String str )
533     {
534         return ( ( str == null ) || ( str.trim().length() == 0 ) );
535     }
536 
537 }