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 = dominant.getAttributes();
229 List<XmlNode> children = null;
230
231 for (Map.Entry<String, String> attr : recessive.getAttributes().entrySet()) {
232 String key = attr.getKey();
233 if (isEmpty(attrs.get(key))) {
234 if (attrs == dominant.getAttributes()) {
235 attrs = new HashMap<>(attrs);
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 = attrs.get(CHILDREN_COMBINATION_MODE_ATTRIBUTE);
247 if (CHILDREN_COMBINATION_APPEND.equals(childMergeMode)) {
248 mergeChildren = false;
249 }
250 }
251
252 Map<String, Iterator<XmlNode>> commonChildren = new HashMap<>();
253 Set<String> names =
254 recessive.getChildren().stream().map(XmlNode::getName).collect(Collectors.toSet());
255 for (String name : names) {
256 List<XmlNode> dominantChildren = dominant.getChildren().stream()
257 .filter(n -> n.getName().equals(name))
258 .collect(Collectors.toList());
259 if (dominantChildren.size() > 0) {
260 commonChildren.put(name, dominantChildren.iterator());
261 }
262 }
263
264 String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
265
266 for (XmlNode recessiveChild : recessive.getChildren()) {
267 String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
268
269 XmlNode childDom = null;
270 if (isNotEmpty(idValue)) {
271 for (XmlNode dominantChild : dominant.getChildren()) {
272 if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
273 childDom = dominantChild;
274
275 mergeChildren = true;
276 }
277 }
278 } else if (isNotEmpty(keysValue)) {
279 String[] keys = keysValue.split(",");
280 Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
281 .collect(Collectors.toMap(
282 k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k))));
283
284 for (XmlNode dominantChild : dominant.getChildren()) {
285 Map<String, Optional<String>> dominantKeyValues = Stream.of(keys)
286 .collect(Collectors.toMap(
287 k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k))));
288
289 if (recessiveKeyValues.equals(dominantKeyValues)) {
290 childDom = dominantChild;
291
292 mergeChildren = true;
293 }
294 }
295 } else {
296 childDom = dominant.getChild(recessiveChild.getName());
297 }
298
299 if (mergeChildren && childDom != null) {
300 String name = recessiveChild.getName();
301 Iterator<XmlNode> it =
302 commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream()
303 .filter(n2 -> n2.getName().equals(n1))
304 .collect(Collectors.toList()))
305 .filter(l -> !l.isEmpty())
306 .map(List::iterator)
307 .findFirst()
308 .orElse(null));
309 if (it == null) {
310 if (children == null) {
311 children = new ArrayList<>(dominant.getChildren());
312 }
313 children.add(recessiveChild);
314 } else if (it.hasNext()) {
315 XmlNode dominantChild = it.next();
316
317 String dominantChildCombinationMode =
318 dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
319 if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) {
320 if (children == null) {
321 children = new ArrayList<>(dominant.getChildren());
322 }
323 children.remove(dominantChild);
324 } else {
325 int idx = dominant.getChildren().indexOf(dominantChild);
326 XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride);
327 if (merged != dominantChild) {
328 if (children == null) {
329 children = new ArrayList<>(dominant.getChildren());
330 }
331 children.set(idx, merged);
332 }
333 }
334 }
335 } else {
336 if (children == null) {
337 children = new ArrayList<>(dominant.getChildren());
338 }
339 int idx = mergeChildren
340 ? children.size()
341 : recessive.getChildren().indexOf(recessiveChild);
342 children.add(idx, recessiveChild);
343 }
344 }
345 }
346
347 if (value != null || attrs != dominant.getAttributes() || children != null) {
348 if (children == null) {
349 children = dominant.getChildren();
350 }
351 return new XmlNodeImpl(
352 dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location);
353 }
354 }
355 return dominant;
356 }
357
358
359
360
361
362
363
364
365
366
367
368 public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
369 return merge(dominant, recessive, null);
370 }
371
372
373
374
375
376 @Override
377 public boolean equals(Object o) {
378 if (this == o) {
379 return true;
380 }
381 if (o == null || getClass() != o.getClass()) {
382 return false;
383 }
384 XmlNodeImpl that = (XmlNodeImpl) o;
385 return Objects.equals(this.name, that.name)
386 && Objects.equals(this.value, that.value)
387 && Objects.equals(this.attributes, that.attributes)
388 && Objects.equals(this.children, that.children);
389 }
390
391 @Override
392 public int hashCode() {
393 return Objects.hash(name, value, attributes, children);
394 }
395
396 @Override
397 public String toString() {
398 StringWriter writer = new StringWriter();
399 XmlNodeWriter.write(writer, this);
400 return writer.toString();
401 }
402
403 public String toUnescapedString() {
404 StringWriter writer = new StringWriter();
405 XMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
406 XmlNodeWriter.write(xmlWriter, this, false);
407 return writer.toString();
408 }
409
410 private static boolean isNotEmpty(String str) {
411 return ((str != null) && (str.length() > 0));
412 }
413
414 private static boolean isEmpty(String str) {
415 return ((str == null) || (str.length() == 0));
416 }
417 }