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 }