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 javax.xml.stream.XMLStreamException;
22  
23  import java.io.Serializable;
24  import java.io.StringWriter;
25  import java.util.ArrayList;
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.function.Function;
35  import java.util.stream.Collectors;
36  import java.util.stream.Stream;
37  
38  import org.apache.maven.api.xml.XmlNode;
39  
40  
41  
42  
43  public class XmlNodeImpl implements Serializable, XmlNode {
44      private static final long serialVersionUID = 2567894443061173996L;
45  
46      protected final String prefix;
47  
48      protected final String namespaceUri;
49  
50      protected final String name;
51  
52      protected final String value;
53  
54      protected final Map<String, String> attributes;
55  
56      protected final List<XmlNode> children;
57  
58      protected final Object location;
59  
60      public XmlNodeImpl(String name) {
61          this(name, null, null, null, null);
62      }
63  
64      public XmlNodeImpl(String name, String value) {
65          this(name, value, null, null, null);
66      }
67  
68      public XmlNodeImpl(XmlNode from, String name) {
69          this(name, from.getValue(), from.getAttributes(), from.getChildren(), from.getInputLocation());
70      }
71  
72      public XmlNodeImpl(
73              String name, String value, Map<String, String> attributes, List<XmlNode> children, Object location) {
74          this("", "", name, value, attributes, children, location);
75      }
76  
77      public XmlNodeImpl(
78              String prefix,
79              String namespaceUri,
80              String name,
81              String value,
82              Map<String, String> attributes,
83              List<XmlNode> children,
84              Object location) {
85          this.prefix = prefix == null ? "" : prefix;
86          this.namespaceUri = namespaceUri == null ? "" : namespaceUri;
87          this.name = Objects.requireNonNull(name);
88          this.value = value;
89          this.attributes = ImmutableCollections.copy(attributes);
90          this.children = ImmutableCollections.copy(children);
91          this.location = location;
92      }
93  
94      @Override
95      public XmlNode merge(XmlNode source, Boolean childMergeOverride) {
96          return merge(this, source, childMergeOverride);
97      }
98  
99      
100     
101     
102 
103     @Override
104     public String getPrefix() {
105         return prefix;
106     }
107 
108     @Override
109     public String getNamespaceUri() {
110         return namespaceUri;
111     }
112 
113     @Override
114     public String getName() {
115         return name;
116     }
117 
118     
119     
120     
121 
122     public String getValue() {
123         return value;
124     }
125 
126     
127     
128     
129 
130     @Override
131     public Map<String, String> getAttributes() {
132         return attributes;
133     }
134 
135     public String getAttribute(String name) {
136         return attributes.get(name);
137     }
138 
139     
140     
141     
142 
143     public XmlNode getChild(String name) {
144         if (name != null) {
145             ListIterator<XmlNode> it = children.listIterator(children.size());
146             while (it.hasPrevious()) {
147                 XmlNode child = it.previous();
148                 if (name.equals(child.getName())) {
149                     return child;
150                 }
151             }
152         }
153         return null;
154     }
155 
156     public List<XmlNode> getChildren() {
157         return children;
158     }
159 
160     public int getChildCount() {
161         return children.size();
162     }
163 
164     
165     
166     
167 
168     
169 
170 
171 
172     public Object getInputLocation() {
173         return location;
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 
207 
208 
209 
210 
211 
212 
213 
214 
215     @SuppressWarnings("checkstyle:MethodLength")
216     public static XmlNode merge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride) {
217         
218         if (recessive == null) {
219             return dominant;
220         }
221         if (dominant == null) {
222             return recessive;
223         }
224 
225         boolean mergeSelf = true;
226 
227         String selfMergeMode = dominant.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
228 
229         if (SELF_COMBINATION_OVERRIDE.equals(selfMergeMode)) {
230             mergeSelf = false;
231         }
232 
233         if (mergeSelf) {
234 
235             String value = dominant.getValue();
236             Object location = dominant.getInputLocation();
237             Map<String, String> attrs = dominant.getAttributes();
238             List<XmlNode> children = null;
239 
240             for (Map.Entry<String, String> attr : recessive.getAttributes().entrySet()) {
241                 String key = attr.getKey();
242                 if (isEmpty(attrs.get(key))) {
243                     if (attrs == dominant.getAttributes()) {
244                         attrs = new HashMap<>(attrs);
245                     }
246                     attrs.put(key, attr.getValue());
247                 }
248             }
249 
250             if (!recessive.getChildren().isEmpty()) {
251                 boolean mergeChildren = true;
252                 if (childMergeOverride != null) {
253                     mergeChildren = childMergeOverride;
254                 } else {
255                     String childMergeMode = attrs.get(CHILDREN_COMBINATION_MODE_ATTRIBUTE);
256                     if (CHILDREN_COMBINATION_APPEND.equals(childMergeMode)) {
257                         mergeChildren = false;
258                     }
259                 }
260 
261                 Map<String, Iterator<XmlNode>> commonChildren = new HashMap<>();
262                 Set<String> names =
263                         recessive.getChildren().stream().map(XmlNode::getName).collect(Collectors.toSet());
264                 for (String name : names) {
265                     List<XmlNode> dominantChildren = dominant.getChildren().stream()
266                             .filter(n -> n.getName().equals(name))
267                             .collect(Collectors.toList());
268                     if (!dominantChildren.isEmpty()) {
269                         commonChildren.put(name, dominantChildren.iterator());
270                     }
271                 }
272 
273                 String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
274 
275                 for (XmlNode recessiveChild : recessive.getChildren()) {
276                     String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
277 
278                     XmlNode childDom = null;
279                     if (!isEmpty(idValue)) {
280                         for (XmlNode dominantChild : dominant.getChildren()) {
281                             if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
282                                 childDom = dominantChild;
283                                 
284                                 mergeChildren = true;
285                             }
286                         }
287                     } else if (!isEmpty(keysValue)) {
288                         String[] keys = keysValue.split(",");
289                         Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
290                                 .collect(Collectors.toMap(
291                                         k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k))));
292 
293                         for (XmlNode dominantChild : dominant.getChildren()) {
294                             Map<String, Optional<String>> dominantKeyValues = Stream.of(keys)
295                                     .collect(Collectors.toMap(
296                                             k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k))));
297 
298                             if (recessiveKeyValues.equals(dominantKeyValues)) {
299                                 childDom = dominantChild;
300                                 
301                                 mergeChildren = true;
302                             }
303                         }
304                     } else {
305                         childDom = dominant.getChild(recessiveChild.getName());
306                     }
307 
308                     if (mergeChildren && childDom != null) {
309                         String name = recessiveChild.getName();
310                         Iterator<XmlNode> it =
311                                 commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream()
312                                                 .filter(n2 -> n2.getName().equals(n1))
313                                                 .collect(Collectors.toList()))
314                                         .filter(l -> !l.isEmpty())
315                                         .map(List::iterator)
316                                         .findFirst()
317                                         .orElse(null));
318                         if (it == null) {
319                             if (children == null) {
320                                 children = new ArrayList<>(dominant.getChildren());
321                             }
322                             children.add(recessiveChild);
323                         } else if (it.hasNext()) {
324                             XmlNode dominantChild = it.next();
325 
326                             String dominantChildCombinationMode =
327                                     dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
328                             if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
329                                 if (children == null) {
330                                     children = new ArrayList<>(dominant.getChildren());
331                                 }
332                                 children.remove(dominantChild);
333                             } else {
334                                 int idx = dominant.getChildren().indexOf(dominantChild);
335                                 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
336                                 if (merged != dominantChild) {
337                                     if (children == null) {
338                                         children = new ArrayList<>(dominant.getChildren());
339                                     }
340                                     children.set(idx, merged);
341                                 }
342                             }
343                         }
344                     } else {
345                         if (children == null) {
346                             children = new ArrayList<>(dominant.getChildren());
347                         }
348                         int idx = mergeChildren
349                                 ? children.size()
350                                 : recessive.getChildren().indexOf(recessiveChild);
351                         children.add(idx, recessiveChild);
352                     }
353                 }
354             }
355 
356             if (value != null || attrs != dominant.getAttributes() || children != null) {
357                 if (children == null) {
358                     children = dominant.getChildren();
359                 }
360                 if (!Objects.equals(value, dominant.getValue())
361                         || !Objects.equals(attrs, dominant.getAttributes())
362                         || !Objects.equals(children, dominant.getChildren())
363                         || !Objects.equals(location, dominant.getInputLocation())) {
364                     return new XmlNodeImpl(
365                             dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
366                 } else {
367                     return dominant;
368                 }
369             }
370         }
371         return dominant;
372     }
373 
374     
375 
376 
377 
378 
379 
380 
381 
382 
383 
384     public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
385         return merge(dominant, recessive, null);
386     }
387 
388     
389     
390     
391 
392     @Override
393     public boolean equals(Object o) {
394         if (this == o) {
395             return true;
396         }
397         if (o == null || getClass() != o.getClass()) {
398             return false;
399         }
400         XmlNodeImpl that = (XmlNodeImpl) o;
401         return Objects.equals(this.name, that.name)
402                 && Objects.equals(this.value, that.value)
403                 && Objects.equals(this.attributes, that.attributes)
404                 && Objects.equals(this.children, that.children);
405     }
406 
407     @Override
408     public int hashCode() {
409         return Objects.hash(name, value, attributes, children);
410     }
411 
412     @Override
413     public String toString() {
414         try {
415             return toStringXml();
416         } catch (XMLStreamException e) {
417             return toStringObject();
418         }
419     }
420 
421     public String toStringXml() throws XMLStreamException {
422         StringWriter writer = new StringWriter();
423         XmlNodeWriter.write(writer, this);
424         return writer.toString();
425     }
426 
427     public String toStringObject() {
428         StringBuilder sb = new StringBuilder();
429         sb.append("XmlNode[");
430         boolean w = false;
431         w = addToStringField(sb, prefix, o -> !o.isEmpty(), "prefix", w);
432         w = addToStringField(sb, namespaceUri, o -> !o.isEmpty(), "namespaceUri", w);
433         w = addToStringField(sb, name, o -> !o.isEmpty(), "name", w);
434         w = addToStringField(sb, value, o -> !o.isEmpty(), "value", w);
435         w = addToStringField(sb, attributes, o -> !o.isEmpty(), "attributes", w);
436         w = addToStringField(sb, children, o -> !o.isEmpty(), "children", w);
437         w = addToStringField(sb, location, Objects::nonNull, "location", w);
438         sb.append("]");
439         return sb.toString();
440     }
441 
442     private static <T> boolean addToStringField(StringBuilder sb, T o, Function<T, Boolean> p, String n, boolean w) {
443         if (!p.apply(o)) {
444             if (w) {
445                 sb.append(", ");
446             } else {
447                 w = true;
448             }
449             sb.append(n).append("='").append(o).append('\'');
450         }
451         return w;
452     }
453 
454     private static boolean isEmpty(String str) {
455         return str == null || str.isEmpty();
456     }
457 }