Index: /trunk/src/org/openstreetmap/josm/command/ChangeMembersCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/ChangeMembersCommand.java	(revision 17199)
+++ /trunk/src/org/openstreetmap/josm/command/ChangeMembersCommand.java	(revision 17199)
@@ -0,0 +1,87 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.command;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+
+/**
+ * Command that changes the members of a relation.
+ * The same can be done with ChangeCommand, but this is more efficient.
+ * @author Gerd Petermann
+ * @since xxx
+ *
+ */
+public class ChangeMembersCommand extends Command {
+
+    private final Relation relation;
+    private final List<RelationMember> cmdMembers;
+
+    /**
+     * Constructs a new {@code ChangeMembersCommand} in the context of a given data set.
+     * @param data the data set. Must not be null.
+     * @param relation the relation
+     * @param newMembers the new member list, must not be empty
+     */
+    public ChangeMembersCommand(DataSet data, Relation relation, List<RelationMember> newMembers) {
+        super(data);
+        this.relation = Objects.requireNonNull(relation, "relation");
+        this.cmdMembers = Objects.requireNonNull(newMembers, "newMembers");
+        if (cmdMembers.isEmpty()) {
+            throw new IllegalArgumentException("Members collection is empty");
+        }
+    }
+
+    /**
+     * Constructs a new {@code ChangeMembersCommand}  in the context of {@code r} data set.
+     * @param relation the relation. It must belong to a data set
+     * @param newMembers the new member list, must not be empty
+     */
+    public ChangeMembersCommand(Relation relation, List<RelationMember> newMembers) {
+        this(relation.getDataSet(), relation, newMembers);
+    }
+
+    @Override
+    public boolean executeCommand() {
+        super.executeCommand();
+        relation.setMembers(cmdMembers);
+        relation.setModified(true);
+        return true;
+
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Change members of {0}", relation.getDisplayName(DefaultNameFormatter.getInstance()));
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
+            Collection<OsmPrimitive> added) {
+        modified.add(relation);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), relation, cmdMembers);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        if (!super.equals(obj)) return false;
+        ChangeMembersCommand that = (ChangeMembersCommand) obj;
+        return Objects.equals(relation, that.relation) &&
+               Objects.equals(cmdMembers, that.cmdMembers);
+    }
+
+}
Index: /trunk/test/unit/org/openstreetmap/josm/command/ChangeMembersCommandTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/command/ChangeMembersCommandTest.java	(revision 17199)
+++ /trunk/test/unit/org/openstreetmap/josm/command/ChangeMembersCommandTest.java	(revision 17199)
@@ -0,0 +1,114 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.command;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.command.CommandTest.CommandTestDataWithRelation;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.User;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+
+/**
+ * Unit tests of {@link ChangeMembersCommand} class.
+ */
+public class ChangeMembersCommandTest {
+
+    /**
+     * We need prefs for nodes.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences().i18n();
+    private CommandTestDataWithRelation testData;
+
+    /**
+     * Set up the test data.
+     */
+    @Before
+    public void createTestData() {
+        testData = new CommandTestDataWithRelation();
+    }
+
+    /**
+     * Test {@link ChangeMembersCommand#executeCommand()}
+     */
+    @Test
+    public void testChange() {
+        assertTrue(testData.existingNode.getReferrers().contains(testData.existingRelation));
+        assertEquals(2, testData.existingRelation.getMembersCount());
+        List<RelationMember> members = testData.existingRelation.getMembers();
+        members.add(new RelationMember("n2", testData.existingNode2));
+        new ChangeMembersCommand(testData.existingRelation, members).executeCommand();
+        assertEquals(3, testData.existingRelation.getMembersCount());
+        members = testData.existingRelation.getMembers();
+        members.remove(0);
+        new ChangeMembersCommand(testData.existingRelation, members).executeCommand();
+        assertEquals(2, testData.existingRelation.getMembersCount());
+        assertTrue(testData.existingRelation.getMembersFor(Collections.singleton(testData.existingNode)).isEmpty());
+        assertEquals(testData.existingWay, testData.existingRelation.getMember(0).getMember());
+        assertEquals(testData.existingNode2, testData.existingRelation.getMember(1).getMember());
+    }
+
+    /**
+     * Test {@link ChangeMembersCommand#undoCommand()}
+     */
+    @Test
+    public void testUndo() {
+        List<RelationMember> members = testData.existingRelation.getMembers();
+        members.add(new RelationMember("n2", testData.existingNode2));
+        Command command = new ChangeMembersCommand(testData.existingRelation, members);
+        command.executeCommand();
+
+        assertEquals(3, testData.existingRelation.getMembersCount());
+
+        command.undoCommand();
+        assertEquals(2, testData.existingRelation.getMembersCount());
+    }
+
+    /**
+     * Test {@link ChangeMembersCommand#getDescriptionText()}
+     */
+    @Test
+    public void testDescription() {
+        testData.existingRelation.put("name", "xy");
+        List<RelationMember> members = testData.existingRelation.getMembers();
+        members.remove(1);
+        assertTrue(new ChangeMembersCommand(testData.existingRelation, members).getDescriptionText().matches("Change members of .*xy.*"));
+    }
+
+    /**
+     * Unit test of methods {@link ChangeMembersCommand#equals} and {@link ChangeMembersCommand#hashCode}.
+     */
+    @Test
+    public void testEqualsContract() {
+        TestUtils.assumeWorkingEqualsVerifier();
+        EqualsVerifier.forClass(ChangeMembersCommand.class).usingGetClass()
+            .withPrefabValues(DataSet.class,
+                new DataSet(), new DataSet())
+            .withPrefabValues(User.class,
+                    User.createOsmUser(1, "foo"), User.createOsmUser(2, "bar"))
+            .withPrefabValues(OsmPrimitive.class,
+                new Node(1), new Node(2))
+            .withPrefabValues(OsmDataLayer.class,
+                new OsmDataLayer(new DataSet(), "1", null), new OsmDataLayer(new DataSet(), "2", null))
+            .suppress(Warning.NONFINAL_FIELDS)
+            .verify();
+    }
+
+}
