diff --git a/src/main/java/org/codehaus/plexus/util/xml/Xpp3Dom.java b/src/main/java/org/codehaus/plexus/util/xml/Xpp3Dom.java index 321b7855..e10498f1 100644 --- a/src/main/java/org/codehaus/plexus/util/xml/Xpp3Dom.java +++ b/src/main/java/org/codehaus/plexus/util/xml/Xpp3Dom.java @@ -91,6 +91,15 @@ public Xpp3Dom( String name ) childMap = new HashMap(); } + /** + * @since 3.2.0 + */ + public Xpp3Dom( String name, Object inputLocation ) + { + this( name ); + this.inputLocation = inputLocation; + } + /** * Copy constructor. */ @@ -105,6 +114,7 @@ public Xpp3Dom( Xpp3Dom src ) public Xpp3Dom( Xpp3Dom src, String name ) { this.name = name; + this.inputLocation = src.inputLocation; int childCount = src.getChildCount(); @@ -321,23 +331,41 @@ public void writeToSerializer( String namespace, XmlSerializer serializer ) } /** - * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm. The - * algorithm is as follows: 1. if the recessive DOM is null, there is nothing to do...return. 2. Determine whether - * the dominant node will suppress the recessive one (flag=mergeSelf). A. retrieve the 'combine.self' attribute on - * the dominant node, and try to match against 'override'... if it matches 'override', then set mergeSelf == - * false...the dominant node suppresses the recessive one completely. B. otherwise, use the default value for - * mergeSelf, which is true...this is the same as specifying 'combine.self' == 'merge' as an attribute of the - * dominant root node. 3. If mergeSelf == true A. if the dominant root node's value is empty, set it to the - * recessive root node's value B. For each attribute in the recessive root node which is not set in the dominant - * root node, set it. C. Determine whether children from the recessive DOM will be merged or appended to the - * dominant DOM as siblings (flag=mergeChildren). i. if childMergeOverride is set (non-null), use that value - * (true/false) ii. retrieve the 'combine.children' attribute on the dominant node, and try to match against - * 'append'...if it matches 'append', then set mergeChildren == false...the recessive children will be appended as - * siblings of the dominant children. iii. otherwise, use the default value for mergeChildren, which is true...this - * is the same as specifying 'combine.children' == 'merge' as an attribute on the dominant root node. D. Iterate - * through the recessive children, and: i. if mergeChildren == true and there is a corresponding dominant child - * (matched by element name), merge the two. ii. otherwise, add the recessive child as a new child on the dominant - * root node. + * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.

+ * The algorithm is as follows: + *

    + *
  1. if the recessive DOM is null, there is nothing to do... return.
  2. + *
  3. Determine whether the dominant node will suppress the recessive one (flag=mergeSelf). + *
      + *
    1. retrieve the 'combine.self' attribute on the dominant node, and try to match against 'override'... + * if it matches 'override', then set mergeSelf == false...the dominant node suppresses the recessive one + * completely.
    2. + *
    3. otherwise, use the default value for mergeSelf, which is true...this is the same as specifying + * 'combine.self' == 'merge' as an attribute of the dominant root node.
    4. + *
  4. + *
  5. If mergeSelf == true + *
      + *
    1. if the dominant root node's value is empty, set it to the recessive root node's value
    2. + *
    3. For each attribute in the recessive root node which is not set in the dominant root node, set it.
    4. + *
    5. Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as + * siblings (flag=mergeChildren). + *
        + *
      1. if childMergeOverride is set (non-null), use that value (true/false)
      2. + *
      3. retrieve the 'combine.children' attribute on the dominant node, and try to match against + * 'append'...
      4. + *
      5. if it matches 'append', then set mergeChildren == false...the recessive children will be appended as + * siblings of the dominant children.
      6. + *
      7. otherwise, use the default value for mergeChildren, which is true...this is the same as specifying + * 'combine.children' == 'merge' as an attribute on the dominant root node.
      8. + *
    6. + *
    7. Iterate through the recessive children, and: + *
        + *
      1. if mergeChildren == true and there is a corresponding dominant child (matched by element name), + * merge the two.
      2. + *
      3. otherwise, add the recessive child as a new child on the dominant root node.
      4. + *
    8. + *
  6. + *
*/ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride ) { @@ -358,9 +386,10 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole if ( mergeSelf ) { - if ( isEmpty( dominant.getValue() ) ) + if ( isEmpty( dominant.getValue() ) && !isEmpty( recessive.getValue() ) ) { dominant.setValue( recessive.getValue() ); + dominant.setInputLocation( recessive.getInputLocation() ); } String[] recessiveAttrs = recessive.getAttributeNames(); diff --git a/src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java b/src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java index f3d5d488..70d00143 100644 --- a/src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java +++ b/src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java @@ -19,6 +19,10 @@ import org.codehaus.plexus.util.xml.pull.XmlSerializer; import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; /** @author Jason van Zyl */ public class Xpp3DomUtils @@ -71,24 +75,43 @@ public void writeToSerializer( String namespace, XmlSerializer serializer, Xpp3D } /** - * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm. The - * algorithm is as follows: 1. if the recessive DOM is null, there is nothing to do...return. 2. Determine whether - * the dominant node will suppress the recessive one (flag=mergeSelf). A. retrieve the 'combine.self' attribute on - * the dominant node, and try to match against 'override'... if it matches 'override', then set mergeSelf == - * false...the dominant node suppresses the recessive one completely. B. otherwise, use the default value for - * mergeSelf, which is true...this is the same as specifying 'combine.self' == 'merge' as an attribute of the - * dominant root node. 3. If mergeSelf == true A. if the dominant root node's value is empty, set it to the - * recessive root node's value B. For each attribute in the recessive root node which is not set in the dominant - * root node, set it. C. Determine whether children from the recessive DOM will be merged or appended to the - * dominant DOM as siblings (flag=mergeChildren). i. if childMergeOverride is set (non-null), use that value - * (true/false) ii. retrieve the 'combine.children' attribute on the dominant node, and try to match against - * 'append'...if it matches 'append', then set mergeChildren == false...the recessive children will be appended as - * siblings of the dominant children. iii. otherwise, use the default value for mergeChildren, which is true...this - * is the same as specifying 'combine.children' == 'merge' as an attribute on the dominant root node. D. Iterate - * through the recessive children, and: i. if 'combine.id' is set and there is a corresponding dominant child - * (matched by value of 'combine.id'), merge the two. ii. if mergeChildren == true and there is a corresponding - * dominant child (matched by element name), merge the two. iii. otherwise, add the recessive child as a new child - * on the dominant root node. + * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.

+ * The algorithm is as follows: + *

    + *
  1. if the recessive DOM is null, there is nothing to do... return.
  2. + *
  3. Determine whether the dominant node will suppress the recessive one (flag=mergeSelf). + *
      + *
    1. retrieve the 'combine.self' attribute on the dominant node, and try to match against 'override'... + * if it matches 'override', then set mergeSelf == false...the dominant node suppresses the recessive one + * completely.
    2. + *
    3. otherwise, use the default value for mergeSelf, which is true...this is the same as specifying + * 'combine.self' == 'merge' as an attribute of the dominant root node.
    4. + *
  4. + *
  5. If mergeSelf == true + *
      + *
    1. if the dominant root node's value is empty, set it to the recessive root node's value
    2. + *
    3. For each attribute in the recessive root node which is not set in the dominant root node, set it.
    4. + *
    5. Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as + * siblings (flag=mergeChildren). + *
        + *
      1. if childMergeOverride is set (non-null), use that value (true/false)
      2. + *
      3. retrieve the 'combine.children' attribute on the dominant node, and try to match against + * 'append'...
      4. + *
      5. if it matches 'append', then set mergeChildren == false...the recessive children will be appended as + * siblings of the dominant children.
      6. + *
      7. otherwise, use the default value for mergeChildren, which is true...this is the same as specifying + * 'combine.children' == 'merge' as an attribute on the dominant root node.
      8. + *
    6. + *
    7. Iterate through the recessive children, and: + *
        + *
      1. if 'combine.id' is set and there is a corresponding dominant child (matched by value of 'combine.id'), + * merge the two.
      2. + *
      3. if mergeChildren == true and there is a corresponding dominant child (matched by element name), + * merge the two.
      4. + *
      5. otherwise, add the recessive child as a new child on the dominant root node.
      6. + *
    8. + *
  6. + *
*/ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride ) { @@ -109,9 +132,10 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole if ( mergeSelf ) { - if ( isEmpty( dominant.getValue() ) ) + if ( isEmpty( dominant.getValue() ) && !isEmpty( recessive.getValue() ) ) { dominant.setValue( recessive.getValue() ); + dominant.setInputLocation( recessive.getInputLocation() ); } String[] recessiveAttrs = recessive.getAttributeNames(); diff --git a/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomTest.java b/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomTest.java index dd06f7b1..04444f31 100644 --- a/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomTest.java +++ b/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomTest.java @@ -27,6 +27,7 @@ import java.io.StringReader; import java.util.HashMap; +import org.codehaus.plexus.util.xml.pull.XmlPullParser; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.junit.Test; @@ -38,17 +39,21 @@ public void testShouldPerformAppendAtFirstSubElementLevel() // create the dominant DOM Xpp3Dom t1 = new Xpp3Dom( "top" ); t1.setAttribute( Xpp3Dom.CHILDREN_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.CHILDREN_COMBINATION_APPEND ); + t1.setInputLocation( "t1top" ); Xpp3Dom t1s1 = new Xpp3Dom( "topsub1" ); t1s1.setValue( "t1s1Value" ); + t1s1.setInputLocation( "t1s1" ); t1.addChild( t1s1 ); // create the recessive DOM Xpp3Dom t2 = new Xpp3Dom( "top" ); + t2.setInputLocation( "t2top" ); Xpp3Dom t2s1 = new Xpp3Dom( "topsub1" ); t2s1.setValue( "t2s1Value" ); + t2s1.setInputLocation( "t2s1" ); t2.addChild( t2s1 ); @@ -56,6 +61,12 @@ public void testShouldPerformAppendAtFirstSubElementLevel() Xpp3Dom result = Xpp3Dom.mergeXpp3Dom( t1, t2 ); assertEquals( 2, result.getChildren( "topsub1" ).length ); + assertEquals( "t2s1Value", result.getChildren( "topsub1" )[0].getValue() ); + assertEquals( "t1s1Value", result.getChildren( "topsub1" )[1].getValue() ); + + assertEquals( "t1top", result.getInputLocation() ); + assertEquals( "t2s1", result.getChildren( "topsub1" )[0].getInputLocation() ); + assertEquals( "t1s1", result.getChildren( "topsub1" )[1].getInputLocation() ); } @Test @@ -64,17 +75,21 @@ public void testShouldOverrideAppendAndDeepMerge() // create the dominant DOM Xpp3Dom t1 = new Xpp3Dom( "top" ); t1.setAttribute( Xpp3Dom.CHILDREN_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.CHILDREN_COMBINATION_APPEND ); + t1.setInputLocation( "t1top" ); Xpp3Dom t1s1 = new Xpp3Dom( "topsub1" ); t1s1.setValue( "t1s1Value" ); + t1s1.setInputLocation( "t1s1" ); t1.addChild( t1s1 ); // create the recessive DOM Xpp3Dom t2 = new Xpp3Dom( "top" ); + t2.setInputLocation( "t2top" ); Xpp3Dom t2s1 = new Xpp3Dom( "topsub1" ); t2s1.setValue( "t2s1Value" ); + t2s1.setInputLocation( "t2s1" ); t2.addChild( t2s1 ); @@ -82,6 +97,10 @@ public void testShouldOverrideAppendAndDeepMerge() Xpp3Dom result = Xpp3Dom.mergeXpp3Dom( t1, t2, Boolean.TRUE ); assertEquals( 1, result.getChildren( "topsub1" ).length ); + assertEquals( "t1s1Value", result.getChildren( "topsub1" )[0].getValue() ); + + assertEquals( "t1top", result.getInputLocation() ); + assertEquals( "t1s1", result.getChildren( "topsub1" )[0].getInputLocation() ); } @Test @@ -90,6 +109,7 @@ public void testShouldPerformSelfOverrideAtTopLevel() // create the dominant DOM Xpp3Dom t1 = new Xpp3Dom( "top" ); t1.setAttribute( "attr", "value" ); + t1.setInputLocation( "t1top" ); t1.setAttribute( Xpp3Dom.SELF_COMBINATION_MODE_ATTRIBUTE, Xpp3Dom.SELF_COMBINATION_OVERRIDE ); @@ -97,12 +117,14 @@ public void testShouldPerformSelfOverrideAtTopLevel() Xpp3Dom t2 = new Xpp3Dom( "top" ); t2.setAttribute( "attr2", "value2" ); t2.setValue( "t2Value" ); + t2.setInputLocation( "t2top" ); // merge and check results. Xpp3Dom result = Xpp3Dom.mergeXpp3Dom( t1, t2 ); assertEquals( 2, result.getAttributeNames().length ); assertNull( result.getValue() ); + assertEquals( "t1top", result.getInputLocation() ); } @Test @@ -111,11 +133,13 @@ public void testShouldMergeValuesAtTopLevelByDefault() // create the dominant DOM Xpp3Dom t1 = new Xpp3Dom( "top" ); t1.setAttribute( "attr", "value" ); + t1.setInputLocation( "t1top" ); // create the recessive DOM Xpp3Dom t2 = new Xpp3Dom( "top" ); t2.setAttribute( "attr2", "value2" ); t2.setValue( "t2Value" ); + t2.setInputLocation( "t2top" ); // merge and check results. Xpp3Dom result = Xpp3Dom.mergeXpp3Dom( t1, t2 ); @@ -124,6 +148,7 @@ public void testShouldMergeValuesAtTopLevelByDefault() assertEquals( 2, result.getAttributeNames().length ); assertEquals( result.getValue(), t2.getValue() ); + assertEquals( "t2top", result.getInputLocation() ); } @Test @@ -213,10 +238,12 @@ public void testShouldOverwritePluginConfigurationSubItemsByDefault() throws XmlPullParserException, IOException { String parentConfigStr = "onetwo"; - Xpp3Dom parentConfig = Xpp3DomBuilder.build( new StringReader( parentConfigStr ) ); + Xpp3Dom parentConfig = + Xpp3DomBuilder.build( new StringReader( parentConfigStr ), new FixedInputLocationBuilder( "parent" ) ); String childConfigStr = "three"; - Xpp3Dom childConfig = Xpp3DomBuilder.build( new StringReader( childConfigStr ) ); + Xpp3Dom childConfig = + Xpp3DomBuilder.build( new StringReader( childConfigStr ), new FixedInputLocationBuilder( "child" ) ); Xpp3Dom result = Xpp3Dom.mergeXpp3Dom( childConfig, parentConfig ); Xpp3Dom items = result.getChild( "items" ); @@ -225,6 +252,7 @@ public void testShouldOverwritePluginConfigurationSubItemsByDefault() Xpp3Dom item = items.getChild( 0 ); assertEquals( "three", item.getValue() ); + assertEquals( "child", item.getInputLocation() ); } @Test @@ -232,11 +260,13 @@ public void testShouldMergePluginConfigurationSubItemsWithMergeAttributeSet() throws XmlPullParserException, IOException { String parentConfigStr = "onetwo"; - Xpp3Dom parentConfig = Xpp3DomBuilder.build( new StringReader( parentConfigStr ) ); + Xpp3Dom parentConfig = + Xpp3DomBuilder.build( new StringReader( parentConfigStr ), new FixedInputLocationBuilder( "parent" ) ); String childConfigStr = "three"; - Xpp3Dom childConfig = Xpp3DomBuilder.build( new StringReader( childConfigStr ) ); + Xpp3Dom childConfig = + Xpp3DomBuilder.build( new StringReader( childConfigStr ), new FixedInputLocationBuilder( "child" ) ); Xpp3Dom result = Xpp3Dom.mergeXpp3Dom( childConfig, parentConfig ); Xpp3Dom items = result.getChild( "items" ); @@ -246,8 +276,11 @@ public void testShouldMergePluginConfigurationSubItemsWithMergeAttributeSet() Xpp3Dom[] item = items.getChildren(); assertEquals( "one", item[0].getValue() ); + assertEquals( "parent", item[0].getInputLocation() ); assertEquals( "two", item[1].getValue() ); + assertEquals( "parent", item[1].getInputLocation() ); assertEquals( "three", item[2].getValue() ); + assertEquals( "child", item[2].getInputLocation() ); } @Test @@ -295,4 +328,20 @@ public void testDupeChildren() assertNotNull( dom ); assertEquals( "y", dom.getChild( "foo" ).getValue() ); } + + private static class FixedInputLocationBuilder + implements Xpp3DomBuilder.InputLocationBuilder + { + private final Object location; + + public FixedInputLocationBuilder( Object location ) + { + this.location = location; + } + + public Object toInputLocation( XmlPullParser parser ) + { + return location; + } + } } diff --git a/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java b/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java index f41f2641..f4d0eb04 100644 --- a/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java +++ b/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java @@ -20,6 +20,7 @@ import java.io.StringReader; +import org.codehaus.plexus.util.xml.pull.XmlPullParser; import org.junit.Test; public class Xpp3DomUtilsTest @@ -34,17 +35,44 @@ public void testCombineId() String rhs = "" + "RHS-ONLYRHS" + "TOOVERWRITERHS" + ""; - Xpp3Dom leftDom = Xpp3DomBuilder.build( new StringReader( lhs ) ); - Xpp3Dom rightDom = Xpp3DomBuilder.build( new StringReader( rhs ) ); + Xpp3Dom leftDom = Xpp3DomBuilder.build( new StringReader( lhs ), new FixedInputLocationBuilder( "left" ) ); + Xpp3Dom rightDom = Xpp3DomBuilder.build( new StringReader( rhs ), new FixedInputLocationBuilder( "right" ) ); Xpp3Dom mergeResult = Xpp3DomUtils.mergeXpp3Dom( leftDom, rightDom, true ); assertEquals( 3, mergeResult.getChildren( "property" ).length ); - assertEquals( "LHS-ONLY", mergeResult.getChildren( "property" )[0].getChild( "name" ).getValue() ); - assertEquals( "LHS", mergeResult.getChildren( "property" )[0].getChild( "value" ).getValue() ); + Xpp3Dom p0 = mergeResult.getChildren( "property" )[0]; + assertEquals( "LHS-ONLY", p0.getChild( "name" ).getValue() ); + assertEquals( "left", p0.getChild( "name" ).getInputLocation() ); + assertEquals( "LHS", p0.getChild( "value" ).getValue() ); + assertEquals( "left", p0.getChild( "value" ).getInputLocation() ); + + Xpp3Dom p1 = mergeResult.getChildren( "property" )[1]; assertEquals( "TOOVERWRITE", mergeResult.getChildren( "property" )[1].getChild( "name" ).getValue() ); + assertEquals( "left", p1.getChild( "name" ).getInputLocation() ); assertEquals( "LHS", mergeResult.getChildren( "property" )[1].getChild( "value" ).getValue() ); + assertEquals( "left", p1.getChild( "value" ).getInputLocation() ); + + Xpp3Dom p2 = mergeResult.getChildren( "property" )[2]; assertEquals( "RHS-ONLY", mergeResult.getChildren( "property" )[2].getChild( "name" ).getValue() ); + assertEquals( "right", p2.getChild( "name" ).getInputLocation() ); assertEquals( "RHS", mergeResult.getChildren( "property" )[2].getChild( "value" ).getValue() ); + assertEquals( "right", p2.getChild( "value" ).getInputLocation() ); + } + + private static class FixedInputLocationBuilder + implements Xpp3DomBuilder.InputLocationBuilder + { + private final Object location; + + public FixedInputLocationBuilder( Object location ) + { + this.location = location; + } + + public Object toInputLocation( XmlPullParser parser ) + { + return location; + } } }