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.io.StringWriter;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 /**
29 * @version $Id: Xpp3Dom.java 8010 2009-01-07 12:59:50Z vsiveton $
30 * NOTE: remove all the util code in here when separated, this class should be pure data.
31 */
32 public class Xpp3Dom
33 {
34 protected String name;
35
36 protected String value;
37
38 protected Map attributes;
39
40 protected final List childList;
41
42 protected final Map childMap;
43
44 protected Xpp3Dom parent;
45
46 private static final Xpp3Dom[] EMPTY_DOM_ARRAY = new Xpp3Dom[0];
47
48 public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
49
50 public static final String CHILDREN_COMBINATION_MERGE = "merge";
51
52 public static final String CHILDREN_COMBINATION_APPEND = "append";
53
54 /**
55 * This default mode for combining children DOMs during merge means that where element names
56 * match, the process will try to merge the element data, rather than putting the dominant
57 * and recessive elements (which share the same element name) as siblings in the resulting
58 * DOM.
59 */
60 public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
61
62 public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
63
64 public static final String SELF_COMBINATION_OVERRIDE = "override";
65
66 public static final String SELF_COMBINATION_MERGE = "merge";
67
68 /**
69 * This default mode for combining a DOM node during merge means that where element names
70 * match, the process will try to merge the element attributes and values, rather than
71 * overriding the recessive element completely with the dominant one. This means that
72 * wherever the dominant element doesn't provide the value or a particular attribute, that
73 * value or attribute will be set from the recessive DOM node.
74 */
75 public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
76
77 public Xpp3Dom( String name )
78 {
79 this.name = name;
80 childList = new ArrayList();
81 childMap = new HashMap();
82 }
83
84 /**
85 * Copy constructor.
86 */
87 public Xpp3Dom( Xpp3Dom src )
88 {
89 this( src.getName() );
90 setValue( src.getValue() );
91
92 String[] attributeNames = src.getAttributeNames();
93 for ( int i = 0; i < attributeNames.length; i++ )
94 {
95 String attributeName = attributeNames[i];
96 setAttribute( attributeName, src.getAttribute( attributeName ) );
97 }
98
99 Xpp3Dom[] children = src.getChildren();
100 for ( int i = 0; i < children.length; i++ )
101 {
102 addChild( new Xpp3Dom( children[i] ) );
103 }
104 }
105
106 // ----------------------------------------------------------------------
107 // Name handling
108 // ----------------------------------------------------------------------
109
110 public String getName()
111 {
112 return name;
113 }
114
115 // ----------------------------------------------------------------------
116 // Value handling
117 // ----------------------------------------------------------------------
118
119 public String getValue()
120 {
121 return value;
122 }
123
124 public void setValue( String value )
125 {
126 this.value = value;
127 }
128
129 // ----------------------------------------------------------------------
130 // Attribute handling
131 // ----------------------------------------------------------------------
132
133 public String[] getAttributeNames()
134 {
135 if ( null == attributes )
136 {
137 return new String[0];
138 }
139 else
140 {
141 return (String[]) attributes.keySet().toArray( new String[0] );
142 }
143 }
144
145 public String getAttribute( String name )
146 {
147 return ( null != attributes ) ? (String) attributes.get( name ) : null;
148 }
149
150 /**
151 * Set the attribute value
152 * @param name String not null
153 * @param value String not null
154 */
155 public void setAttribute( String name, String value )
156 {
157 if ( null == value ) {
158 throw new NullPointerException( "Attribute value can not be null" );
159 }
160 if ( null == name ) {
161 throw new NullPointerException( "Attribute name can not be null" );
162 }
163 if ( null == attributes )
164 {
165 attributes = new HashMap();
166 }
167
168 attributes.put( name, value );
169 }
170
171 // ----------------------------------------------------------------------
172 // Child handling
173 // ----------------------------------------------------------------------
174
175 public Xpp3Dom getChild( int i )
176 {
177 return (Xpp3Dom) childList.get( i );
178 }
179
180 public Xpp3Dom getChild( String name )
181 {
182 return (Xpp3Dom) childMap.get( name );
183 }
184
185 public void addChild( Xpp3Dom xpp3Dom )
186 {
187 xpp3Dom.setParent( this );
188 childList.add( xpp3Dom );
189 childMap.put( xpp3Dom.getName(), xpp3Dom );
190 }
191
192 public Xpp3Dom[] getChildren()
193 {
194 if ( null == childList )
195 {
196 return EMPTY_DOM_ARRAY;
197 }
198 else
199 {
200 return (Xpp3Dom[]) childList.toArray( EMPTY_DOM_ARRAY );
201 }
202 }
203
204 public Xpp3Dom[] getChildren( String name )
205 {
206 if ( null == childList )
207 {
208 return EMPTY_DOM_ARRAY;
209 }
210 else
211 {
212 ArrayList children = new ArrayList();
213 int size = childList.size();
214
215 for ( int i = 0; i < size; i++ )
216 {
217 Xpp3Dom configuration = (Xpp3Dom) childList.get( i );
218 if ( name.equals( configuration.getName() ) )
219 {
220 children.add( configuration );
221 }
222 }
223
224 return (Xpp3Dom[]) children.toArray( EMPTY_DOM_ARRAY );
225 }
226 }
227
228 public int getChildCount()
229 {
230 if ( null == childList )
231 {
232 return 0;
233 }
234
235 return childList.size();
236 }
237
238 public void removeChild( int i )
239 {
240 Xpp3Dom child = getChild( i );
241 childMap.values().remove( child );
242 childList.remove( i );
243 // In case of any dangling references
244 child.setParent( null );
245 }
246
247 // ----------------------------------------------------------------------
248 // Parent handling
249 // ----------------------------------------------------------------------
250
251 public Xpp3Dom getParent()
252 {
253 return parent;
254 }
255
256 public void setParent( Xpp3Dom parent )
257 {
258 this.parent = parent;
259 }
260
261 // ----------------------------------------------------------------------
262 // Helpers
263 // ----------------------------------------------------------------------
264
265 public void writeToSerializer( String namespace, XmlSerializer serializer )
266 throws IOException
267 {
268 // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new document - not the desired behaviour!
269 SerializerXMLWriter xmlWriter = new SerializerXMLWriter( namespace, serializer );
270 Xpp3DomWriter.write( xmlWriter, this );
271 if ( xmlWriter.getExceptions().size() > 0 )
272 {
273 throw (IOException) xmlWriter.getExceptions().get( 0 );
274 }
275 }
276
277 /**
278 * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.
279 * The algorithm is as follows:
280 *
281 * 1. if the recessive DOM is null, there is nothing to do...return.
282 *
283 * 2. Determine whether the dominant node will suppress the recessive one (flag=mergeSelf).
284 *
285 * A. retrieve the 'combine.self' attribute on the dominant node, and try to match against 'override'...
286 * if it matches 'override', then set mergeSelf == false...the dominant node suppresses the recessive
287 * one completely.
288 *
289 * B. otherwise, use the default value for mergeSelf, which is true...this is the same as specifying
290 * 'combine.self' == 'merge' as an attribute of the dominant root node.
291 *
292 * 3. If mergeSelf == true
293 *
294 * A. if the dominant root node's value is empty, set it to the recessive root node's value
295 *
296 * B. For each attribute in the recessive root node which is not set in the dominant root node, set it.
297 *
298 * C. Determine whether children from the recessive DOM will be merged or appended to the dominant
299 * DOM as siblings (flag=mergeChildren).
300 *
301 * i. if childMergeOverride is set (non-null), use that value (true/false)
302 *
303 * ii. retrieve the 'combine.children' attribute on the dominant node, and try to match against
304 * 'append'...if it matches 'append', then set mergeChildren == false...the recessive children
305 * will be appended as siblings of the dominant children.
306 *
307 * iii. otherwise, use the default value for mergeChildren, which is true...this is the same as
308 * specifying 'combine.children' == 'merge' as an attribute on the dominant root node.
309 *
310 * D. Iterate through the recessive children, and:
311 *
312 * i. if mergeChildren == true and there is a corresponding dominant child (matched by element name),
313 * merge the two.
314 *
315 * ii. otherwise, add the recessive child as a new child on the dominant root node.
316 */
317 private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
318 {
319 // TODO: share this as some sort of assembler, implement a walk interface?
320 if ( recessive == null )
321 {
322 return;
323 }
324
325 boolean mergeSelf = true;
326
327 String selfMergeMode = dominant.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
328
329 if ( isNotEmpty( selfMergeMode ) && SELF_COMBINATION_OVERRIDE.equals( selfMergeMode ) )
330 {
331 mergeSelf = false;
332 }
333
334 if ( mergeSelf )
335 {
336 if ( isEmpty( dominant.getValue() ) )
337 {
338 dominant.setValue( recessive.getValue() );
339 }
340
341 String[] recessiveAttrs = recessive.getAttributeNames();
342 for ( int i = 0; i < recessiveAttrs.length; i++ )
343 {
344 String attr = recessiveAttrs[i];
345
346 if ( isEmpty( dominant.getAttribute( attr ) ) )
347 {
348 dominant.setAttribute( attr, recessive.getAttribute( attr ) );
349 }
350 }
351
352 boolean mergeChildren = true;
353
354 if ( childMergeOverride != null )
355 {
356 mergeChildren = childMergeOverride.booleanValue();
357 }
358 else
359 {
360 String childMergeMode = dominant.getAttribute( CHILDREN_COMBINATION_MODE_ATTRIBUTE );
361
362 if ( isNotEmpty( childMergeMode ) && CHILDREN_COMBINATION_APPEND.equals( childMergeMode ) )
363 {
364 mergeChildren = false;
365 }
366 }
367
368 Xpp3Dom[] dominantChildren = dominant.getChildren();
369 if ( !mergeChildren )
370 {
371 // remove these now, so we can append them to the recessive list later.
372 dominant.childList.clear();
373 }
374
375 Xpp3Dom[] children = recessive.getChildren();
376 for ( int i = 0; i < children.length; i++ )
377 {
378 Xpp3Dom child = children[i];
379 Xpp3Dom childDom = dominant.getChild( child.getName() );
380 if ( mergeChildren && ( childDom != null ) )
381 {
382 mergeIntoXpp3Dom( childDom, child, childMergeOverride );
383 }
384 else
385 {
386 dominant.addChild( new Xpp3Dom( child ) );
387 }
388 }
389
390 if ( !mergeChildren )
391 {
392 // now, re-add these children so they'll be appended to the recessive list.
393 for ( int i = 0; i < dominantChildren.length; i++ )
394 {
395 dominant.addChild( dominantChildren[i] );
396 }
397 }
398 }
399 }
400
401 /**
402 * Merge two DOMs, with one having dominance in the case of collision.
403 *
404 * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
405 * @see #SELF_COMBINATION_MODE_ATTRIBUTE
406 *
407 * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
408 * @param recessive The recessive DOM, which will be merged into the dominant DOM
409 * @param childMergeOverride Overrides attribute flags to force merging or appending of child elements
410 * into the dominant DOM
411 */
412 public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
413 {
414 if ( dominant != null )
415 {
416 mergeIntoXpp3Dom( dominant, recessive, childMergeOverride );
417 return dominant;
418 }
419 return recessive;
420 }
421
422 /**
423 * Merge two DOMs, with one having dominance in the case of collision.
424 * Merge mechanisms (vs. override for nodes, or vs. append for children) is determined by
425 * attributes of the dominant root node.
426 *
427 * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
428 * @see #SELF_COMBINATION_MODE_ATTRIBUTE
429 *
430 * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
431 * @param recessive The recessive DOM, which will be merged into the dominant DOM
432 */
433 public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
434 {
435 if ( dominant != null )
436 {
437 mergeIntoXpp3Dom( dominant, recessive, null );
438 return dominant;
439 }
440 return recessive;
441 }
442
443 // ----------------------------------------------------------------------
444 // Standard object handling
445 // ----------------------------------------------------------------------
446
447 public boolean equals( Object obj )
448 {
449 if ( obj == this )
450 {
451 return true;
452 }
453
454 if ( !( obj instanceof Xpp3Dom ) )
455 {
456 return false;
457 }
458
459 Xpp3Dom dom = (Xpp3Dom) obj;
460
461 if ( name == null ? dom.name != null : !name.equals( dom.name ) )
462 {
463 return false;
464 }
465 else if ( value == null ? dom.value != null : !value.equals( dom.value ) )
466 {
467 return false;
468 }
469 else if ( attributes == null ? dom.attributes != null : !attributes.equals( dom.attributes ) )
470 {
471 return false;
472 }
473 else if ( childList == null ? dom.childList != null : !childList.equals( dom.childList ) )
474 {
475 return false;
476 }
477 else
478 {
479 return true;
480 }
481 }
482
483 public int hashCode()
484 {
485 int result = 17;
486 result = 37 * result + ( name != null ? name.hashCode() : 0 );
487 result = 37 * result + ( value != null ? value.hashCode() : 0 );
488 result = 37 * result + ( attributes != null ? attributes.hashCode() : 0 );
489 result = 37 * result + ( childList != null ? childList.hashCode() : 0 );
490 return result;
491 }
492
493 public String toString()
494 {
495 // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new document - not the desired behaviour!
496 StringWriter writer = new StringWriter();
497 XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
498 Xpp3DomWriter.write( xmlWriter, this );
499 return writer.toString();
500 }
501
502 public String toUnescapedString()
503 {
504 // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new document - not the desired behaviour!
505 StringWriter writer = new StringWriter();
506 XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
507 Xpp3DomWriter.write( xmlWriter, this, false );
508 return writer.toString();
509 }
510
511 public static boolean isNotEmpty( String str )
512 {
513 return ( ( str != null ) && ( str.length() > 0 ) );
514 }
515
516 public static boolean isEmpty( String str )
517 {
518 return ( ( str == null ) || ( str.trim().length() == 0 ) );
519 }
520
521 }