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