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 }