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