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.Serial;
24 import java.io.Serializable;
25 import java.io.StringWriter;
26 import java.util.ArrayList;
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 @Serial
46 private static final long serialVersionUID = 2567894443061173996L;
47
48 protected final String prefix;
49
50 protected final String namespaceUri;
51
52 protected final String name;
53
54 protected final String value;
55
56 protected final Map<String, String> attributes;
57
58 protected final List<XmlNode> children;
59
60 protected final Object location;
61
62 public XmlNodeImpl(String name) {
63 this(name, null, null, null, null);
64 }
65
66 public XmlNodeImpl(String name, String value) {
67 this(name, value, null, null, null);
68 }
69
70 public XmlNodeImpl(XmlNode from, String name) {
71 this(name, from.getValue(), from.getAttributes(), from.getChildren(), from.getInputLocation());
72 }
73
74 public XmlNodeImpl(
75 String name, String value, Map<String, String> attributes, List<XmlNode> children, Object location) {
76 this("", "", name, value, attributes, children, location);
77 }
78
79 public XmlNodeImpl(
80 String prefix,
81 String namespaceUri,
82 String name,
83 String value,
84 Map<String, String> attributes,
85 List<XmlNode> children,
86 Object location) {
87 this.prefix = prefix == null ? "" : prefix;
88 this.namespaceUri = namespaceUri == null ? "" : namespaceUri;
89 this.name = Objects.requireNonNull(name);
90 this.value = value;
91 this.attributes = ImmutableCollections.copy(attributes);
92 this.children = ImmutableCollections.copy(children);
93 this.location = location;
94 }
95
96 @Override
97 public XmlNode merge(XmlNode source, Boolean childMergeOverride) {
98 return merge(this, source, childMergeOverride);
99 }
100
101
102
103
104
105 @Override
106 public String getPrefix() {
107 return prefix;
108 }
109
110 @Override
111 public String getNamespaceUri() {
112 return namespaceUri;
113 }
114
115 @Override
116 public String getName() {
117 return name;
118 }
119
120
121
122
123
124 public String getValue() {
125 return value;
126 }
127
128
129
130
131
132 @Override
133 public Map<String, String> getAttributes() {
134 return attributes;
135 }
136
137 public String getAttribute(String name) {
138 return attributes.get(name);
139 }
140
141
142
143
144
145 public XmlNode getChild(String name) {
146 if (name != null) {
147 ListIterator<XmlNode> it = children.listIterator(children.size());
148 while (it.hasPrevious()) {
149 XmlNode child = it.previous();
150 if (name.equals(child.getName())) {
151 return child;
152 }
153 }
154 }
155 return null;
156 }
157
158 public List<XmlNode> getChildren() {
159 return children;
160 }
161
162 public int getChildCount() {
163 return children.size();
164 }
165
166
167
168
169
170
171
172
173
174 public Object getInputLocation() {
175 return location;
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
216
217 @SuppressWarnings("checkstyle:MethodLength")
218 public static XmlNode merge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride) {
219
220 if (recessive == null) {
221 return dominant;
222 }
223 if (dominant == null) {
224 return recessive;
225 }
226
227 boolean mergeSelf = true;
228
229 String selfMergeMode = getSelfCombinationMode(dominant);
230
231 if (SELF_COMBINATION_OVERRIDE.equals(selfMergeMode)) {
232 mergeSelf = false;
233 }
234
235 if (mergeSelf) {
236
237 String value = dominant.getValue();
238 Object location = dominant.getInputLocation();
239 Map<String, String> attrs = dominant.getAttributes();
240 List<XmlNode> children = null;
241
242 for (Map.Entry<String, String> attr : recessive.getAttributes().entrySet()) {
243 String key = attr.getKey();
244 if (isEmpty(attrs.get(key))) {
245 if (attrs == dominant.getAttributes()) {
246 attrs = new HashMap<>(attrs);
247 }
248 attrs.put(key, attr.getValue());
249 }
250 }
251
252 if (!recessive.getChildren().isEmpty()) {
253 boolean mergeChildren = true;
254 if (childMergeOverride != null) {
255 mergeChildren = childMergeOverride;
256 } else {
257 String childCombinationMode = getChildCombinationMode(attrs);
258 if (CHILDREN_COMBINATION_APPEND.equals(childCombinationMode)) {
259 mergeChildren = false;
260 }
261 }
262
263 Map<String, Iterator<XmlNode>> commonChildren = new HashMap<>();
264 Set<String> names =
265 recessive.getChildren().stream().map(XmlNode::getName).collect(Collectors.toSet());
266 for (String name : names) {
267 List<XmlNode> dominantChildren = dominant.getChildren().stream()
268 .filter(n -> n.getName().equals(name))
269 .toList();
270 if (!dominantChildren.isEmpty()) {
271 commonChildren.put(name, dominantChildren.iterator());
272 }
273 }
274
275 String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
276
277 int recessiveChildIndex = 0;
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 = getSelfCombinationMode(dominantChild);
330 if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
331 if (children == null) {
332 children = new ArrayList<>(dominant.getChildren());
333 }
334 children.remove(dominantChild);
335 } else {
336 int idx = dominant.getChildren().indexOf(dominantChild);
337 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
338 if (merged != dominantChild) {
339 if (children == null) {
340 children = new ArrayList<>(dominant.getChildren());
341 }
342 children.set(idx, merged);
343 }
344 }
345 }
346 } else {
347 if (children == null) {
348 children = new ArrayList<>(dominant.getChildren());
349 }
350 int idx = mergeChildren ? children.size() : recessiveChildIndex;
351 children.add(idx, recessiveChild);
352 }
353 recessiveChildIndex++;
354 }
355 }
356
357 if (value != null || attrs != dominant.getAttributes() || children != null) {
358 if (children == null) {
359 children = dominant.getChildren();
360 }
361 if (!Objects.equals(value, dominant.getValue())
362 || !Objects.equals(attrs, dominant.getAttributes())
363 || !Objects.equals(children, dominant.getChildren())
364 || !Objects.equals(location, dominant.getInputLocation())) {
365 return new XmlNodeImpl(
366 dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
367 } else {
368 return dominant;
369 }
370 }
371 }
372 return dominant;
373 }
374
375 private static String getChildCombinationMode(Map<String, String> attrs) {
376 String attribute = attrs.get(CHILDREN_COMBINATION_MODE_ATTRIBUTE);
377 if (CHILDREN_COMBINATION_APPEND.equals(attribute) || CHILDREN_COMBINATION_MERGE.equals(attribute)) {
378 return attribute;
379 }
380 return DEFAULT_CHILDREN_COMBINATION_MODE;
381 }
382
383 private static String getSelfCombinationMode(XmlNode node) {
384 String attribute = node.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
385 if (SELF_COMBINATION_OVERRIDE.equals(attribute)
386 || SELF_COMBINATION_MERGE.equals(attribute)
387 || SELF_COMBINATION_REMOVE.equals(attribute)) {
388 return attribute;
389 }
390 return DEFAULT_SELF_COMBINATION_MODE;
391 }
392
393
394
395
396
397
398
399
400
401
402
403 public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
404 return merge(dominant, recessive, null);
405 }
406
407
408
409
410
411 @Override
412 public boolean equals(Object o) {
413 if (this == o) {
414 return true;
415 }
416 if (o == null || getClass() != o.getClass()) {
417 return false;
418 }
419 XmlNodeImpl that = (XmlNodeImpl) o;
420 return Objects.equals(this.name, that.name)
421 && Objects.equals(this.value, that.value)
422 && Objects.equals(this.attributes, that.attributes)
423 && Objects.equals(this.children, that.children);
424 }
425
426 @Override
427 public int hashCode() {
428 return Objects.hash(name, value, attributes, children);
429 }
430
431 @Override
432 public String toString() {
433 try {
434 return toStringXml();
435 } catch (XMLStreamException e) {
436 return toStringObject();
437 }
438 }
439
440 public String toStringXml() throws XMLStreamException {
441 StringWriter writer = new StringWriter();
442 XmlNodeWriter.write(writer, this);
443 return writer.toString();
444 }
445
446 public String toStringObject() {
447 StringBuilder sb = new StringBuilder();
448 sb.append("XmlNode[");
449 boolean w = false;
450 w = addToStringField(sb, prefix, o -> !o.isEmpty(), "prefix", w);
451 w = addToStringField(sb, namespaceUri, o -> !o.isEmpty(), "namespaceUri", w);
452 w = addToStringField(sb, name, o -> !o.isEmpty(), "name", w);
453 w = addToStringField(sb, value, o -> !o.isEmpty(), "value", w);
454 w = addToStringField(sb, attributes, o -> !o.isEmpty(), "attributes", w);
455 w = addToStringField(sb, children, o -> !o.isEmpty(), "children", w);
456 w = addToStringField(sb, location, Objects::nonNull, "location", w);
457 sb.append("]");
458 return sb.toString();
459 }
460
461 private static <T> boolean addToStringField(StringBuilder sb, T o, Function<T, Boolean> p, String n, boolean w) {
462 if (!p.apply(o)) {
463 if (w) {
464 sb.append(", ");
465 } else {
466 w = true;
467 }
468 sb.append(n).append("='").append(o).append('\'');
469 }
470 return w;
471 }
472
473 private static boolean isEmpty(String str) {
474 return str == null || str.isEmpty();
475 }
476 }