1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.internal.xml;
20  
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.io.StringWriter;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.ListIterator;
30  import java.util.Map;
31  import java.util.Objects;
32  import java.util.Optional;
33  import java.util.Set;
34  import java.util.stream.Collectors;
35  import java.util.stream.Stream;
36  
37  import org.apache.maven.api.xml.XmlNode;
38  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
39  import org.codehaus.plexus.util.xml.SerializerXMLWriter;
40  import org.codehaus.plexus.util.xml.XMLWriter;
41  import org.codehaus.plexus.util.xml.pull.XmlSerializer;
42  
43  
44  
45  
46  public class XmlNodeImpl implements Serializable, XmlNode {
47      private static final long serialVersionUID = 2567894443061173996L;
48  
49      protected final String name;
50  
51      protected final String value;
52  
53      protected final Map<String, String> attributes;
54  
55      protected final List<XmlNode> children;
56  
57      protected final Object location;
58  
59      public XmlNodeImpl(String name) {
60          this(name, null, null, null, null);
61      }
62  
63      public XmlNodeImpl(String name, String value) {
64          this(name, value, null, null, null);
65      }
66  
67      public XmlNodeImpl(XmlNode from, String name) {
68          this(name, from.getValue(), from.getAttributes(), from.getChildren(), from.getInputLocation());
69      }
70  
71      public XmlNodeImpl(
72              String name, String value, Map<String, String> attributes, List<XmlNode> children, Object location) {
73          this.name = Objects.requireNonNull(name);
74          this.value = value;
75          this.attributes =
76                  attributes != null ? Collections.unmodifiableMap(new HashMap<>(attributes)) : Collections.emptyMap();
77          this.children =
78                  children != null ? Collections.unmodifiableList(new ArrayList<>(children)) : Collections.emptyList();
79          this.location = location;
80      }
81  
82      @Override
83      public XmlNode merge(XmlNode source, Boolean childMergeOverride) {
84          return merge(this, source, childMergeOverride);
85      }
86  
87      public XmlNode clone() {
88          return this;
89      }
90  
91      
92      
93      
94  
95      public String getName() {
96          return name;
97      }
98  
99      
100     
101     
102 
103     public String getValue() {
104         return value;
105     }
106 
107     
108     
109     
110 
111     @Override
112     public Map<String, String> getAttributes() {
113         return attributes;
114     }
115 
116     public String getAttribute(String name) {
117         return attributes.get(name);
118     }
119 
120     
121     
122     
123 
124     public XmlNode getChild(String name) {
125         if (name != null) {
126             ListIterator<XmlNode> it = children.listIterator(children.size());
127             while (it.hasPrevious()) {
128                 XmlNode child = it.previous();
129                 if (name.equals(child.getName())) {
130                     return child;
131                 }
132             }
133         }
134         return null;
135     }
136 
137     public List<XmlNode> getChildren() {
138         return children;
139     }
140 
141     public int getChildCount() {
142         return children.size();
143     }
144 
145     
146     
147     
148 
149     
150 
151 
152 
153     public Object getInputLocation() {
154         return location;
155     }
156 
157     
158     
159     
160 
161     public void writeToSerializer(String namespace, XmlSerializer serializer) throws IOException {
162         
163         
164         SerializerXMLWriter xmlWriter = new SerializerXMLWriter(namespace, serializer);
165         XmlNodeWriter.write(xmlWriter, this);
166         if (xmlWriter.getExceptions().size() > 0) {
167             throw (IOException) xmlWriter.getExceptions().get(0);
168         }
169     }
170 
171     
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
183 
184 
185 
186 
187 
188 
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199 
200 
201 
202 
203 
204 
205 
206     @SuppressWarnings("checkstyle:MethodLength")
207     public static XmlNode merge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride) {
208         
209         if (recessive == null) {
210             return dominant;
211         }
212         if (dominant == null) {
213             return recessive;
214         }
215 
216         boolean mergeSelf = true;
217 
218         String selfMergeMode = dominant.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
219 
220         if (SELF_COMBINATION_OVERRIDE.equals(selfMergeMode)) {
221             mergeSelf = false;
222         }
223 
224         if (mergeSelf) {
225 
226             String value = dominant.getValue();
227             Object location = dominant.getInputLocation();
228             Map<String, String> attrs = null;
229             List<XmlNode> children = null;
230 
231             for (Map.Entry<String, String> attr : recessive.getAttributes().entrySet()) {
232                 String key = attr.getKey();
233                 if (isEmpty(dominant.getAttribute(key))) {
234                     if (attrs == null) {
235                         attrs = new HashMap<>();
236                     }
237                     attrs.put(key, attr.getValue());
238                 }
239             }
240 
241             if (recessive.getChildren().size() > 0) {
242                 boolean mergeChildren = true;
243                 if (childMergeOverride != null) {
244                     mergeChildren = childMergeOverride;
245                 } else {
246                     String childMergeMode = dominant.getAttribute(CHILDREN_COMBINATION_MODE_ATTRIBUTE);
247                     if (CHILDREN_COMBINATION_APPEND.equals(childMergeMode)) {
248                         mergeChildren = false;
249                     }
250                 }
251 
252                 Map<String, Iterator<XmlNode>> commonChildren = new HashMap<>();
253                 Set<String> names =
254                         recessive.getChildren().stream().map(XmlNode::getName).collect(Collectors.toSet());
255                 for (String name : names) {
256                     List<XmlNode> dominantChildren = dominant.getChildren().stream()
257                             .filter(n -> n.getName().equals(name))
258                             .collect(Collectors.toList());
259                     if (dominantChildren.size() > 0) {
260                         commonChildren.put(name, dominantChildren.iterator());
261                     }
262                 }
263 
264                 String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
265 
266                 for (XmlNode recessiveChild : recessive.getChildren()) {
267                     String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
268 
269                     XmlNode childDom = null;
270                     if (isNotEmpty(idValue)) {
271                         for (XmlNode dominantChild : dominant.getChildren()) {
272                             if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
273                                 childDom = dominantChild;
274                                 
275                                 mergeChildren = true;
276                             }
277                         }
278                     } else if (isNotEmpty(keysValue)) {
279                         String[] keys = keysValue.split(",");
280                         Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
281                                 .collect(Collectors.toMap(
282                                         k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k))));
283 
284                         for (XmlNode dominantChild : dominant.getChildren()) {
285                             Map<String, Optional<String>> dominantKeyValues = Stream.of(keys)
286                                     .collect(Collectors.toMap(
287                                             k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k))));
288 
289                             if (recessiveKeyValues.equals(dominantKeyValues)) {
290                                 childDom = dominantChild;
291                                 
292                                 mergeChildren = true;
293                             }
294                         }
295                     } else {
296                         childDom = dominant.getChild(recessiveChild.getName());
297                     }
298 
299                     if (mergeChildren && childDom != null) {
300                         String name = recessiveChild.getName();
301                         Iterator<XmlNode> it =
302                                 commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream()
303                                                 .filter(n2 -> n2.getName().equals(n1))
304                                                 .collect(Collectors.toList()))
305                                         .filter(l -> !l.isEmpty())
306                                         .map(List::iterator)
307                                         .findFirst()
308                                         .orElse(null));
309                         if (it == null) {
310                             if (children == null) {
311                                 children = new ArrayList<>(dominant.getChildren());
312                             }
313                             children.add(recessiveChild);
314                         } else if (it.hasNext()) {
315                             XmlNode dominantChild = it.next();
316 
317                             String dominantChildCombinationMode =
318                                     dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
319                             if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
320                                 if (children == null) {
321                                     children = new ArrayList<>(dominant.getChildren());
322                                 }
323                                 children.remove(dominantChild);
324                             } else {
325                                 int idx = dominant.getChildren().indexOf(dominantChild);
326                                 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
327                                 if (merged != dominantChild) {
328                                     if (children == null) {
329                                         children = new ArrayList<>(dominant.getChildren());
330                                     }
331                                     children.set(idx, merged);
332                                 }
333                             }
334                         }
335                     } else {
336                         if (children == null) {
337                             children = new ArrayList<>(dominant.getChildren());
338                         }
339                         int idx = mergeChildren
340                                 ? children.size()
341                                 : recessive.getChildren().indexOf(recessiveChild);
342                         children.add(idx, recessiveChild);
343                     }
344                 }
345             }
346 
347             if (value != null || attrs != null || children != null) {
348                 if (attrs != null) {
349                     Map<String, String> nattrs = attrs;
350                     attrs = new HashMap<>(dominant.getAttributes());
351                     attrs.putAll(nattrs);
352                 } else {
353                     attrs = dominant.getAttributes();
354                 }
355                 if (children == null) {
356                     children = dominant.getChildren();
357                 }
358                 return new XmlNodeImpl(
359                         dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
360             }
361         }
362         return dominant;
363     }
364 
365     
366 
367 
368 
369 
370 
371 
372 
373 
374 
375     public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
376         return merge(dominant, recessive, null);
377     }
378 
379     
380     
381     
382 
383     @Override
384     public boolean equals(Object o) {
385         if (this == o) {
386             return true;
387         }
388         if (o == null || getClass() != o.getClass()) {
389             return false;
390         }
391         XmlNodeImpl that = (XmlNodeImpl) o;
392         return Objects.equals(this.name, that.name)
393                 && Objects.equals(this.value, that.value)
394                 && Objects.equals(this.attributes, that.attributes)
395                 && Objects.equals(this.children, that.children);
396     }
397 
398     @Override
399     public int hashCode() {
400         return Objects.hash(name, value, attributes, children);
401     }
402 
403     @Override
404     public String toString() {
405         StringWriter writer = new StringWriter();
406         XmlNodeWriter.write(writer, this);
407         return writer.toString();
408     }
409 
410     public String toUnescapedString() {
411         StringWriter writer = new StringWriter();
412         XMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
413         XmlNodeWriter.write(xmlWriter, this, false);
414         return writer.toString();
415     }
416 
417     private static boolean isNotEmpty(String str) {
418         return ((str != null) && (str.length() > 0));
419     }
420 
421     private static boolean isEmpty(String str) {
422         return ((str == null) || (str.length() == 0));
423     }
424 }