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