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 java.io.IOException;
22 import java.io.Serializable;
23 import java.io.StringWriter;
24 import java.util.ArrayList;
25 import java.util.Collections;
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.stream.Collectors;
35 import java.util.stream.Stream;
36
37 import org.apache.maven.api.xml.XmlNode;
38 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
39 import org.codehaus.plexus.util.xml.SerializerXMLWriter;
40 import org.codehaus.plexus.util.xml.XMLWriter;
41 import org.codehaus.plexus.util.xml.pull.XmlSerializer;
42
43
44
45
46 public class XmlNodeImpl implements Serializable, XmlNode {
47 private static final long serialVersionUID = 2567894443061173996L;
48
49 protected final String name;
50
51 protected final String value;
52
53 protected final Map<String, String> attributes;
54
55 protected final List<XmlNode> children;
56
57 protected final Object location;
58
59 public XmlNodeImpl(String name) {
60 this(name, null, null, null, null);
61 }
62
63 public XmlNodeImpl(String name, String value) {
64 this(name, value, null, null, null);
65 }
66
67 public XmlNodeImpl(XmlNode from, String name) {
68 this(name, from.getValue(), from.getAttributes(), from.getChildren(), from.getInputLocation());
69 }
70
71 public XmlNodeImpl(
72 String name, String value, Map<String, String> attributes, List<XmlNode> children, Object location) {
73 this.name = Objects.requireNonNull(name);
74 this.value = value;
75 this.attributes =
76 attributes != null ? Collections.unmodifiableMap(new HashMap<>(attributes)) : Collections.emptyMap();
77 this.children =
78 children != null ? Collections.unmodifiableList(new ArrayList<>(children)) : Collections.emptyList();
79 this.location = location;
80 }
81
82 @Override
83 public XmlNode merge(XmlNode source, Boolean childMergeOverride) {
84 return merge(this, source, childMergeOverride);
85 }
86
87 public XmlNode clone() {
88 return this;
89 }
90
91
92
93
94
95 public String getName() {
96 return name;
97 }
98
99
100
101
102
103 public String getValue() {
104 return value;
105 }
106
107
108
109
110
111 @Override
112 public Map<String, String> getAttributes() {
113 return attributes;
114 }
115
116 public String getAttribute(String name) {
117 return attributes.get(name);
118 }
119
120
121
122
123
124 public XmlNode getChild(String name) {
125 if (name != null) {
126 ListIterator<XmlNode> it = children.listIterator(children.size());
127 while (it.hasPrevious()) {
128 XmlNode child = it.previous();
129 if (name.equals(child.getName())) {
130 return child;
131 }
132 }
133 }
134 return null;
135 }
136
137 public List<XmlNode> getChildren() {
138 return children;
139 }
140
141 public int getChildCount() {
142 return children.size();
143 }
144
145
146
147
148
149
150
151
152
153 public Object getInputLocation() {
154 return location;
155 }
156
157
158
159
160
161 public void writeToSerializer(String namespace, XmlSerializer serializer) throws IOException {
162
163
164 SerializerXMLWriter xmlWriter = new SerializerXMLWriter(namespace, serializer);
165 XmlNodeWriter.write(xmlWriter, this);
166 if (xmlWriter.getExceptions().size() > 0) {
167 throw (IOException) xmlWriter.getExceptions().get(0);
168 }
169 }
170
171
172
173
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 @SuppressWarnings("checkstyle:MethodLength")
207 public static XmlNode merge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride) {
208
209 if (recessive == null) {
210 return dominant;
211 }
212 if (dominant == null) {
213 return recessive;
214 }
215
216 boolean mergeSelf = true;
217
218 String selfMergeMode = dominant.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
219
220 if (SELF_COMBINATION_OVERRIDE.equals(selfMergeMode)) {
221 mergeSelf = false;
222 }
223
224 if (mergeSelf) {
225
226 String value = dominant.getValue();
227 Object location = dominant.getInputLocation();
228 Map<String, String> attrs = null;
229 List<XmlNode> children = null;
230
231 for (Map.Entry<String, String> attr : recessive.getAttributes().entrySet()) {
232 String key = attr.getKey();
233 if (isEmpty(dominant.getAttribute(key)) && !SELF_COMBINATION_MODE_ATTRIBUTE.equals(key)) {
234 if (attrs == null) {
235 attrs = new HashMap<>();
236 }
237 attrs.put(key, attr.getValue());
238 }
239 }
240
241 if (recessive.getChildren().size() > 0) {
242 boolean mergeChildren = true;
243 if (childMergeOverride != null) {
244 mergeChildren = childMergeOverride;
245 } else {
246 String childMergeMode = dominant.getAttribute(CHILDREN_COMBINATION_MODE_ATTRIBUTE);
247 if (CHILDREN_COMBINATION_APPEND.equals(childMergeMode)) {
248 mergeChildren = false;
249 }
250 }
251
252 String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
253
254 for (XmlNode recessiveChild : recessive.getChildren()) {
255 String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
256
257 XmlNode childDom = null;
258 if (isNotEmpty(idValue)) {
259 for (XmlNode dominantChild : dominant.getChildren()) {
260 if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
261 childDom = dominantChild;
262
263 mergeChildren = true;
264 }
265 }
266 } else if (isNotEmpty(keysValue)) {
267 String[] keys = keysValue.split(",");
268 Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
269 .collect(Collectors.toMap(
270 k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k))));
271
272 for (XmlNode dominantChild : dominant.getChildren()) {
273 Map<String, Optional<String>> dominantKeyValues = Stream.of(keys)
274 .collect(Collectors.toMap(
275 k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k))));
276
277 if (recessiveKeyValues.equals(dominantKeyValues)) {
278 childDom = dominantChild;
279
280 mergeChildren = true;
281 }
282 }
283 } else {
284 childDom = dominant.getChild(recessiveChild.getName());
285 }
286
287 if (mergeChildren && childDom != null) {
288 Map<String, Iterator<XmlNode>> commonChildren = new HashMap<>();
289 Set<String> names = recessive.getChildren().stream()
290 .map(XmlNode::getName)
291 .collect(Collectors.toSet());
292 for (String name : names) {
293 List<XmlNode> dominantChildren = dominant.getChildren().stream()
294 .filter(n -> n.getName().equals(name))
295 .collect(Collectors.toList());
296 if (dominantChildren.size() > 0) {
297 commonChildren.put(name, dominantChildren.iterator());
298 }
299 }
300
301 String name = recessiveChild.getName();
302 Iterator<XmlNode> it =
303 commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream()
304 .filter(n2 -> n2.getName().equals(n1))
305 .collect(Collectors.toList()))
306 .filter(l -> !l.isEmpty())
307 .map(List::iterator)
308 .findFirst()
309 .orElse(null));
310 if (it == null) {
311 if (children == null) {
312 children = new ArrayList<>(dominant.getChildren());
313 }
314 children.add(recessiveChild);
315 } else if (it.hasNext()) {
316 XmlNode dominantChild = it.next();
317
318 String dominantChildCombinationMode =
319 dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
320 if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
321 if (children == null) {
322 children = new ArrayList<>(dominant.getChildren());
323 }
324 children.remove(dominantChild);
325 } else {
326 int idx = dominant.getChildren().indexOf(dominantChild);
327 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
328 if (merged != dominantChild) {
329 if (children == null) {
330 children = new ArrayList<>(dominant.getChildren());
331 }
332 children.set(idx, merged);
333 }
334 }
335 }
336 } else {
337 if (children == null) {
338 children = new ArrayList<>(dominant.getChildren());
339 }
340 int idx = mergeChildren
341 ? children.size()
342 : recessive.getChildren().indexOf(recessiveChild);
343 children.add(idx, recessiveChild);
344 }
345 }
346 }
347
348 if (value != null || attrs != null || children != null) {
349 if (attrs != null) {
350 Map<String, String> nattrs = attrs;
351 attrs = new HashMap<>(dominant.getAttributes());
352 attrs.putAll(nattrs);
353 } else {
354 attrs = dominant.getAttributes();
355 }
356 if (children == null) {
357 children = dominant.getChildren();
358 }
359 return new XmlNodeImpl(
360 dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
361 }
362 }
363 return dominant;
364 }
365
366
367
368
369
370
371
372
373
374
375
376 public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
377 return merge(dominant, recessive, null);
378 }
379
380
381
382
383
384 @Override
385 public boolean equals(Object o) {
386 if (this == o) {
387 return true;
388 }
389 if (o == null || getClass() != o.getClass()) {
390 return false;
391 }
392 XmlNodeImpl that = (XmlNodeImpl) o;
393 return Objects.equals(this.name, that.name)
394 && Objects.equals(this.value, that.value)
395 && Objects.equals(this.attributes, that.attributes)
396 && Objects.equals(this.children, that.children);
397 }
398
399 @Override
400 public int hashCode() {
401 return Objects.hash(name, value, attributes, children);
402 }
403
404 @Override
405 public String toString() {
406 StringWriter writer = new StringWriter();
407 XmlNodeWriter.write(writer, this);
408 return writer.toString();
409 }
410
411 public String toUnescapedString() {
412 StringWriter writer = new StringWriter();
413 XMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
414 XmlNodeWriter.write(xmlWriter, this, false);
415 return writer.toString();
416 }
417
418 private static boolean isNotEmpty(String str) {
419 return ((str != null) && (str.length() > 0));
420 }
421
422 private static boolean isEmpty(String str) {
423 return ((str == null) || (str.length() == 0));
424 }
425 }