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