diff --git a/pom.xml b/pom.xml
index f6602acd..93014125 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@ limitations under the License.
plexus-utils
- 3.3.1-SNAPSHOT
+ 3.4.0-SNAPSHOT
Plexus Common Utilities
A collection of various utility classes to ease working with strings, files, command lines, XML and
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 70d00143..71efe467 100644
--- a/src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java
+++ b/src/main/java/org/codehaus/plexus/util/xml/Xpp3DomUtils.java
@@ -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 */
@@ -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
@@ -106,6 +112,8 @@ public void writeToSerializer( String namespace, XmlSerializer serializer, Xpp3D
*
* - if 'combine.id' is set and there is a corresponding dominant child (matched by value of 'combine.id'),
* merge the two.
+ * - if 'combine.keys' is set and there is a corresponding dominant child (matched by value of key elements),
+ * merge the two.
* - if mergeChildren == true and there is a corresponding dominant child (matched by element name),
* merge the two.
* - otherwise, add the recessive child as a new child on the dominant root node.
@@ -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 ) )
@@ -181,6 +190,32 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole
}
}
}
+ else if ( isNotEmpty( keysValue ) )
+ {
+ String[] keys = keysValue.split( "," );
+ Map recessiveKeyValues = new HashMap( keys.length );
+ for ( String key : keys )
+ {
+ recessiveKeyValues.put( key, recessiveChild.getChild( key ).getValue() );
+ }
+
+ for ( Xpp3Dom dominantChild : dominant.getChildren() )
+ {
+ Map dominantKeyValues = new HashMap( 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() );
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 f4d0eb04..e5a68e77 100644
--- a/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java
+++ b/src/test/java/org/codehaus/plexus/util/xml/Xpp3DomUtilsTest.java
@@ -60,6 +60,41 @@ public void testCombineId()
assertEquals( "right", p2.getChild( "value" ).getInputLocation() );
}
+ @Test
+ public void testCombineKeys()
+ throws Exception
+ {
+ String lhs = "" + "LHS-ONLYLHS"
+ + "TOOVERWRITELHS" + "";
+
+ String rhs = "" + "RHS-ONLYRHS"
+ + "TOOVERWRITERHS" + "";
+
+ 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
{