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 .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 int recessiveChildIndex = 0;
276 for (XmlNode recessiveChild : recessive.getChildren()) {
277 String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
278
279 XmlNode childDom = null;
280 if (!isEmpty(idValue)) {
281 for (XmlNode dominantChild : dominant.getChildren()) {
282 if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
283 childDom = dominantChild;
284
285 mergeChildren = true;
286 }
287 }
288 } else if (!isEmpty(keysValue)) {
289 String[] keys = keysValue.split(",");
290 Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
291 .collect(Collectors.toMap(
292 k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k))));
293
294 for (XmlNode dominantChild : dominant.getChildren()) {
295 Map<String, Optional<String>> dominantKeyValues = Stream.of(keys)
296 .collect(Collectors.toMap(
297 k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k))));
298
299 if (recessiveKeyValues.equals(dominantKeyValues)) {
300 childDom = dominantChild;
301
302 mergeChildren = true;
303 }
304 }
305 } else {
306 childDom = dominant.getChild(recessiveChild.getName());
307 }
308
309 if (mergeChildren && childDom != null) {
310 String name = recessiveChild.getName();
311 Iterator<XmlNode> it =
312 commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream()
313 .filter(n2 -> n2.getName().equals(n1))
314 .collect(Collectors.toList()))
315 .filter(l -> !l.isEmpty())
316 .map(List::iterator)
317 .findFirst()
318 .orElse(null));
319 if (it == null) {
320 if (children == null) {
321 children = new ArrayList<>(dominant.getChildren());
322 }
323 children.add(recessiveChild);
324 } else if (it.hasNext()) {
325 XmlNode dominantChild = it.next();
326
327 String dominantChildCombinationMode =
328 dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
329 if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
330 if (children == null) {
331 children = new ArrayList<>(dominant.getChildren());
332 }
333 children.remove(dominantChild);
334 } else {
335 int idx = dominant.getChildren().indexOf(dominantChild);
336 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
337 if (merged != dominantChild) {
338 if (children == null) {
339 children = new ArrayList<>(dominant.getChildren());
340 }
341 children.set(idx, merged);
342 }
343 }
344 }
345 } else {
346 if (children == null) {
347 children = new ArrayList<>(dominant.getChildren());
348 }
349 int idx = mergeChildren ? children.size() : recessiveChildIndex;
350 children.add(idx, recessiveChild);
351 }
352 recessiveChildIndex++;
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 }