1 package org.codehaus.plexus.util.xml;
2
3 /*
4 * Copyright The Codehaus Foundation.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 import java.io.IOException;
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import org.codehaus.plexus.util.xml.pull.XmlSerializer;
23
24 /** @author Jason van Zyl */
25 public class Xpp3DomUtils {
26 public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
27
28 public static final String CHILDREN_COMBINATION_MERGE = "merge";
29
30 public static final String CHILDREN_COMBINATION_APPEND = "append";
31
32 /**
33 * This default mode for combining children DOMs during merge means that where element names match, the process will
34 * try to merge the element data, rather than putting the dominant and recessive elements (which share the same
35 * element name) as siblings in the resulting DOM.
36 */
37 public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
38
39 public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
40
41 public static final String SELF_COMBINATION_OVERRIDE = "override";
42
43 public static final String SELF_COMBINATION_MERGE = "merge";
44
45 /**
46 * In case of complex XML structures, combining can be done based on id.
47 *
48 * @since 3.0.22
49 */
50 public static final String ID_COMBINATION_MODE_ATTRIBUTE = "combine.id";
51
52 /**
53 * In case of complex XML structures, combining can be done based on keys.
54 * This is a comma separated list of attribute names.
55 *
56 * @since 3.4.0
57 */
58 public static final String KEYS_COMBINATION_MODE_ATTRIBUTE = "combine.keys";
59
60 /**
61 * This default mode for combining a DOM node during merge means that where element names match, the process will
62 * try to merge the element attributes and values, rather than overriding the recessive element completely with the
63 * dominant one. This means that wherever the dominant element doesn't provide the value or a particular attribute,
64 * that value or attribute will be set from the recessive DOM node.
65 */
66 public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
67
68 public void writeToSerializer(String namespace, XmlSerializer serializer, Xpp3Dom dom) throws IOException {
69 // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new
70 // document - not the desired behaviour!
71 SerializerXMLWriter xmlWriter = new SerializerXMLWriter(namespace, serializer);
72 Xpp3DomWriter.write(xmlWriter, dom);
73 if (xmlWriter.getExceptions().size() > 0) {
74 throw (IOException) xmlWriter.getExceptions().get(0);
75 }
76 }
77
78 /**
79 * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.<p>
80 * The algorithm is as follows:
81 * <ol>
82 * <li> if the recessive DOM is null, there is nothing to do... return.</li>
83 * <li> Determine whether the dominant node will suppress the recessive one (flag=mergeSelf).
84 * <ol type="A">
85 * <li> retrieve the 'combine.self' attribute on the dominant node, and try to match against 'override'...
86 * if it matches 'override', then set mergeSelf == false...the dominant node suppresses the recessive one
87 * completely.</li>
88 * <li> otherwise, use the default value for mergeSelf, which is true...this is the same as specifying
89 * 'combine.self' == 'merge' as an attribute of the dominant root node.</li>
90 * </ol></li>
91 * <li> If mergeSelf == true
92 * <ol type="A">
93 * <li> Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as
94 * siblings (flag=mergeChildren).
95 * <ol type="i">
96 * <li> if childMergeOverride is set (non-null), use that value (true/false)</li>
97 * <li> retrieve the 'combine.children' attribute on the dominant node, and try to match against
98 * 'append'...</li>
99 * <li> if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
100 * siblings of the dominant children.</li>
101 * <li> otherwise, use the default value for mergeChildren, which is true...this is the same as specifying
102 * 'combine.children' == 'merge' as an attribute on the dominant root node.</li>
103 * </ol></li>
104 * <li> Iterate through the recessive children, and:
105 * <ol type="i">
106 * <li> if 'combine.id' is set and there is a corresponding dominant child (matched by value of 'combine.id'),
107 * merge the two.</li>
108 * <li> if 'combine.keys' is set and there is a corresponding dominant child (matched by value of key elements),
109 * merge the two.</li>
110 * <li> if mergeChildren == true and there is a corresponding dominant child (matched by element name),
111 * merge the two.</li>
112 * <li> otherwise, add the recessive child as a new child on the dominant root node.</li>
113 * </ol></li>
114 * </ol></li>
115 * </ol>
116 */
117 private static void mergeIntoXpp3Dom(Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride) {
118 // TODO: share this as some sort of assembler, implement a walk interface?
119 if (recessive == null) {
120 return;
121 }
122
123 boolean mergeSelf = true;
124
125 String selfMergeMode = dominant.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE);
126
127 if (isNotEmpty(selfMergeMode) && SELF_COMBINATION_OVERRIDE.equals(selfMergeMode)) {
128 mergeSelf = false;
129 }
130
131 if (mergeSelf) {
132 String[] recessiveAttrs = recessive.getAttributeNames();
133 for (String attr : recessiveAttrs) {
134 if (isEmpty(dominant.getAttribute(attr))) {
135 dominant.setAttribute(attr, recessive.getAttribute(attr));
136 }
137 }
138
139 boolean mergeChildren = true;
140
141 if (childMergeOverride != null) {
142 mergeChildren = childMergeOverride;
143 } else {
144 String childMergeMode = dominant.getAttribute(CHILDREN_COMBINATION_MODE_ATTRIBUTE);
145
146 if (isNotEmpty(childMergeMode) && CHILDREN_COMBINATION_APPEND.equals(childMergeMode)) {
147 mergeChildren = false;
148 }
149 }
150
151 final String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE);
152
153 Xpp3Dom[] children = recessive.getChildren();
154 for (Xpp3Dom recessiveChild : children) {
155 String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
156
157 Xpp3Dom childDom = null;
158 if (isNotEmpty(idValue)) {
159 for (Xpp3Dom dominantChild : dominant.getChildren()) {
160 if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
161 childDom = dominantChild;
162 // we have a match, so don't append but merge
163 mergeChildren = true;
164 }
165 }
166 } else if (isNotEmpty(keysValue)) {
167 String[] keys = keysValue.split(",");
168 Map<String, String> recessiveKeyValues = new HashMap<>(keys.length);
169 for (String key : keys) {
170 recessiveKeyValues.put(key, recessiveChild.getAttribute(key));
171 }
172
173 for (Xpp3Dom dominantChild : dominant.getChildren()) {
174 Map<String, String> dominantKeyValues = new HashMap<>(keys.length);
175 for (String key : keys) {
176 dominantKeyValues.put(key, dominantChild.getAttribute(key));
177 }
178
179 if (recessiveKeyValues.equals(dominantKeyValues)) {
180 childDom = dominantChild;
181 // we have a match, so don't append but merge
182 mergeChildren = true;
183 }
184 }
185
186 } else {
187 childDom = dominant.getChild(recessiveChild.getName());
188 }
189
190 if (mergeChildren && childDom != null) {
191 mergeIntoXpp3Dom(childDom, recessiveChild, childMergeOverride);
192 } else {
193 dominant.addChild(new Xpp3Dom(recessiveChild));
194 }
195 }
196 }
197 }
198
199 /**
200 * Merge two DOMs, with one having dominance in the case of collision.
201 *
202 * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
203 * @see #SELF_COMBINATION_MODE_ATTRIBUTE
204 * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
205 * @param recessive The recessive DOM, which will be merged into the dominant DOM
206 * @param childMergeOverride Overrides attribute flags to force merging or appending of child elements into the
207 * dominant DOM
208 * @return merged DOM
209 */
210 public static Xpp3Dom mergeXpp3Dom(Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride) {
211 if (dominant != null) {
212 mergeIntoXpp3Dom(dominant, recessive, childMergeOverride);
213 return dominant;
214 }
215 return recessive;
216 }
217
218 /**
219 * Merge two DOMs, with one having dominance in the case of collision. Merge mechanisms (vs. override for nodes, or
220 * vs. append for children) is determined by attributes of the dominant root node.
221 *
222 * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
223 * @see #SELF_COMBINATION_MODE_ATTRIBUTE
224 * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
225 * @param recessive The recessive DOM, which will be merged into the dominant DOM
226 * @return merged DOM
227 */
228 public static Xpp3Dom mergeXpp3Dom(Xpp3Dom dominant, Xpp3Dom recessive) {
229 if (dominant != null) {
230 mergeIntoXpp3Dom(dominant, recessive, null);
231 return dominant;
232 }
233 return recessive;
234 }
235
236 /**
237 * @deprecated Use {@link org.codehaus.plexus.util.StringUtils#isNotEmpty(String)} instead
238 */
239 @Deprecated
240 public static boolean isNotEmpty(String str) {
241 return (str != null && str.length() > 0);
242 }
243
244 /**
245 * @deprecated Use {@link org.codehaus.plexus.util.StringUtils#isEmpty(String)} instead
246 */
247 @Deprecated
248 public static boolean isEmpty(String str) {
249 return (str == null || str.length() == 0);
250 }
251 }