Skip to content

Commit

Permalink
#61 keep input location while merging
Browse files Browse the repository at this point in the history
  • Loading branch information
hboutemy committed Mar 9, 2019
1 parent 2d07bf7 commit 0eaf19a
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 45 deletions.
65 changes: 47 additions & 18 deletions src/main/java/org/codehaus/plexus/util/xml/Xpp3Dom.java
Expand Up @@ -91,6 +91,15 @@ public Xpp3Dom( String name )
childMap = new HashMap<String, Xpp3Dom>();
}

/**
* @since 3.2.0
*/
public Xpp3Dom( String name, Object inputLocation )
{
this( name );
this.inputLocation = inputLocation;
}

/**
* Copy constructor.
*/
Expand All @@ -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();

Expand Down Expand Up @@ -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.<p>
* The algorithm is as follows:
* <ol>
* <li> if the recessive DOM is null, there is nothing to do... return.</li>
* <li> Determine whether the dominant node will suppress the recessive one (flag=mergeSelf).
* <ol type="A">
* <li> 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.</li>
* <li> 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.</li>
* </ol></li>
* <li> If mergeSelf == true
* <ol type="A">
* <li> if the dominant root node's value is empty, set it to the recessive root node's value</li>
* <li> For each attribute in the recessive root node which is not set in the dominant root node, set it.</li>
* <li> Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as
* siblings (flag=mergeChildren).
* <ol type="i">
* <li> if childMergeOverride is set (non-null), use that value (true/false)</li>
* <li> retrieve the 'combine.children' attribute on the dominant node, and try to match against
* 'append'...</li>
* <li> if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
* siblings of the dominant children.</li>
* <li> 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.</li>
* </ol></li>
* <li> Iterate through the recessive children, and:
* <ol type="i">
* <li> if mergeChildren == true and there is a corresponding dominant child (matched by element name),
* merge the two.</li>
* <li> otherwise, add the recessive child as a new child on the dominant root node.</li>
* </ol></li>
* </ol></li>
* </ol>
*/
private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
{
Expand All @@ -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();
Expand Down
62 changes: 43 additions & 19 deletions src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java
Expand Up @@ -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
Expand Down Expand Up @@ -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.<p>
* The algorithm is as follows:
* <ol>
* <li> if the recessive DOM is null, there is nothing to do... return.</li>
* <li> Determine whether the dominant node will suppress the recessive one (flag=mergeSelf).
* <ol type="A">
* <li> 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.</li>
* <li> 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.</li>
* </ol></li>
* <li> If mergeSelf == true
* <ol type="A">
* <li> if the dominant root node's value is empty, set it to the recessive root node's value</li>
* <li> For each attribute in the recessive root node which is not set in the dominant root node, set it.</li>
* <li> Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as
* siblings (flag=mergeChildren).
* <ol type="i">
* <li> if childMergeOverride is set (non-null), use that value (true/false)</li>
* <li> retrieve the 'combine.children' attribute on the dominant node, and try to match against
* 'append'...</li>
* <li> if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
* siblings of the dominant children.</li>
* <li> 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.</li>
* </ol></li>
* <li> Iterate through the recessive children, and:
* <ol type="i">
* <li> if 'combine.id' is set and there is a corresponding dominant child (matched by value of 'combine.id'),
* merge the two.</li>
* <li> if mergeChildren == true and there is a corresponding dominant child (matched by element name),
* merge the two.</li>
* <li> otherwise, add the recessive child as a new child on the dominant root node.</li>
* </ol></li>
* </ol></li>
* </ol>
*/
private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
{
Expand All @@ -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();
Expand Down
57 changes: 53 additions & 4 deletions src/test/java/org/codehaus/plexus/util/xml/Xpp3DomTest.java
Expand Up @@ -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;

Expand All @@ -38,24 +39,34 @@ 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 );

// merge and check results.
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
Expand All @@ -64,24 +75,32 @@ 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 );

// merge and check results.
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
Expand All @@ -90,19 +109,22 @@ 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 );

// 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 );

assertEquals( 2, result.getAttributeNames().length );
assertNull( result.getValue() );
assertEquals( "t1top", result.getInputLocation() );
}

@Test
Expand All @@ -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 );
Expand All @@ -124,6 +148,7 @@ public void testShouldMergeValuesAtTopLevelByDefault()
assertEquals( 2, result.getAttributeNames().length );

assertEquals( result.getValue(), t2.getValue() );
assertEquals( "t2top", result.getInputLocation() );
}

@Test
Expand Down Expand Up @@ -213,10 +238,12 @@ public void testShouldOverwritePluginConfigurationSubItemsByDefault()
throws XmlPullParserException, IOException
{
String parentConfigStr = "<configuration><items><item>one</item><item>two</item></items></configuration>";
Xpp3Dom parentConfig = Xpp3DomBuilder.build( new StringReader( parentConfigStr ) );
Xpp3Dom parentConfig =
Xpp3DomBuilder.build( new StringReader( parentConfigStr ), new FixedInputLocationBuilder( "parent" ) );

String childConfigStr = "<configuration><items><item>three</item></items></configuration>";
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" );
Expand All @@ -225,18 +252,21 @@ public void testShouldOverwritePluginConfigurationSubItemsByDefault()

Xpp3Dom item = items.getChild( 0 );
assertEquals( "three", item.getValue() );
assertEquals( "child", item.getInputLocation() );
}

@Test
public void testShouldMergePluginConfigurationSubItemsWithMergeAttributeSet()
throws XmlPullParserException, IOException
{
String parentConfigStr = "<configuration><items><item>one</item><item>two</item></items></configuration>";
Xpp3Dom parentConfig = Xpp3DomBuilder.build( new StringReader( parentConfigStr ) );
Xpp3Dom parentConfig =
Xpp3DomBuilder.build( new StringReader( parentConfigStr ), new FixedInputLocationBuilder( "parent" ) );

String childConfigStr =
"<configuration><items combine.children=\"append\"><item>three</item></items></configuration>";
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" );
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
}

0 comments on commit 0eaf19a

Please sign in to comment.