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)) && !SELF_COMBINATION_MODE_ATTRIBUTE.equals(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                 String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
253 
254                 for (XmlNode recessiveChild : recessive.getChildren()) {
255                     String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
256 
257                     XmlNode childDom = null;
258                     if (isNotEmpty(idValue)) {
259                         for (XmlNode dominantChild : dominant.getChildren()) {
260                             if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
261                                 childDom = dominantChild;
262                                 
263                                 mergeChildren = true;
264                             }
265                         }
266                     } else if (isNotEmpty(keysValue)) {
267                         String[] keys = keysValue.split(",");
268                         Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
269                                 .collect(Collectors.toMap(
270                                         k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k))));
271 
272                         for (XmlNode dominantChild : dominant.getChildren()) {
273                             Map<String, Optional<String>> dominantKeyValues = Stream.of(keys)
274                                     .collect(Collectors.toMap(
275                                             k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k))));
276 
277                             if (recessiveKeyValues.equals(dominantKeyValues)) {
278                                 childDom = dominantChild;
279                                 
280                                 mergeChildren = true;
281                             }
282                         }
283                     } else {
284                         childDom = dominant.getChild(recessiveChild.getName());
285                     }
286 
287                     if (mergeChildren && childDom != null) {
288                         Map<String, Iterator<XmlNode>> commonChildren = new HashMap<>();
289                         Set<String> names = recessive.getChildren().stream()
290                                 .map(XmlNode::getName)
291                                 .collect(Collectors.toSet());
292                         for (String name : names) {
293                             List<XmlNode> dominantChildren = dominant.getChildren().stream()
294                                     .filter(n -> n.getName().equals(name))
295                                     .collect(Collectors.toList());
296                             if (dominantChildren.size() > 0) {
297                                 commonChildren.put(name, dominantChildren.iterator());
298                             }
299                         }
300 
301                         String name = recessiveChild.getName();
302                         Iterator<XmlNode> it =
303                                 commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream()
304                                                 .filter(n2 -> n2.getName().equals(n1))
305                                                 .collect(Collectors.toList()))
306                                         .filter(l -> !l.isEmpty())
307                                         .map(List::iterator)
308                                         .findFirst()
309                                         .orElse(null));
310                         if (it == null) {
311                             if (children == null) {
312                                 children = new ArrayList<>(dominant.getChildren());
313                             }
314                             children.add(recessiveChild);
315                         } else if (it.hasNext()) {
316                             XmlNode dominantChild = it.next();
317 
318                             String dominantChildCombinationMode =
319                                     dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
320                             if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
321                                 if (children == null) {
322                                     children = new ArrayList<>(dominant.getChildren());
323                                 }
324                                 children.remove(dominantChild);
325                             } else {
326                                 int idx = dominant.getChildren().indexOf(dominantChild);
327                                 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
328                                 if (merged != dominantChild) {
329                                     if (children == null) {
330                                         children = new ArrayList<>(dominant.getChildren());
331                                     }
332                                     children.set(idx, merged);
333                                 }
334                             }
335                         }
336                     } else {
337                         if (children == null) {
338                             children = new ArrayList<>(dominant.getChildren());
339                         }
340                         int idx = mergeChildren
341                                 ? children.size()
342                                 : recessive.getChildren().indexOf(recessiveChild);
343                         children.add(idx, recessiveChild);
344                     }
345                 }
346             }
347 
348             if (value != null || attrs != null || children != null) {
349                 if (attrs != null) {
350                     Map<String, String> nattrs = attrs;
351                     attrs = new HashMap<>(dominant.getAttributes());
352                     attrs.putAll(nattrs);
353                 } else {
354                     attrs = dominant.getAttributes();
355                 }
356                 if (children == null) {
357                     children = dominant.getChildren();
358                 }
359                 return new XmlNodeImpl(
360                         dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
361             }
362         }
363         return dominant;
364     }
365 
366     
367 
368 
369 
370 
371 
372 
373 
374 
375 
376     public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
377         return merge(dominant, recessive, null);
378     }
379 
380     
381     
382     
383 
384     @Override
385     public boolean equals(Object o) {
386         if (this == o) {
387             return true;
388         }
389         if (o == null || getClass() != o.getClass()) {
390             return false;
391         }
392         XmlNodeImpl that = (XmlNodeImpl) o;
393         return Objects.equals(this.name, that.name)
394                 && Objects.equals(this.value, that.value)
395                 && Objects.equals(this.attributes, that.attributes)
396                 && Objects.equals(this.children, that.children);
397     }
398 
399     @Override
400     public int hashCode() {
401         return Objects.hash(name, value, attributes, children);
402     }
403 
404     @Override
405     public String toString() {
406         StringWriter writer = new StringWriter();
407         XmlNodeWriter.write(writer, this);
408         return writer.toString();
409     }
410 
411     public String toUnescapedString() {
412         StringWriter writer = new StringWriter();
413         XMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
414         XmlNodeWriter.write(xmlWriter, this, false);
415         return writer.toString();
416     }
417 
418     private static boolean isNotEmpty(String str) {
419         return ((str != null) && (str.length() > 0));
420     }
421 
422     private static boolean isEmpty(String str) {
423         return ((str == null) || (str.length() == 0));
424     }
425 }