Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support combine.keys #76

Merged
merged 1 commit into from Dec 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -26,7 +26,7 @@ limitations under the License.
</parent>

<artifactId>plexus-utils</artifactId>
<version>3.3.1-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>

<name>Plexus Common Utilities</name>
<description>A collection of various utility classes to ease working with strings, files, command lines, XML and
Expand Down
39 changes: 37 additions & 2 deletions src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java
Expand Up @@ -19,9 +19,7 @@
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 */
Expand Down Expand Up @@ -52,6 +50,14 @@ public class Xpp3DomUtils
* @since 3.0.22
*/
public static final String ID_COMBINATION_MODE_ATTRIBUTE = "combine.id";

/**
* In case of complex XML structures, combining can be done based on keys.
* This is a comma separated list of attribute names.
*
* @Since 3.4.0
*/
public static final String KEYS_COMBINATION_MODE_ATTRIBUTE = "combine.keys";

/**
* This default mode for combining a DOM node during merge means that where element names match, the process will
Expand Down Expand Up @@ -106,6 +112,8 @@ public void writeToSerializer( String namespace, XmlSerializer serializer, Xpp3D
* <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 'combine.keys' is set and there is a corresponding dominant child (matched by value of key elements),
* 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>
Expand Down Expand Up @@ -167,6 +175,7 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole
for ( Xpp3Dom recessiveChild : children )
{
String idValue = recessiveChild.getAttribute( ID_COMBINATION_MODE_ATTRIBUTE );
String keysValue = recessiveChild.getAttribute( KEYS_COMBINATION_MODE_ATTRIBUTE );

Xpp3Dom childDom = null;
if ( isNotEmpty( idValue ) )
Expand All @@ -181,6 +190,32 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole
}
}
}
else if ( isNotEmpty( keysValue ) )
{
String[] keys = keysValue.split( "," );
Map<String, String> recessiveKeyValues = new HashMap<String, String>( keys.length );
for ( String key : keys )
{
recessiveKeyValues.put( key, recessiveChild.getChild( key ).getValue() );
}

for ( Xpp3Dom dominantChild : dominant.getChildren() )
{
Map<String, String> dominantKeyValues = new HashMap<String, String>( keys.length );
for ( String key : keys )
{
dominantKeyValues.put( key, dominantChild.getChild( key ).getValue() );
}

if ( recessiveKeyValues.equals( dominantKeyValues ) )
{
childDom = dominantChild;
// we have a match, so don't append but merge
mergeChildren = true;
}
}

}
else
{
childDom = dominant.getChild( recessiveChild.getName() );
Expand Down
35 changes: 35 additions & 0 deletions src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java
Expand Up @@ -60,6 +60,41 @@ public void testCombineId()
assertEquals( "right", p2.getChild( "value" ).getInputLocation() );
}

@Test
public void testCombineKeys()
throws Exception
{
String lhs = "<props>" + "<property combine.keys='name'><name>LHS-ONLY</name><value>LHS</value></property>"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this ok? according to the readme in https://github.com/atteo/xml-combiner, the value of combine.keys are attributes names, and here are tag names.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I see what you mean. In case of Maven I am missing the ability to identity the key of complex objects.
But there's indeed something else wrong: combine.keys shõuld not be on the element itself, but the wrapping element. And with that I understand why it is for attributes. I need to have another look at it.

Great catch!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ "<property combine.keys='name'><name>TOOVERWRITE</name><value>LHS</value></property>" + "</props>";

String rhs = "<props>" + "<property combine.keys='name'><name>RHS-ONLY</name><value>RHS</value></property>"
+ "<property combine.keys='name'><name>TOOVERWRITE</name><value>RHS</value></property>" + "</props>";

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

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
{
Expand Down