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