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  
23  /** @author Jason van Zyl */
24  public class Xpp3DomUtils
25  {
26      public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
27  
28      public static final String CHILDREN_COMBINATION_MERGE = "merge";
29  
30      public static final String CHILDREN_COMBINATION_APPEND = "append";
31  
32      /**
33       * This default mode for combining children DOMs during merge means that where element names match, the process will
34       * try to merge the element data, rather than putting the dominant and recessive elements (which share the same
35       * element name) as siblings in the resulting DOM.
36       */
37      public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
38  
39      public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
40  
41      public static final String SELF_COMBINATION_OVERRIDE = "override";
42  
43      public static final String SELF_COMBINATION_MERGE = "merge";
44  
45      /**
46       * In case of complex XML structures, combining can be done based on id.
47       * 
48       * @since 3.0.22
49       */
50      public static final String ID_COMBINATION_MODE_ATTRIBUTE = "combine.id";
51  
52      /**
53       * This default mode for combining a DOM node during merge means that where element names match, the process will
54       * try to merge the element attributes and values, rather than overriding the recessive element completely with the
55       * dominant one. This means that wherever the dominant element doesn't provide the value or a particular attribute,
56       * that value or attribute will be set from the recessive DOM node.
57       */
58      public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
59  
60      public void writeToSerializer( String namespace, XmlSerializer serializer, Xpp3Dom dom )
61          throws IOException
62      {
63          // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new
64          // document - not the desired behaviour!
65          SerializerXMLWriter xmlWriter = new SerializerXMLWriter( namespace, serializer );
66          Xpp3DomWriter.write( xmlWriter, dom );
67          if ( xmlWriter.getExceptions().size() > 0 )
68          {
69              throw (IOException) xmlWriter.getExceptions().get( 0 );
70          }
71      }
72  
73      /**
74       * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm. The
75       * algorithm is as follows: 1. if the recessive DOM is null, there is nothing to do...return. 2. Determine whether
76       * the dominant node will suppress the recessive one (flag=mergeSelf). A. retrieve the 'combine.self' attribute on
77       * the dominant node, and try to match against 'override'... if it matches 'override', then set mergeSelf ==
78       * false...the dominant node suppresses the recessive one completely. B. otherwise, use the default value for
79       * mergeSelf, which is true...this is the same as specifying 'combine.self' == 'merge' as an attribute of the
80       * dominant root node. 3. If mergeSelf == true A. if the dominant root node's value is empty, set it to the
81       * recessive root node's value B. For each attribute in the recessive root node which is not set in the dominant
82       * root node, set it. C. Determine whether children from the recessive DOM will be merged or appended to the
83       * dominant DOM as siblings (flag=mergeChildren). i. if childMergeOverride is set (non-null), use that value
84       * (true/false) ii. retrieve the 'combine.children' attribute on the dominant node, and try to match against
85       * 'append'...if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
86       * siblings of the dominant children. iii. otherwise, use the default value for mergeChildren, which is true...this
87       * is the same as specifying 'combine.children' == 'merge' as an attribute on the dominant root node. D. Iterate
88       * through the recessive children, and: i. if 'combine.id' is set and there is a corresponding dominant child
89       * (matched by value of 'combine.id'), merge the two. ii. if mergeChildren == true and there is a corresponding
90       * dominant child (matched by element name), merge the two. iii. otherwise, add the recessive child as a new child
91       * on the dominant root node.
92       */
93      private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
94      {
95          // TODO: share this as some sort of assembler, implement a walk interface?
96          if ( recessive == null )
97          {
98              return;
99          }
100 
101         boolean mergeSelf = true;
102 
103         String selfMergeMode = dominant.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
104 
105         if ( isNotEmpty( selfMergeMode ) && SELF_COMBINATION_OVERRIDE.equals( selfMergeMode ) )
106         {
107             mergeSelf = false;
108         }
109 
110         if ( mergeSelf )
111         {
112             if ( isEmpty( dominant.getValue() ) )
113             {
114                 dominant.setValue( recessive.getValue() );
115             }
116 
117             String[] recessiveAttrs = recessive.getAttributeNames();
118             for ( String attr : recessiveAttrs )
119             {
120                 if ( isEmpty( dominant.getAttribute( attr ) ) )
121                 {
122                     dominant.setAttribute( attr, recessive.getAttribute( attr ) );
123                 }
124             }
125 
126             boolean mergeChildren = true;
127 
128             if ( childMergeOverride != null )
129             {
130                 mergeChildren = childMergeOverride;
131             }
132             else
133             {
134                 String childMergeMode = dominant.getAttribute( CHILDREN_COMBINATION_MODE_ATTRIBUTE );
135 
136                 if ( isNotEmpty( childMergeMode ) && CHILDREN_COMBINATION_APPEND.equals( childMergeMode ) )
137                 {
138                     mergeChildren = false;
139                 }
140             }
141 
142             Xpp3Dom[] children = recessive.getChildren();
143             for ( Xpp3Dom recessiveChild : children )
144             {
145                 String idValue = recessiveChild.getAttribute( ID_COMBINATION_MODE_ATTRIBUTE );
146 
147                 Xpp3Dom childDom = null;
148                 if ( isNotEmpty( idValue ) )
149                 {
150                     for ( Xpp3Dom dominantChild : dominant.getChildren() )
151                     {
152                         if ( idValue.equals( dominantChild.getAttribute( ID_COMBINATION_MODE_ATTRIBUTE ) ) )
153                         {
154                             childDom = dominantChild;
155                             // we have a match, so don't append but merge
156                             mergeChildren = true;
157                         }
158                     }
159                 }
160                 else
161                 {
162                     childDom = dominant.getChild( recessiveChild.getName() );
163                 }
164 
165                 if ( mergeChildren && childDom != null )
166                 {
167                     mergeIntoXpp3Dom( childDom, recessiveChild, childMergeOverride );
168                 }
169                 else
170                 {
171                     dominant.addChild( new Xpp3Dom( recessiveChild ) );
172                 }
173             }
174         }
175     }
176 
177     /**
178      * Merge two DOMs, with one having dominance in the case of collision.
179      *
180      * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
181      * @see #SELF_COMBINATION_MODE_ATTRIBUTE
182      * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
183      * @param recessive The recessive DOM, which will be merged into the dominant DOM
184      * @param childMergeOverride Overrides attribute flags to force merging or appending of child elements into the
185      *            dominant DOM
186      */
187     public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
188     {
189         if ( dominant != null )
190         {
191             mergeIntoXpp3Dom( dominant, recessive, childMergeOverride );
192             return dominant;
193         }
194         return recessive;
195     }
196 
197     /**
198      * Merge two DOMs, with one having dominance in the case of collision. Merge mechanisms (vs. override for nodes, or
199      * vs. append for children) is determined by attributes of the dominant root node.
200      *
201      * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
202      * @see #SELF_COMBINATION_MODE_ATTRIBUTE
203      * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
204      * @param recessive The recessive DOM, which will be merged into the dominant DOM
205      */
206     public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
207     {
208         if ( dominant != null )
209         {
210             mergeIntoXpp3Dom( dominant, recessive, null );
211             return dominant;
212         }
213         return recessive;
214     }
215 
216     public static boolean isNotEmpty( String str )
217     {
218         return ( str != null && str.length() > 0 );
219     }
220 
221     public static boolean isEmpty( String str )
222     {
223         return ( str == null || str.trim().length() == 0 );
224     }
225 }