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