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 }