1 package org.codehaus.plexus.util.xml;
2
3 import java.io.IOException;
4 import java.util.HashMap;
5 import java.util.Map;
6
7 /*
8 * Copyright The Codehaus Foundation.
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 */
22
23 import org.codehaus.plexus.util.xml.pull.XmlSerializer;
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> Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as
99 * siblings (flag=mergeChildren).
100 * <ol type="i">
101 * <li> if childMergeOverride is set (non-null), use that value (true/false)</li>
102 * <li> retrieve the 'combine.children' attribute on the dominant node, and try to match against
103 * 'append'...</li>
104 * <li> if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
105 * siblings of the dominant children.</li>
106 * <li> otherwise, use the default value for mergeChildren, which is true...this is the same as specifying
107 * 'combine.children' == 'merge' as an attribute on the dominant root node.</li>
108 * </ol></li>
109 * <li> Iterate through the recessive children, and:
110 * <ol type="i">
111 * <li> if 'combine.id' is set and there is a corresponding dominant child (matched by value of 'combine.id'),
112 * merge the two.</li>
113 * <li> if 'combine.keys' is set and there is a corresponding dominant child (matched by value of key elements),
114 * merge the two.</li>
115 * <li> if mergeChildren == true and there is a corresponding dominant child (matched by element name),
116 * merge the two.</li>
117 * <li> otherwise, add the recessive child as a new child on the dominant root node.</li>
118 * </ol></li>
119 * </ol></li>
120 * </ol>
121 */
122 private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
123 {
124 // TODO: share this as some sort of assembler, implement a walk interface?
125 if ( recessive == null )
126 {
127 return;
128 }
129
130 boolean mergeSelf = true;
131
132 String selfMergeMode = dominant.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
133
134 if ( isNotEmpty( selfMergeMode ) && SELF_COMBINATION_OVERRIDE.equals( selfMergeMode ) )
135 {
136 mergeSelf = false;
137 }
138
139 if ( mergeSelf )
140 {
141 String[] recessiveAttrs = recessive.getAttributeNames();
142 for ( String attr : recessiveAttrs )
143 {
144 if ( isEmpty( dominant.getAttribute( attr ) ) )
145 {
146 dominant.setAttribute( attr, recessive.getAttribute( attr ) );
147 }
148 }
149
150 boolean mergeChildren = true;
151
152 if ( childMergeOverride != null )
153 {
154 mergeChildren = childMergeOverride;
155 }
156 else
157 {
158 String childMergeMode = dominant.getAttribute( CHILDREN_COMBINATION_MODE_ATTRIBUTE );
159
160 if ( isNotEmpty( childMergeMode ) && CHILDREN_COMBINATION_APPEND.equals( childMergeMode ) )
161 {
162 mergeChildren = false;
163 }
164 }
165
166 final String keysValue = recessive.getAttribute( KEYS_COMBINATION_MODE_ATTRIBUTE );
167
168 Xpp3Dom[] children = recessive.getChildren();
169 for ( Xpp3Dom recessiveChild : children )
170 {
171 String idValue = recessiveChild.getAttribute( ID_COMBINATION_MODE_ATTRIBUTE );
172
173 Xpp3Dom childDom = null;
174 if ( isNotEmpty( idValue ) )
175 {
176 for ( Xpp3Dom dominantChild : dominant.getChildren() )
177 {
178 if ( idValue.equals( dominantChild.getAttribute( ID_COMBINATION_MODE_ATTRIBUTE ) ) )
179 {
180 childDom = dominantChild;
181 // we have a match, so don't append but merge
182 mergeChildren = true;
183 }
184 }
185 }
186 else if ( isNotEmpty( keysValue ) )
187 {
188 String[] keys = keysValue.split( "," );
189 Map<String, String> recessiveKeyValues = new HashMap<>( keys.length );
190 for ( String key : keys )
191 {
192 recessiveKeyValues.put( key, recessiveChild.getAttribute( key ) );
193 }
194
195 for ( Xpp3Dom dominantChild : dominant.getChildren() )
196 {
197 Map<String, String> dominantKeyValues = new HashMap<>( keys.length );
198 for ( String key : keys )
199 {
200 dominantKeyValues.put( key, dominantChild.getAttribute( key ) );
201 }
202
203 if ( recessiveKeyValues.equals( dominantKeyValues ) )
204 {
205 childDom = dominantChild;
206 // we have a match, so don't append but merge
207 mergeChildren = true;
208 }
209 }
210
211 }
212 else
213 {
214 childDom = dominant.getChild( recessiveChild.getName() );
215 }
216
217 if ( mergeChildren && childDom != null )
218 {
219 mergeIntoXpp3Dom( childDom, recessiveChild, childMergeOverride );
220 }
221 else
222 {
223 dominant.addChild( new Xpp3Dom( recessiveChild ) );
224 }
225 }
226 }
227 }
228
229 /**
230 * Merge two DOMs, with one having dominance in the case of collision.
231 *
232 * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
233 * @see #SELF_COMBINATION_MODE_ATTRIBUTE
234 * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
235 * @param recessive The recessive DOM, which will be merged into the dominant DOM
236 * @param childMergeOverride Overrides attribute flags to force merging or appending of child elements into the
237 * dominant DOM
238 * @return merged DOM
239 */
240 public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
241 {
242 if ( dominant != null )
243 {
244 mergeIntoXpp3Dom( dominant, recessive, childMergeOverride );
245 return dominant;
246 }
247 return recessive;
248 }
249
250 /**
251 * Merge two DOMs, with one having dominance in the case of collision. Merge mechanisms (vs. override for nodes, or
252 * vs. append for children) is determined by attributes of the dominant root node.
253 *
254 * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
255 * @see #SELF_COMBINATION_MODE_ATTRIBUTE
256 * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
257 * @param recessive The recessive DOM, which will be merged into the dominant DOM
258 * @return merged DOM
259 */
260 public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
261 {
262 if ( dominant != null )
263 {
264 mergeIntoXpp3Dom( dominant, recessive, null );
265 return dominant;
266 }
267 return recessive;
268 }
269
270 /**
271 * @deprecated Use {@link org.codehaus.plexus.util.StringUtils#isNotEmpty(String)} instead
272 */
273 @Deprecated
274 public static boolean isNotEmpty( String str )
275 {
276 return ( str != null && str.length() > 0 );
277 }
278
279 /**
280 * @deprecated Use {@link org.codehaus.plexus.util.StringUtils#isEmpty(String)} instead
281 */
282 @Deprecated
283 public static boolean isEmpty( String str )
284 {
285 return ( str == null || str.length() == 0 );
286 }
287 }