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