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