View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.xml;
20  
21  import javax.xml.stream.XMLStreamException;
22  import javax.xml.stream.XMLStreamReader;
23  
24  import java.io.IOException;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.stream.Collectors;
32  
33  import org.apache.maven.api.xml.XmlNode;
34  import org.codehaus.plexus.util.xml.Xpp3Dom;
35  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
36  import org.junit.jupiter.api.Test;
37  
38  import static org.junit.jupiter.api.Assertions.assertEquals;
39  import static org.junit.jupiter.api.Assertions.assertNotEquals;
40  import static org.junit.jupiter.api.Assertions.assertNotNull;
41  import static org.junit.jupiter.api.Assertions.assertNotSame;
42  import static org.junit.jupiter.api.Assertions.assertNull;
43  
44  class XmlNodeImplTest {
45  
46      @Test
47      void testCombineChildrenAppend() throws Exception {
48          String lhs = "<configuration>\n"
49                  + "    <plugins>\n"
50                  + "        <plugin>\n"
51                  + "            <groupId>foo.bar</groupId>\n"
52                  + "            <artifactId>foo-bar-plugin</artifactId>\n"
53                  + "            <configuration>\n"
54                  + "                <plugins>\n"
55                  + "                    <plugin>\n"
56                  + "                        <groupId>org.apache.maven.plugins</groupId>\n"
57                  + "                        <artifactId>maven-compiler-plugin</artifactId>\n"
58                  + "                    </plugin>\n"
59                  + "                    <plugin>\n"
60                  + "                        <groupId>org.apache.maven.plugins</groupId>\n"
61                  + "                        <artifactId>maven-surefire-plugin</artifactId>\n"
62                  + "                        <foo>\n"
63                  + "                            <properties combine.children=\"append\">\n"
64                  + "                                <property>\n"
65                  + "                                    <name>prop2</name>\n"
66                  + "                                    <value>value2</value>\n"
67                  + "                                </property>\n"
68                  + "                            </properties>\n"
69                  + "                        </foo>\n"
70                  + "                    </plugin>\n"
71                  + "                </plugins>\n"
72                  + "            </configuration>\n"
73                  + "        </plugin>\n"
74                  + "    </plugins>\n"
75                  + "</configuration>";
76  
77          String rhs = "<configuration>\n"
78                  + "    <plugins>\n"
79                  + "        <plugin>\n"
80                  + "            <groupId>foo.bar</groupId>\n"
81                  + "            <artifactId>foo-bar-plugin</artifactId>\n"
82                  + "            <configuration>\n"
83                  + "                <plugins>\n"
84                  + "                    <plugin>\n"
85                  + "                        <groupId>org.apache.maven.plugins</groupId>\n"
86                  + "                        <artifactId>maven-compiler-plugin</artifactId>\n"
87                  + "                        <bar>\n"
88                  + "                            <value>foo</value>\n"
89                  + "                        </bar>\n"
90                  + "                    </plugin>\n"
91                  + "                    <plugin>\n"
92                  + "                        <groupId>org.apache.maven.plugins</groupId>\n"
93                  + "                        <artifactId>maven-surefire-plugin</artifactId>\n"
94                  + "                        <foo>\n"
95                  + "                            <properties>\n"
96                  + "                                <property>\n"
97                  + "                                    <name>prop1</name>\n"
98                  + "                                    <value>value1</value>\n"
99                  + "                                </property>\n"
100                 + "                            </properties>\n"
101                 + "                        </foo>\n"
102                 + "                    </plugin>\n"
103                 + "                </plugins>\n"
104                 + "            </configuration>\n"
105                 + "        </plugin>\n"
106                 + "    </plugins>\n"
107                 + "</configuration>";
108 
109         String result = "<configuration>\n"
110                 + "    <plugins>\n"
111                 + "        <plugin>\n"
112                 + "            <groupId>foo.bar</groupId>\n"
113                 + "            <artifactId>foo-bar-plugin</artifactId>\n"
114                 + "            <configuration>\n"
115                 + "                <plugins>\n"
116                 + "                    <plugin>\n"
117                 + "                        <groupId>org.apache.maven.plugins</groupId>\n"
118                 + "                        <artifactId>maven-compiler-plugin</artifactId>\n"
119                 + "                        <bar>\n"
120                 + "                            <value>foo</value>\n"
121                 + "                        </bar>\n"
122                 + "                    </plugin>\n"
123                 + "                    <plugin>\n"
124                 + "                        <groupId>org.apache.maven.plugins</groupId>\n"
125                 + "                        <artifactId>maven-surefire-plugin</artifactId>\n"
126                 + "                        <foo>\n"
127                 + "                            <properties combine.children=\"append\">\n"
128                 + "                                <property>\n"
129                 + "                                    <name>prop1</name>\n"
130                 + "                                    <value>value1</value>\n"
131                 + "                                </property>\n"
132                 + "                                <property>\n"
133                 + "                                    <name>prop2</name>\n"
134                 + "                                    <value>value2</value>\n"
135                 + "                                </property>\n"
136                 + "                            </properties>\n"
137                 + "                        </foo>\n"
138                 + "                    </plugin>\n"
139                 + "                </plugins>\n"
140                 + "            </configuration>\n"
141                 + "        </plugin>\n"
142                 + "    </plugins>\n"
143                 + "</configuration>";
144 
145         XmlNode leftDom = toXmlNode(lhs);
146         XmlNode rightDom = toXmlNode(rhs);
147 
148         XmlNode mergeResult = leftDom.merge(rightDom);
149 
150         assertEquals(toXmlNode(result), mergeResult);
151     }
152 
153     /**
154      * <p>testCombineId.</p>
155      *
156      * @throws java.lang.Exception if any.
157      */
158     @Test
159     void testCombineId() throws Exception {
160         String lhs = "<props>" + "<property combine.id='LHS-ONLY'><name>LHS-ONLY</name><value>LHS</value></property>"
161                 + "<property combine.id='TOOVERWRITE'><name>TOOVERWRITE</name><value>LHS</value></property>"
162                 + "</props>";
163 
164         String rhs = "<props>" + "<property combine.id='RHS-ONLY'><name>RHS-ONLY</name><value>RHS</value></property>"
165                 + "<property combine.id='TOOVERWRITE'><name>TOOVERWRITE</name><value>RHS</value></property>"
166                 + "</props>";
167 
168         XmlNodeImpl leftDom = XmlNodeStaxBuilder.build(new StringReader(lhs), new FixedInputLocationBuilder("left"));
169         XmlNodeImpl rightDom = XmlNodeStaxBuilder.build(new StringReader(rhs), new FixedInputLocationBuilder("right"));
170 
171         XmlNode mergeResult = XmlNodeImpl.merge(leftDom, rightDom, true);
172         assertEquals(3, getChildren(mergeResult, "property").size());
173 
174         XmlNode p0 = getNthChild(mergeResult, "property", 0);
175         assertEquals("LHS-ONLY", p0.getChild("name").getValue());
176         assertEquals("left", p0.getChild("name").getInputLocation());
177         assertEquals("LHS", p0.getChild("value").getValue());
178         assertEquals("left", p0.getChild("value").getInputLocation());
179 
180         XmlNode p1 = getNthChild(mergeResult, "property", 1);
181         assertEquals(
182                 "TOOVERWRITE",
183                 getNthChild(mergeResult, "property", 1).getChild("name").getValue());
184         assertEquals("left", p1.getChild("name").getInputLocation());
185         assertEquals(
186                 "LHS", getNthChild(mergeResult, "property", 1).getChild("value").getValue());
187         assertEquals("left", p1.getChild("value").getInputLocation());
188 
189         XmlNode p2 = getNthChild(mergeResult, "property", 2);
190         assertEquals(
191                 "RHS-ONLY",
192                 getNthChild(mergeResult, "property", 2).getChild("name").getValue());
193         assertEquals("right", p2.getChild("name").getInputLocation());
194         assertEquals(
195                 "RHS", getNthChild(mergeResult, "property", 2).getChild("value").getValue());
196         assertEquals("right", p2.getChild("value").getInputLocation());
197     }
198 
199     /**
200      * <p>testCombineKeys.</p>
201      *
202      * @throws java.lang.Exception if any.
203      */
204     @Test
205     void testCombineKeys() throws Exception {
206         String lhs = "<props combine.keys='key'>"
207                 + "<property key=\"LHS-ONLY\"><name>LHS-ONLY</name><value>LHS</value></property>"
208                 + "<property combine.keys='name'><name>TOOVERWRITE</name><value>LHS</value></property>" + "</props>";
209 
210         String rhs = "<props combine.keys='key'>"
211                 + "<property key=\"RHS-ONLY\"><name>RHS-ONLY</name><value>RHS</value></property>"
212                 + "<property combine.keys='name'><name>TOOVERWRITE</name><value>RHS</value></property>" + "</props>";
213 
214         XmlNodeImpl leftDom = XmlNodeStaxBuilder.build(new StringReader(lhs), new FixedInputLocationBuilder("left"));
215         XmlNodeImpl rightDom = XmlNodeStaxBuilder.build(new StringReader(rhs), new FixedInputLocationBuilder("right"));
216 
217         XmlNode mergeResult = XmlNodeImpl.merge(leftDom, rightDom, true);
218         assertEquals(3, getChildren(mergeResult, "property").size());
219 
220         XmlNode p0 = getNthChild(mergeResult, "property", 0);
221         assertEquals("LHS-ONLY", p0.getChild("name").getValue());
222         assertEquals("left", p0.getChild("name").getInputLocation());
223         assertEquals("LHS", p0.getChild("value").getValue());
224         assertEquals("left", p0.getChild("value").getInputLocation());
225 
226         XmlNode p1 = getNthChild(mergeResult, "property", 1);
227         assertEquals(
228                 "TOOVERWRITE",
229                 getNthChild(mergeResult, "property", 1).getChild("name").getValue());
230         assertEquals("left", p1.getChild("name").getInputLocation());
231         assertEquals(
232                 "LHS", getNthChild(mergeResult, "property", 1).getChild("value").getValue());
233         assertEquals("left", p1.getChild("value").getInputLocation());
234 
235         XmlNode p2 = getNthChild(mergeResult, "property", 2);
236         assertEquals(
237                 "RHS-ONLY",
238                 getNthChild(mergeResult, "property", 2).getChild("name").getValue());
239         assertEquals("right", p2.getChild("name").getInputLocation());
240         assertEquals(
241                 "RHS", getNthChild(mergeResult, "property", 2).getChild("value").getValue());
242         assertEquals("right", p2.getChild("value").getInputLocation());
243     }
244 
245     @Test
246     void testPreserveDominantBlankValue() throws XMLStreamException, IOException {
247         String lhs = "<parameter xml:space=\"preserve\"> </parameter>";
248 
249         String rhs = "<parameter>recessive</parameter>";
250 
251         XmlNodeImpl leftDom = XmlNodeStaxBuilder.build(new StringReader(lhs), new FixedInputLocationBuilder("left"));
252         XmlNodeImpl rightDom = XmlNodeStaxBuilder.build(new StringReader(rhs), new FixedInputLocationBuilder("right"));
253 
254         XmlNode mergeResult = XmlNodeImpl.merge(leftDom, rightDom, true);
255         assertEquals(" ", mergeResult.getValue());
256     }
257 
258     @Test
259     void testPreserveDominantEmptyNode() throws XMLStreamException, IOException {
260         String lhs = "<parameter></parameter>";
261 
262         String rhs = "<parameter>recessive</parameter>";
263 
264         XmlNodeImpl leftDom = XmlNodeStaxBuilder.build(new StringReader(lhs), new FixedInputLocationBuilder("left"));
265         XmlNodeImpl rightDom = XmlNodeStaxBuilder.build(new StringReader(rhs), new FixedInputLocationBuilder("right"));
266 
267         XmlNode mergeResult = XmlNodeImpl.merge(leftDom, rightDom, true);
268         assertEquals("", mergeResult.getValue());
269     }
270 
271     @Test
272     void testPreserveDominantEmptyNode2() throws XMLStreamException, IOException {
273         String lhs = "<parameter/>";
274 
275         String rhs = "<parameter>recessive</parameter>";
276 
277         XmlNodeImpl leftDom = XmlNodeStaxBuilder.build(new StringReader(lhs), new FixedInputLocationBuilder("left"));
278         XmlNodeImpl rightDom = XmlNodeStaxBuilder.build(new StringReader(rhs), new FixedInputLocationBuilder("right"));
279 
280         XmlNode mergeResult = XmlNodeImpl.merge(leftDom, rightDom, true);
281         assertNull(mergeResult.getValue());
282     }
283 
284     /**
285      * <p>testShouldPerformAppendAtFirstSubElementLevel.</p>
286      */
287     @Test
288     void testShouldPerformAppendAtFirstSubElementLevel() {
289         // create the dominant DOM
290         Xpp3Dom t1 = new Xpp3Dom("top");
291         t1.setAttribute(Xpp3Dom.CHILDREN_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.CHILDREN_COMBINATION_APPEND);
292         t1.setInputLocation("t1top");
293 
294         Xpp3Dom t1s1 = new Xpp3Dom("topsub1");
295         t1s1.setValue("t1s1Value");
296         t1s1.setInputLocation("t1s1");
297 
298         t1.addChild(t1s1);
299 
300         // create the recessive DOM
301         Xpp3Dom t2 = new Xpp3Dom("top");
302         t2.setInputLocation("t2top");
303 
304         Xpp3Dom t2s1 = new Xpp3Dom("topsub1");
305         t2s1.setValue("t2s1Value");
306         t2s1.setInputLocation("t2s1");
307 
308         t2.addChild(t2s1);
309 
310         // merge and check results.
311         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(t1, t2);
312 
313         assertEquals(2, result.getChildren("topsub1").length);
314         assertEquals("t2s1Value", result.getChildren("topsub1")[0].getValue());
315         assertEquals("t1s1Value", result.getChildren("topsub1")[1].getValue());
316 
317         assertEquals("t1top", result.getInputLocation());
318         assertEquals("t2s1", result.getChildren("topsub1")[0].getInputLocation());
319         assertEquals("t1s1", result.getChildren("topsub1")[1].getInputLocation());
320     }
321 
322     /**
323      * <p>testShouldOverrideAppendAndDeepMerge.</p>
324      */
325     @Test
326     void testShouldOverrideAppendAndDeepMerge() {
327         // create the dominant DOM
328         Xpp3Dom t1 = new Xpp3Dom("top");
329         t1.setAttribute(Xpp3Dom.CHILDREN_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.CHILDREN_COMBINATION_APPEND);
330         t1.setInputLocation("t1top");
331 
332         Xpp3Dom t1s1 = new Xpp3Dom("topsub1");
333         t1s1.setValue("t1s1Value");
334         t1s1.setInputLocation("t1s1");
335 
336         t1.addChild(t1s1);
337 
338         // create the recessive DOM
339         Xpp3Dom t2 = new Xpp3Dom("top");
340         t2.setInputLocation("t2top");
341 
342         Xpp3Dom t2s1 = new Xpp3Dom("topsub1");
343         t2s1.setValue("t2s1Value");
344         t2s1.setInputLocation("t2s1");
345 
346         t2.addChild(t2s1);
347 
348         // merge and check results.
349         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(t1, t2, Boolean.TRUE);
350 
351         assertEquals(1, result.getChildren("topsub1").length);
352         assertEquals("t1s1Value", result.getChildren("topsub1")[0].getValue());
353 
354         assertEquals("t1top", result.getInputLocation());
355         assertEquals("t1s1", result.getChildren("topsub1")[0].getInputLocation());
356     }
357 
358     /**
359      * <p>testShouldPerformSelfOverrideAtTopLevel.</p>
360      */
361     @Test
362     void testShouldPerformSelfOverrideAtTopLevel() {
363         // create the dominant DOM
364         Xpp3Dom t1 = new Xpp3Dom("top");
365         t1.setAttribute("attr", "value");
366         t1.setInputLocation("t1top");
367 
368         t1.setAttribute(Xpp3Dom.SELF_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.SELF_COMBINATION_OVERRIDE);
369 
370         // create the recessive DOM
371         Xpp3Dom t2 = new Xpp3Dom("top");
372         t2.setAttribute("attr2", "value2");
373         t2.setValue("t2Value");
374         t2.setInputLocation("t2top");
375 
376         // merge and check results.
377         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(t1, t2);
378 
379         assertEquals(2, result.getAttributeNames().length);
380         assertNull(result.getValue());
381         assertEquals("t1top", result.getInputLocation());
382     }
383 
384     /**
385      * <p>testShouldMergeValuesAtTopLevelByDefault.</p>
386      */
387     @Test
388     void testShouldNotMergeValuesAtTopLevelByDefault() {
389         // create the dominant DOM
390         Xpp3Dom t1 = new Xpp3Dom("top");
391         t1.setAttribute("attr", "value");
392         t1.setInputLocation("t1top");
393 
394         // create the recessive DOM
395         Xpp3Dom t2 = new Xpp3Dom("top");
396         t2.setAttribute("attr2", "value2");
397         t2.setValue("t2Value");
398         t2.setInputLocation("t2top");
399 
400         // merge and check results.
401         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(t1, t2);
402 
403         // this is still 2, since we're not using the merge-control attribute.
404         assertEquals(2, result.getAttributeNames().length);
405 
406         assertNull(result.getValue());
407         assertEquals("t1top", result.getInputLocation());
408     }
409 
410     /**
411      * <p>testShouldMergeValuesAtTopLevel.</p>
412      */
413     @Test
414     void testShouldNotMergeValuesAtTopLevel() {
415         // create the dominant DOM
416         Xpp3Dom t1 = new Xpp3Dom("top");
417         t1.setAttribute("attr", "value");
418 
419         t1.setAttribute(Xpp3Dom.SELF_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.SELF_COMBINATION_MERGE);
420 
421         // create the recessive DOM
422         Xpp3Dom t2 = new Xpp3Dom("top");
423         t2.setAttribute("attr2", "value2");
424         t2.setValue("t2Value");
425 
426         // merge and check results.
427         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(t1, t2);
428 
429         assertEquals(3, result.getAttributeNames().length);
430         assertNull(result.getValue());
431     }
432 
433     /**
434      * <p>testEquals.</p>
435      */
436     @Test
437     void testEquals() {
438         XmlNodeImpl dom = new XmlNodeImpl("top");
439 
440         assertEquals(dom, dom);
441         assertNotEquals(dom, null);
442         assertNotEquals(dom, new XmlNodeImpl(""));
443     }
444 
445     /**
446      * <p>testEqualsIsNullSafe.</p>
447      */
448     @Test
449     void testEqualsIsNullSafe() throws XMLStreamException, IOException {
450         String testDom = "<configuration><items thing='blah'><item>one</item><item>two</item></items></configuration>";
451         XmlNode dom = toXmlNode(testDom);
452 
453         Map<String, String> attributes = new HashMap<>();
454         attributes.put("nullValue", null);
455         attributes.put(null, "nullKey");
456         List<XmlNode> childList = new ArrayList<>();
457         childList.add(null);
458         Xpp3Dom dom2 = new Xpp3Dom(new XmlNodeImpl(dom.getName(), null, attributes, childList, null));
459 
460         assertNotEquals(dom, dom2);
461         assertNotEquals(dom2, dom);
462     }
463 
464     /**
465      * <p>testShouldOverwritePluginConfigurationSubItemsByDefault.</p>
466      */
467     @Test
468     void testShouldOverwritePluginConfigurationSubItemsByDefault() throws XMLStreamException, IOException {
469         String parentConfigStr = "<configuration><items><item>one</item><item>two</item></items></configuration>";
470         XmlNode parentConfig = toXmlNode(parentConfigStr, new FixedInputLocationBuilder("parent"));
471 
472         String childConfigStr = "<configuration><items><item>three</item></items></configuration>";
473         XmlNode childConfig = toXmlNode(childConfigStr, new FixedInputLocationBuilder("child"));
474 
475         XmlNode result = XmlNode.merge(childConfig, parentConfig);
476         XmlNode items = result.getChild("items");
477 
478         assertEquals(1, items.getChildren().size());
479 
480         XmlNode item = items.getChildren().get(0);
481         assertEquals("three", item.getValue());
482         assertEquals("child", item.getInputLocation());
483     }
484 
485     /**
486      * <p>testShouldMergePluginConfigurationSubItemsWithMergeAttributeSet.</p>
487      */
488     @Test
489     void testShouldMergePluginConfigurationSubItemsWithMergeAttributeSet() throws XMLStreamException, IOException {
490         String parentConfigStr = "<configuration><items><item>one</item><item>two</item></items></configuration>";
491         XmlNode parentConfig = toXmlNode(parentConfigStr, new FixedInputLocationBuilder("parent"));
492 
493         String childConfigStr =
494                 "<configuration><items combine.children=\"append\"><item>three</item></items></configuration>";
495         XmlNode childConfig = toXmlNode(childConfigStr, new FixedInputLocationBuilder("child"));
496 
497         XmlNode result = XmlNode.merge(childConfig, parentConfig);
498         XmlNode items = result.getChild("items");
499 
500         XmlNode[] item = items.getChildren().toArray(new XmlNode[0]);
501         assertEquals(3, item.length);
502         assertEquals("one", item[0].getValue());
503         assertEquals("parent", item[0].getInputLocation());
504         assertEquals("two", item[1].getValue());
505         assertEquals("parent", item[1].getInputLocation());
506         assertEquals("three", item[2].getValue());
507         assertEquals("child", item[2].getInputLocation());
508     }
509 
510     /**
511      * <p>testShouldNotChangeUponMergeWithItselfWhenFirstOrLastSubItemIsEmpty.</p>
512      *
513      * @throws java.lang.Exception if any.
514      */
515     @Test
516     void testShouldNotChangeUponMergeWithItselfWhenFirstOrLastSubItemIsEmpty() throws Exception {
517         String configStr = "<configuration><items><item/><item>test</item><item/></items></configuration>";
518         Xpp3Dom dominantConfig = Xpp3DomBuilder.build(new StringReader(configStr));
519         Xpp3Dom recessiveConfig = Xpp3DomBuilder.build(new StringReader(configStr));
520 
521         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(dominantConfig, recessiveConfig);
522         Xpp3Dom items = result.getChild("items");
523 
524         assertEquals(3, items.getChildCount());
525 
526         assertNull(items.getChild(0).getValue());
527         assertEquals("test", items.getChild(1).getValue());
528         assertNull(items.getChild(2).getValue());
529     }
530 
531     /**
532      * <p>testShouldCopyRecessiveChildrenNotPresentInTarget.</p>
533      *
534      * @throws java.lang.Exception if any.
535      */
536     @Test
537     void testShouldCopyRecessiveChildrenNotPresentInTarget() throws Exception {
538         String dominantStr = "<configuration><foo>x</foo></configuration>";
539         String recessiveStr = "<configuration><bar>y</bar></configuration>";
540         Xpp3Dom dominantConfig = Xpp3DomBuilder.build(new StringReader(dominantStr));
541         Xpp3Dom recessiveConfig = Xpp3DomBuilder.build(new StringReader(recessiveStr));
542 
543         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(dominantConfig, recessiveConfig);
544 
545         assertEquals(2, result.getChildCount());
546 
547         assertEquals("x", result.getChild("foo").getValue());
548         assertEquals("y", result.getChild("bar").getValue());
549         assertNotSame(result.getChild("bar"), recessiveConfig.getChild("bar"));
550     }
551 
552     /**
553      * <p>testDupeChildren.</p>
554      */
555     @Test
556     void testDupeChildren() throws IOException, XMLStreamException {
557         String dupes = "<configuration><foo>x</foo><foo>y</foo></configuration>";
558         XmlNode dom = toXmlNode(new StringReader(dupes));
559         assertNotNull(dom);
560         assertEquals("y", dom.getChild("foo").getValue());
561     }
562 
563     /**
564      * <p>testShouldRemoveEntireElementWithAttributesAndChildren.</p>
565      *
566      * @throws java.lang.Exception if any.
567      */
568     @Test
569     void testShouldRemoveEntireElementWithAttributesAndChildren() throws Exception {
570         String dominantStr = "<config><service combine.self=\"remove\"/></config>";
571         String recessiveStr = "<config><service><parameter>parameter</parameter></service></config>";
572         Xpp3Dom dominantConfig = Xpp3DomBuilder.build(new StringReader(dominantStr));
573         Xpp3Dom recessiveConfig = Xpp3DomBuilder.build(new StringReader(recessiveStr));
574 
575         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(dominantConfig, recessiveConfig);
576 
577         assertEquals(0, result.getChildCount());
578         assertEquals("config", result.getName());
579     }
580 
581     /**
582      * <p>testShouldRemoveDoNotRemoveTagWhenSwappedInputDOMs.</p>
583      *
584      * @throws java.lang.Exception if any.
585      */
586     @Test
587     void testShouldRemoveDoNotRemoveTagWhenSwappedInputDOMs() throws Exception {
588         String dominantStr = "<config><service combine.self=\"remove\"/></config>";
589         String recessiveStr = "<config><service><parameter>parameter</parameter></service></config>";
590         Xpp3Dom dominantConfig = Xpp3DomBuilder.build(new StringReader(dominantStr));
591         Xpp3Dom recessiveConfig = Xpp3DomBuilder.build(new StringReader(recessiveStr));
592 
593         // same DOMs as testShouldRemoveEntireElementWithAttributesAndChildren(), swapping dominant <--> recessive
594         Xpp3Dom result = Xpp3Dom.mergeXpp3Dom(recessiveConfig, dominantConfig);
595 
596         assertEquals(recessiveConfig.toString(), result.toString());
597     }
598 
599     @Test
600     void testMergeCombineChildrenAppendOnRecessive() throws XMLStreamException, IOException {
601         String dominant = "<relocations>\n" + "  <relocation>\n"
602                 + "    <pattern>org.apache.shiro.crypto.CipherService</pattern>\n"
603                 + "    <shadedPattern>org.apache.shiro.crypto.cipher.CipherService</shadedPattern>\n"
604                 + "  </relocation>\n"
605                 + "</relocations>";
606         String recessive = "<relocations combine.children=\"append\">\n"
607                 + "  <relocation>\n"
608                 + "    <pattern>javax.faces</pattern>\n"
609                 + "    <shadedPattern>jakarta.faces</shadedPattern>\n"
610                 + "  </relocation>\n"
611                 + "</relocations>";
612         String expected = "<relocations combine.children=\"append\">\n"
613                 + "  <relocation>\n"
614                 + "    <pattern>javax.faces</pattern>\n"
615                 + "    <shadedPattern>jakarta.faces</shadedPattern>\n"
616                 + "  </relocation>\n"
617                 + "  <relocation>\n"
618                 + "    <pattern>org.apache.shiro.crypto.CipherService</pattern>\n"
619                 + "    <shadedPattern>org.apache.shiro.crypto.cipher.CipherService</shadedPattern>\n"
620                 + "  </relocation>\n"
621                 + "</relocations>";
622 
623         XmlNode d = toXmlNode(dominant);
624         XmlNode r = toXmlNode(recessive);
625         XmlNode m = d.merge(r);
626         assertEquals(expected, m.toString().replaceAll("\r\n", "\n"));
627     }
628 
629     private static List<XmlNode> getChildren(XmlNode node, String name) {
630         return node.getChildren().stream().filter(n -> n.getName().equals(name)).collect(Collectors.toList());
631     }
632 
633     private static XmlNode getNthChild(XmlNode node, String name, int nth) {
634         return node.getChildren().stream()
635                 .filter(n -> n.getName().equals(name))
636                 .skip(nth)
637                 .findFirst()
638                 .orElse(null);
639     }
640 
641     private static XmlNode toXmlNode(String xml) throws XMLStreamException, IOException {
642         return toXmlNode(xml, null);
643     }
644 
645     private static XmlNode toXmlNode(String xml, XmlNodeStaxBuilder.InputLocationBuilderStax locationBuilder)
646             throws XMLStreamException, IOException {
647         return toXmlNode(new StringReader(xml), locationBuilder);
648     }
649 
650     private static XmlNode toXmlNode(Reader reader) throws XMLStreamException, IOException {
651         return toXmlNode(reader, null);
652     }
653 
654     private static XmlNode toXmlNode(Reader reader, XmlNodeStaxBuilder.InputLocationBuilderStax locationBuilder)
655             throws XMLStreamException, IOException {
656         return XmlNodeStaxBuilder.build(reader, locationBuilder);
657     }
658 
659     private static class FixedInputLocationBuilder implements XmlNodeStaxBuilder.InputLocationBuilderStax {
660         private final Object location;
661 
662         public FixedInputLocationBuilder(Object location) {
663             this.location = location;
664         }
665 
666         public Object toInputLocation(XMLStreamReader parser) {
667             return location;
668         }
669     }
670 }