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                 return new XmlNodeImpl(
361                         dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
362             }
363         }
364         return dominant;
365     }
366 
367     
368 
369 
370 
371 
372 
373 
374 
375 
376 
377     public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
378         return merge(dominant, recessive, null);
379     }
380 
381     
382     
383     
384 
385     @Override
386     public boolean equals(Object o) {
387         if (this == o) {
388             return true;
389         }
390         if (o == null || getClass() != o.getClass()) {
391             return false;
392         }
393         XmlNodeImpl that = (XmlNodeImpl) o;
394         return Objects.equals(this.name, that.name)
395                 && Objects.equals(this.value, that.value)
396                 && Objects.equals(this.attributes, that.attributes)
397                 && Objects.equals(this.children, that.children);
398     }
399 
400     @Override
401     public int hashCode() {
402         return Objects.hash(name, value, attributes, children);
403     }
404 
405     @Override
406     public String toString() {
407         try {
408             return toStringXml();
409         } catch (XMLStreamException e) {
410             return toStringObject();
411         }
412     }
413 
414     public String toStringXml() throws XMLStreamException {
415         StringWriter writer = new StringWriter();
416         XmlNodeWriter.write(writer, this);
417         return writer.toString();
418     }
419 
420     public String toStringObject() {
421         StringBuilder sb = new StringBuilder();
422         sb.append("XmlNode[");
423         boolean w = false;
424         w = addToStringField(sb, prefix, o -> !o.isEmpty(), "prefix", w);
425         w = addToStringField(sb, namespaceUri, o -> !o.isEmpty(), "namespaceUri", w);
426         w = addToStringField(sb, name, o -> !o.isEmpty(), "name", w);
427         w = addToStringField(sb, value, o -> !o.isEmpty(), "value", w);
428         w = addToStringField(sb, attributes, o -> !o.isEmpty(), "attributes", w);
429         w = addToStringField(sb, children, o -> !o.isEmpty(), "children", w);
430         w = addToStringField(sb, location, Objects::nonNull, "location", w);
431         sb.append("]");
432         return sb.toString();
433     }
434 
435     private static <T> boolean addToStringField(StringBuilder sb, T o, Function<T, Boolean> p, String n, boolean w) {
436         if (!p.apply(o)) {
437             if (w) {
438                 sb.append(", ");
439             } else {
440                 w = true;
441             }
442             sb.append(n).append("='").append(o).append('\'');
443         }
444         return w;
445     }
446 
447     private static boolean isEmpty(String str) {
448         return str == null || str.isEmpty();
449     }
450 }