Index: trunk/src/org/openstreetmap/josm/command/RelationMemberConflictResolverCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/RelationMemberConflictResolverCommand.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/command/RelationMemberConflictResolverCommand.java	(revision 1631)
@@ -0,0 +1,94 @@
+// 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 javax.swing.JLabel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.MutableTreeNode;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Represent a command for resolving conflicts in the member lists of two
+ * {@see Relation}s.
+ *
+ */
+public class RelationMemberConflictResolverCommand extends Command {
+
+    /** my relation */
+    private final Relation my;
+    /** their relation */
+    private final Relation their;
+    /** the list of merged nodes. This becomes the list of news of my way after the
+     *  command is executed
+     */
+    private final List<RelationMember> mergedMembers;
+
+    /**
+     * 
+     * @param my my relation
+     * @param their their relation
+     * @param mergedNodeList  the list of merged relation members
+     */
+    public RelationMemberConflictResolverCommand(Relation my, Relation their, List<RelationMember> mergedMembers) {
+        this.my = my;
+        this.their = their;
+        this.mergedMembers = mergedMembers;
+    }
+
+
+    @Override
+    public MutableTreeNode description() {
+        return new DefaultMutableTreeNode(
+                new JLabel(
+                        tr("Resolve conflicts in member list of of relation {0}", my.id),
+                        ImageProvider.get("data", "object"),
+                        JLabel.HORIZONTAL
+                )
+        );
+    }
+
+    @Override
+    public boolean executeCommand() {
+        // remember the current state of 'my' way
+        //
+        super.executeCommand();
+
+        // replace the list of nodes of 'my' way by the list of merged
+        // nodes
+        //
+        my.members.clear();
+        for (int i=0; i<mergedMembers.size();i++) {
+            RelationMember n = mergedMembers.get(i);
+            my.members.add(n);
+        }
+        return true;
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
+            Collection<OsmPrimitive> added) {
+        modified.add(my);
+    }
+
+    @Override
+    public void undoCommand() {
+        // restore the former state
+        //
+        super.undoCommand();
+
+        // restore a conflict if necessary
+        //
+        if (!Main.map.conflictDialog.conflicts.containsKey(my)) {
+            Main.map.conflictDialog.conflicts.put(my,their);
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java	(revision 1631)
@@ -7,5 +7,4 @@
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.logging.Logger;
@@ -25,65 +24,55 @@
 import org.openstreetmap.josm.gui.conflict.nodes.NodeListMergeModel;
 import org.openstreetmap.josm.gui.conflict.nodes.NodeListMerger;
+import org.openstreetmap.josm.gui.conflict.relation.RelationMemberListMergeModel;
+import org.openstreetmap.josm.gui.conflict.relation.RelationMemberMerger;
 import org.openstreetmap.josm.gui.conflict.tags.TagMergeModel;
 import org.openstreetmap.josm.gui.conflict.tags.TagMerger;
+import org.openstreetmap.josm.tools.ImageProvider;
 
 /**
  * An UI component for resolving conflicts between two {@see OsmPrimitive}s.
- *   
- *
+ * 
  */
 public class ConflictResolver extends JPanel implements PropertyChangeListener  {
-    
-   private static final Logger logger = Logger.getLogger(ConflictResolver.class.getName());
+
+    private static final Logger logger = Logger.getLogger(ConflictResolver.class.getName());
 
     private JTabbedPane tabbedPane = null;
     private TagMerger tagMerger;
     private NodeListMerger nodeListMerger;
+    private RelationMemberMerger relationMemberMerger;
     private OsmPrimitive my;
     private OsmPrimitive their;
-    
+
     private ImageIcon mergeComplete;
     private ImageIcon mergeIncomplete;
-    
-    // FIXME copied code -> refactor
-    /**
-     * load an icon given by iconName 
-     * 
-     * @param iconName  the name of the icon (without path, i.e. <tt>copystartleft.png</tt>
-     * @return the icon; null, if the icon was not found 
-     */
-    protected ImageIcon getIcon(String iconName) {
-        String fullIconName  = "/images/dialogs/conflict/" + iconName;
-        URL imageURL   = this.getClass().getResource(fullIconName);            
-        if (imageURL == null) {
-            System.out.println(tr("WARNING: failed to load resource {0}", fullIconName));
-            return null;
-        }
-        return new ImageIcon(imageURL);
+
+    protected void loadIcons() {
+        mergeComplete = ImageProvider.get("dialogs/conflict","mergecomplete.png" );
+        mergeIncomplete = ImageProvider.get("dialogs/conflict","mergeincomplete.png" );
     }
-    
-    protected void loadIcons() {
-        mergeComplete = getIcon("mergecomplete.png");
-        mergeIncomplete = getIcon("mergeincomplete.png");
-    }
-    
+
     protected void build() {
         tabbedPane = new JTabbedPane();
-        
+
         tagMerger = new TagMerger();
+        tagMerger.setName("panel.tagmerger");
         tagMerger.getModel().addPropertyChangeListener(this);
         tabbedPane.add("Tags", tagMerger);
-        
+
         nodeListMerger = new NodeListMerger();
+        nodeListMerger.setName("panel.nodelistmerger");
         nodeListMerger.getModel().addPropertyChangeListener(this);
         tabbedPane.add("Nodes", nodeListMerger);
-        
-        tabbedPane.add("Members", new JPanel());
-        
+
+        relationMemberMerger = new RelationMemberMerger();
+        relationMemberMerger.setName("panel.relationmembermerger");
+        relationMemberMerger.getModel().addPropertyChangeListener(this);
+        tabbedPane.add("Members", relationMemberMerger);
+
         setLayout(new BorderLayout());
         add(tabbedPane, BorderLayout.CENTER);
     }
-    
-    
+
     public ConflictResolver() {
         build();
@@ -92,5 +81,5 @@
 
     public void propertyChange(PropertyChangeEvent evt) {
-        
+
         if (evt.getPropertyName().equals(TagMergeModel.PROP_NUM_UNDECIDED_TAGS)) {
             int newValue = (Integer)evt.getNewValue();
@@ -104,32 +93,59 @@
                 tabbedPane.setIconAt(0, mergeIncomplete);
             }
-        } else if (evt.getPropertyName().equals(NodeListMergeModel.PROP_FROZEN)) {
+        } else if (evt.getPropertyName().equals(ListMergeModel.PROP_FROZEN)) {
             boolean frozen = (Boolean)evt.getNewValue();
-            if (frozen) {
+            if (frozen && evt.getSource() == nodeListMerger.getModel()) {
                 tabbedPane.setTitleAt(1, tr("Nodes(resolved)"));
-                tabbedPane.setToolTipTextAt(1, tr("Pending conflicts in the node list of this way"));
+                tabbedPane.setToolTipTextAt(1, tr("Merged node list frozen. No pending conflicts in the node list of this way"));
                 tabbedPane.setIconAt(1, mergeComplete);
             } else {
                 tabbedPane.setTitleAt(1, tr("Nodes(with conflicts)"));
-                tabbedPane.setToolTipTextAt(1, tr("Merged node list frozen. No pending conflicts in the node list of this way"));
+                tabbedPane.setToolTipTextAt(1,tr("Pending conflicts in the node list of this way"));
                 tabbedPane.setIconAt(1, mergeIncomplete);
+            }
+            if (frozen && evt.getSource() == relationMemberMerger.getModel()) {
+                tabbedPane.setTitleAt(2, tr("Members(resolved)"));
+                tabbedPane.setToolTipTextAt(2, tr("Merged member list frozen. No pending conflicts in the member list of this relation"));
+                tabbedPane.setIconAt(2, mergeComplete);
+            } else {
+                tabbedPane.setTitleAt(2, tr("Members(with conflicts)"));
+                tabbedPane.setToolTipTextAt(2, tr("Pending conflicts in the member list of this relation"));
+                tabbedPane.setIconAt(2, mergeIncomplete);
             }
         }
     }
-    
-    public void populate(OsmPrimitive my, OsmPrimitive their) { 
+
+    /**
+     * populates the conflict resolver with the conflicts between my and their
+     * 
+     * @param my   my primitive (i.e. the primitive in the local dataset)
+     * @param their their primitive (i.e. the primitive in the server dataset)
+     * 
+     */
+    public void populate(OsmPrimitive my, OsmPrimitive their) {
         this.my = my;
-        this.their =  their; 
+        this.their =  their;
         tagMerger.getModel().populate(my, their);
-        if (my instanceof Way) {
-           nodeListMerger.populate((Way)my, (Way)their);
-           tabbedPane.setEnabledAt(1, true);
-           tabbedPane.setEnabledAt(2, false);
+        tabbedPane.setEnabledAt(0,true);
+        if (my instanceof Node) {
+            tabbedPane.setEnabledAt(1,false);
+            tabbedPane.setEnabledAt(2,false);
+        } else if (my instanceof Way) {
+            nodeListMerger.populate((Way)my, (Way)their);
+            tabbedPane.setEnabledAt(1, true);
+            tabbedPane.setEnabledAt(2, false);
         } else if (my instanceof Relation) {
+            relationMemberMerger.populate((Relation)my, (Relation)their);
             tabbedPane.setEnabledAt(1, false);
-            tabbedPane.setEnabledAt(2, true);        
-         }
+            tabbedPane.setEnabledAt(2, true);
+        }
     }
-    
+
+    /**
+     * Builds the resolution command(s) for for the resolved conflicts in this
+     * ConflictResolver
+     * 
+     * @return the resolution command
+     */
     public Command buildResolveCommand() {
         ArrayList<Command> commands = new ArrayList<Command>();
@@ -137,27 +153,42 @@
         commands.add(cmd);
         if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
-            commands.add(nodeListMerger.getModel().buildResolveCommand((Way)my, (Way)their));            
+            NodeListMergeModel model  =(NodeListMergeModel)nodeListMerger.getModel();
+            commands.add(model.buildResolveCommand((Way)my, (Way)their));
+        } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) {
+            RelationMemberListMergeModel model  =(RelationMemberListMergeModel)relationMemberMerger.getModel();
+            commands.add(model.buildResolveCommand((Relation)my, (Relation)their));
         }
         if (my instanceof Node) {
-            // resolve the version conflict if this is a node and all tag 
-            // conflicts have been resolved 
-            // 
+            // resolve the version conflict if this is a node and all tag
+            // conflicts have been resolved
+            //
             if (tagMerger.getModel().isResolvedCompletely()) {
                 commands.add(
-                   new VersionConflictResolveCommand(my, their)
+                        new VersionConflictResolveCommand(my, their)
                 );
             }
         } else if (my instanceof Way) {
-            // resolve the version conflict if this is a way, all tag 
+            // resolve the version conflict if this is a way, all tag
             // conflicts have been resolved, and conflicts in the node list
-            // have been resolved 
-            // 
+            // have been resolved
+            //
             if (tagMerger.getModel().isResolvedCompletely() && nodeListMerger.getModel().isFrozen()) {
                 commands.add(
-                   new VersionConflictResolveCommand(my, their)
+                        new VersionConflictResolveCommand(my, their)
                 );
-            }            
+            }
+        }  else if (my instanceof Relation) {
+            // resolve the version conflict if this is a relation, all tag
+            // conflicts and all conflicts in the member list
+            // have been resolved
+            //
+            if (tagMerger.getModel().isResolvedCompletely() && relationMemberMerger.getModel().isFrozen()) {
+                commands.add(
+                        new VersionConflictResolveCommand(my, their)
+                );
+            }
         }
-        return new SequenceCommand("Conflict Resolution", commands);
+
+        return new SequenceCommand(tr("Conflict Resolution"), commands);
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/conflict/ListMergeModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/ListMergeModel.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/ListMergeModel.java	(revision 1631)
@@ -0,0 +1,494 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableModel;
+
+/**
+ * ListMergeModel is a model for interactively comparing and merging two list of entries
+ * of type T. It maintains three lists of entries of type T:
+ * <ol>
+ *   <li>the list of <em>my</em> entries</li>
+ *   <li>the list of <em>their</em> entries</li>
+ *   <li>the list of <em>merged</em> entries</li>
+ * </ol>
+ * 
+ * A ListMergeModel is a factory for three {@see TableModel}s and three {@see ListSelectionModel}s:
+ * <ol>
+ *   <li>the table model and the list selection for for a  {@see JTable} which shows my entries.
+ *    See {@see #getMyTableModel()}</li> and {@see ListMergeModel#getMySelectionModel()}</li>
+ *   <li>dito for their entries and merged entries</li>
+ * </ol>
+ *
+ * A ListMergeModel can be ''frozen''. If it's frozen, it doesn't accept additional merge
+ * decisions. {@see PropertyChangeListener}s can register for property value changes of
+ * {@see #PROP_FROZEN}.
+ * 
+ * ListMergeModel is an abstract class. There methods have to be implemented by subclasses:
+ * <ul>
+ *   <li>{@see ListMergeModel#cloneEntry(Object)} - clones an entry of type T</li>
+ *   <li>{@see ListMergeModel#isEqualEntry(Object, Object)} - checks whether two entries are equals </li>
+ *   <li>{@see ListMergeModel#setValueAt(DefaultTableModel, Object, int, int)} - handles values edited in
+ *     a JTable, dispatched from {@see TableModel#setValueAt(Object, int, int)} </li>
+ * </ul>
+ * A ListMergeModel is used in combination with a {@see ListMerger}.
+ *
+ * @param <T>  the type of the list entries
+ * @see ListMerger
+ */
+public abstract class ListMergeModel<T> {
+    private static final Logger logger = Logger.getLogger(ListMergeModel.class.getName());
+
+    public static final String PROP_FROZEN = ListMergeModel.class.getName() + ".frozen";
+
+    protected ArrayList<T> myEntries;
+    protected ArrayList<T> theirEntries;
+    protected ArrayList<T> mergedEntries;
+
+
+    protected DefaultTableModel myEntriesTableModel;
+    protected DefaultTableModel theirEntriesTableModel;
+    protected DefaultTableModel mergedEntriesTableModel;
+
+    protected DefaultListSelectionModel myEntriesSelectionModel;
+    protected DefaultListSelectionModel theirEntriesSelectionModel;
+    protected DefaultListSelectionModel mergedEntriesSelectionModel;
+
+    private final ArrayList<PropertyChangeListener> listeners;
+    private boolean isFrozen = false;
+
+    /**
+     * Clones an entry of type T
+     * @param entry the entry
+     * @return the cloned entry
+     */
+    protected abstract T cloneEntry(T entry);
+
+    /**
+     * checks whether two entries are equal. This is not necessarily the same as
+     * e1.equals(e2).
+     * 
+     * @param e1  the first entry
+     * @param e2  the second entry
+     * @return true, if the entries are equal, false otherwise.
+     */
+    public abstract boolean isEqualEntry(T e1, T e2);
+
+    /**
+     * Handles method dispatches from {@see TableModel#setValueAt(Object, int, int)}.
+     * 
+     * @param model the table model
+     * @param value  the value to be set
+     * @param row  the row index
+     * @param col the column index
+     * 
+     * @see TableModel#setValueAt(Object, int, int)
+     */
+    protected abstract void setValueAt(DefaultTableModel model, Object value, int row, int col);
+
+
+
+    protected void buildMyEntriesTableModel() {
+        myEntriesTableModel = new ListTableModel<T>(myEntries);
+    }
+
+    protected void buildTheirEntriesTableModel() {
+        theirEntriesTableModel = new ListTableModel<T>(theirEntries);
+    }
+
+    protected void buildMergedEntriesTableModel() {
+        mergedEntriesTableModel = new ListTableModel<T>(mergedEntries);
+    }
+
+    public ListMergeModel() {
+        myEntries = new ArrayList<T>();
+        theirEntries = new ArrayList<T>();
+        mergedEntries = new ArrayList<T>();
+
+        buildMyEntriesTableModel();
+        buildTheirEntriesTableModel();
+        buildMergedEntriesTableModel();
+
+        myEntriesSelectionModel = new DefaultListSelectionModel();
+        theirEntriesSelectionModel = new DefaultListSelectionModel();
+        mergedEntriesSelectionModel = new DefaultListSelectionModel();
+
+        listeners = new ArrayList<PropertyChangeListener>();
+
+        setFrozen(true);
+    }
+
+
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        synchronized(listeners) {
+            if (listener != null && ! listeners.contains(listener)) {
+                listeners.add(listener);
+            }
+        }
+    }
+
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        synchronized(listeners) {
+            if (listener != null && listeners.contains(listener)) {
+                listeners.remove(listener);
+            }
+        }
+    }
+
+    protected void fireFrozenChanged(boolean oldValue, boolean newValue) {
+        synchronized(listeners) {
+            PropertyChangeEvent evt = new PropertyChangeEvent(this, PROP_FROZEN, oldValue, newValue);
+            for (PropertyChangeListener listener: listeners) {
+                listener.propertyChange(evt);
+            }
+        }
+    }
+
+    public void setFrozen(boolean isFrozen) {
+        boolean oldValue = this.isFrozen;
+        this.isFrozen = isFrozen;
+        fireFrozenChanged(oldValue, this.isFrozen);
+    }
+
+    public boolean isFrozen() {
+        return isFrozen;
+    }
+
+    public TableModel getMyTableModel() {
+        return myEntriesTableModel;
+    }
+
+    public TableModel getTheirTableModel() {
+        return theirEntriesTableModel;
+    }
+
+    public TableModel getMergedTableModel() {
+        return mergedEntriesTableModel;
+    }
+
+    public ListSelectionModel getMySelectionModel() {
+        return myEntriesSelectionModel;
+    }
+
+    public ListSelectionModel getTheirSelectionModel() {
+        return theirEntriesSelectionModel;
+    }
+
+    public ListSelectionModel getMergedSelectionModel() {
+        return mergedEntriesSelectionModel;
+    }
+
+
+    protected void fireModelDataChanged() {
+        myEntriesTableModel.fireTableDataChanged();
+        theirEntriesTableModel.fireTableDataChanged();
+        mergedEntriesTableModel.fireTableDataChanged();
+    }
+
+    protected void copyToTop(List<T> source, int []rows) {
+        if (rows == null || rows.length == 0)
+            return;
+        for (int i = rows.length - 1; i >= 0; i--) {
+            int row = rows[i];
+            T n = source.get(row);
+            mergedEntries.add(0, cloneEntry(n));
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.setSelectionInterval(0, rows.length -1);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of my nodes to the
+     * list of merged nodes. Inserts the nodes at the top of the list of merged
+     * nodes.
+     * 
+     * @param rows the indices
+     */
+    public void copyMyToTop(int [] rows) {
+        copyToTop(myEntries, rows);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of their nodes to the
+     * list of merged nodes. Inserts the nodes at the top of the list of merged
+     * nodes.
+     * 
+     * @param rows the indices
+     */
+    public void copyTheirToTop(int [] rows) {
+        copyToTop(theirEntries, rows);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of  nodes in source to the
+     * list of merged nodes. Inserts the nodes at the end of the list of merged
+     * nodes.
+     * 
+     * @param source the list of nodes to copy from
+     * @param rows the indices
+     */
+
+    public void copyToEnd(List<T> source, int [] rows) {
+        if (rows == null || rows.length == 0)
+            return;
+        for (int row : rows) {
+            T n = source.get(row);
+            mergedEntries.add(cloneEntry(n));
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.setSelectionInterval(mergedEntries.size()-rows.length, mergedEntries.size() -1);
+
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of my nodes to the
+     * list of merged nodes. Inserts the nodes at the end of the list of merged
+     * nodes.
+     * 
+     * @param rows the indices
+     */
+    public void copyMyToEnd(int [] rows) {
+        copyToEnd(myEntries, rows);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of their nodes to the
+     * list of merged nodes. Inserts the nodes at the end of the list of merged
+     * nodes.
+     * 
+     * @param rows the indices
+     */
+    public void copyTheirToEnd(int [] rows) {
+        copyToEnd(theirEntries, rows);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of  nodes <code>source</code> to the
+     * list of merged nodes. Inserts the nodes before row given by current.
+     * 
+     * @param source the list of nodes to copy from
+     * @param rows the indices
+     * @param current the row index before which the nodes are inserted
+     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
+     * 
+     */
+    protected void copyBeforeCurrent(List<T> source, int [] rows, int current) {
+        if (rows == null || rows.length == 0)
+            return;
+        if (current < 0 || current >= mergedEntries.size())
+            throw new IllegalArgumentException(tr("parameter current out of range: got {0}", current));
+        for (int i=rows.length -1; i>=0; i--) {
+            int row = rows[i];
+            T n = source.get(row);
+            mergedEntries.add(current, cloneEntry(n));
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.setSelectionInterval(current, current + rows.length-1);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of my nodes to the
+     * list of merged nodes. Inserts the nodes before row given by current.
+     * 
+     * @param rows the indices
+     * @param current the row index before which the nodes are inserted
+     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
+     * 
+     */
+    public void copyMyBeforeCurrent(int [] rows, int current) {
+        copyBeforeCurrent(myEntries,rows,current);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of their nodes to the
+     * list of merged nodes. Inserts the nodes before row given by current.
+     * 
+     * @param rows the indices
+     * @param current the row index before which the nodes are inserted
+     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
+     * 
+     */
+    public void copyTheirBeforeCurrent(int [] rows, int current) {
+        copyBeforeCurrent(theirEntries,rows,current);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of  nodes <code>source</code> to the
+     * list of merged nodes. Inserts the nodes after the row given by current.
+     * 
+     * @param source the list of nodes to copy from
+     * @param rows the indices
+     * @param current the row index after which the nodes are inserted
+     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
+     * 
+     */
+    protected void copyAfterCurrent(List<T> source, int [] rows, int current) {
+        if (rows == null || rows.length == 0)
+            return;
+        if (current < 0 || current >= mergedEntries.size())
+            throw new IllegalArgumentException(tr("parameter current out of range: got {0}", current));
+        if (current == mergedEntries.size() -1) {
+            copyMyToEnd(rows);
+        } else {
+            for (int i=rows.length -1; i>=0; i--) {
+                int row = rows[i];
+                T n = source.get(row);
+                mergedEntries.add(current+1, cloneEntry(n));
+            }
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.setSelectionInterval(current+1, current + rows.length-1);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of my nodes to the
+     * list of merged nodes. Inserts the nodes after the row given by current.
+     * 
+     * @param rows the indices
+     * @param current the row index after which the nodes are inserted
+     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
+     * 
+     */
+    public void copyMyAfterCurrent(int [] rows, int current) {
+        copyAfterCurrent(myEntries, rows, current);
+    }
+
+    /**
+     * Copies the nodes given by indices in rows from the list of my nodes to the
+     * list of merged nodes. Inserts the nodes after the row given by current.
+     * 
+     * @param rows the indices
+     * @param current the row index after which the nodes are inserted
+     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes
+     * 
+     */
+    public void copyTheirAfterCurrent(int [] rows, int current) {
+        copyAfterCurrent(theirEntries, rows, current);
+    }
+
+    /**
+     * Moves the nodes given by indices in rows  up by one position in the list
+     * of merged nodes.
+     * 
+     * @param rows the indices
+     * 
+     */
+    public void moveUpMerged(int [] rows) {
+        if (rows == null || rows.length == 0)
+            return;
+        if (rows[0] == 0)
+            // can't move up
+            return;
+        for (int row: rows) {
+            T n = mergedEntries.get(row);
+            mergedEntries.remove(row);
+            mergedEntries.add(row -1, n);
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.clearSelection();
+        for (int row: rows) {
+            mergedEntriesSelectionModel.addSelectionInterval(row-1, row-1);
+        }
+    }
+
+    /**
+     * Moves the nodes given by indices in rows down by one position in the list
+     * of merged nodes.
+     * 
+     * @param rows the indices
+     */
+    public void moveDownMerged(int [] rows) {
+        if (rows == null || rows.length == 0)
+            return;
+        if (rows[rows.length -1] == mergedEntries.size() -1)
+            // can't move down
+            return;
+        for (int i = rows.length-1; i>=0;i--) {
+            int row = rows[i];
+            T n = mergedEntries.get(row);
+            mergedEntries.remove(row);
+            mergedEntries.add(row +1, n);
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.clearSelection();
+        for (int row: rows) {
+            mergedEntriesSelectionModel.addSelectionInterval(row+1, row+1);
+        }
+    }
+
+    /**
+     * Removes the nodes given by indices in rows from the list
+     * of merged nodes.
+     * 
+     * @param rows the indices
+     */
+    public void removeMerged(int [] rows) {
+        if (rows == null || rows.length == 0)
+            return;
+        for (int i = rows.length-1; i>=0;i--) {
+            mergedEntries.remove(rows[i]);
+        }
+        fireModelDataChanged();
+        mergedEntriesSelectionModel.clearSelection();
+    }
+
+
+    /**
+     * Replies true if the list of my entries and the list of their
+     * entries are equal
+     * 
+     * @return true, if the lists are equal; false otherwise
+     */
+    protected boolean myAndTheirEntriesEqual() {
+        if (myEntries.size() != theirEntries.size())
+            return false;
+        for (int i=0; i < myEntries.size(); i++) {
+            if (! isEqualEntry(myEntries.get(i), theirEntries.get(i)))
+                return false;
+        }
+        return true;
+    }
+
+
+
+    protected class ListTableModel<T> extends DefaultTableModel {
+        private final ArrayList<T> entries;
+
+        public ListTableModel(ArrayList<T> nodes) {
+            this.entries = nodes;
+        }
+
+        @Override
+        public int getRowCount() {
+            return entries == null ? 0 : entries.size();
+        }
+
+        @Override
+        public Object getValueAt(int row, int column) {
+            return entries.get(row);
+        }
+
+        @Override
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+
+        @Override
+        public void setValueAt(Object value, int row, int col) {
+            ListMergeModel.this.setValueAt(this, value,row,col);
+        }
+    }
+
+
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/ListMerger.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/ListMerger.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/ListMerger.java	(revision 1631)
@@ -0,0 +1,730 @@
+package org.openstreetmap.josm.gui.conflict;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JToggleButton;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A UI component for resolving conflicts in two lists of entries of type T.
+ * 
+ * @param T  the type of the entries
+ * @see ListMergeModel
+ */
+public abstract class ListMerger<T> extends JPanel implements PropertyChangeListener {
+    private static final Logger logger = Logger.getLogger(ListMerger.class.getName());
+
+    protected JTable myEntriesTable;
+    protected JTable mergedEntriesTable;
+    protected JTable theirEntriesTable;
+
+    protected ListMergeModel<T> model;
+
+
+    private CopyStartLeftAction copyStartLeftAction;
+    private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction;
+    private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction;
+    private CopyEndLeftAction copyEndLeftAction;
+
+    private CopyStartRightAction copyStartRightAction;
+    private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction;
+    private CopyAfterCurrentRightAction copyAfterCurrentRightAction;
+    private CopyEndRightAction copyEndRightAction;
+
+    private MoveUpMergedAction moveUpMergedAction;
+    private MoveDownMergedAction moveDownMergedAction;
+    private RemoveMergedAction removeMergedAction;
+    private FreezeAction freezeAction;
+
+
+
+    protected JScrollPane embeddInScrollPane(JTable table) {
+        JScrollPane pane = new JScrollPane(table);
+        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        return pane;
+    }
+
+    abstract protected JScrollPane buildMyElementsTable();
+    abstract protected JScrollPane buildMergedElementsTable();
+    abstract protected JScrollPane buildTheirElementsTable();
+
+
+
+    protected void wireActionsToSelectionModels() {
+        myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction);
+
+        myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
+
+        myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
+
+        myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction);
+
+
+        theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction);
+
+        theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
+
+        theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
+
+        theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction);
+
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction);
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction);
+        mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction);
+    }
+
+
+
+    protected JPanel buildLeftButtonPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        gc.gridx = 0;
+        gc.gridy = 0;
+        copyStartLeftAction = new CopyStartLeftAction();
+        JButton btn = new JButton(copyStartLeftAction);
+        btn.setName("button.copystartleft");
+        pnl.add(btn, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction();
+        btn = new JButton(copyBeforeCurrentLeftAction);
+        btn.setName("button.copybeforecurrentleft");
+        pnl.add(btn, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 2;
+        copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction();
+        btn = new JButton(copyAfterCurrentLeftAction);
+        btn.setName("button.copyaftercurrentleft");
+        pnl.add(btn, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 3;
+        copyEndLeftAction = new CopyEndLeftAction();
+        btn = new JButton(copyEndLeftAction);
+        btn.setName("button.copyendleft");
+        pnl.add(btn, gc);
+
+        return pnl;
+    }
+
+    protected JPanel buildRightButtonPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        gc.gridx = 0;
+        gc.gridy = 0;
+        copyStartRightAction = new CopyStartRightAction();
+        pnl.add(new JButton(copyStartRightAction), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction();
+        pnl.add(new JButton(copyBeforeCurrentRightAction), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 2;
+        copyAfterCurrentRightAction = new CopyAfterCurrentRightAction();
+        pnl.add(new JButton(copyAfterCurrentRightAction), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 3;
+        copyEndRightAction = new CopyEndRightAction();
+        pnl.add(new JButton(copyEndRightAction), gc);
+
+        return pnl;
+    }
+
+    protected JPanel buildMergedListControlButtons() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.3;
+        gc.weighty = 0.0;
+        moveUpMergedAction = new MoveUpMergedAction();
+        pnl.add(new JButton(moveUpMergedAction), gc);
+
+        gc.gridx = 1;
+        gc.gridy = 0;
+        moveDownMergedAction = new MoveDownMergedAction();
+        pnl.add(new JButton(moveDownMergedAction), gc);
+
+        gc.gridx = 2;
+        gc.gridy = 0;
+        removeMergedAction = new RemoveMergedAction();
+        pnl.add(new JButton(removeMergedAction), gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = 3;
+        gc.weightx = 1.0;
+        freezeAction = new FreezeAction();
+        JToggleButton btn = new JToggleButton(freezeAction);
+        freezeAction.adapt(btn);
+        btn.setName("button.freeze");
+        btn.addItemListener(freezeAction);
+        pnl.add(btn, gc);
+
+        return pnl;
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+
+        gc.gridx = 0;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(10,0,10,0);
+        JLabel lbl = new JLabel(tr("My version"));
+        lbl.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset"));
+        add(lbl, gc);
+
+        gc.gridx = 2;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        lbl = new JLabel(tr("Merged version"));
+        lbl.setToolTipText(tr("List of merged elements. They will replace the my elements when the merge decisions are applied."));
+        add(lbl, gc);
+
+        gc.gridx = 4;
+        gc.gridy = 0;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        lbl = new JLabel(tr("Their version"));
+        lbl.setToolTipText(tr("List of elements in their dataset, i.e. the server dataset"));
+
+        add(lbl, gc);
+
+        gc.gridx = 0;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        gc.weightx = 0.3;
+        gc.weighty = 1.0;
+        gc.insets = new Insets(0,0,0,0);
+        add(buildMyElementsTable(), gc);
+
+        gc.gridx = 1;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        add(buildLeftButtonPanel(), gc);
+
+        gc.gridx = 2;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        gc.weightx = 0.3;
+        gc.weighty = 0.0;
+        add(buildMergedElementsTable(), gc);
+
+        gc.gridx = 3;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        add(buildRightButtonPanel(), gc);
+
+        gc.gridx = 4;
+        gc.gridy = 1;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.FIRST_LINE_START;
+        gc.weightx = 0.3;
+        gc.weighty = 0.0;
+        add(buildTheirElementsTable(), gc);
+
+        gc.gridx = 2;
+        gc.gridy = 2;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.3;
+        gc.weighty = 0.0;
+        add(buildMergedListControlButtons(), gc);
+
+        wireActionsToSelectionModels();
+    }
+
+    public ListMerger(ListMergeModel<T> model) {
+        this.model = model;
+        build();
+        model.addPropertyChangeListener(this);
+    }
+
+
+    /**
+     * Action for copying selected nodes in the list of my nodes to the list of merged
+     * nodes. Inserts the nodes at the beginning of the list of merged nodes.
+     *
+     */
+    class CopyStartLeftAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyStartLeftAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartleft.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, tr("> top"));
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes to the start of the merged node list"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = myEntriesTable.getSelectedRows();
+            model.copyMyToTop(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty());
+        }
+    }
+
+    /**
+     * Action for copying selected nodes in the list of my nodes to the list of merged
+     * nodes. Inserts the nodes at the end of the list of merged nodes.
+     *
+     */
+    class CopyEndLeftAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyEndLeftAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendleft.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, tr("> bottom"));
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements to the end of the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = myEntriesTable.getSelectedRows();
+            model.copyMyToEnd(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty());
+        }
+    }
+
+    /**
+     * Action for copying selected nodes in the list of my nodes to the list of merged
+     * nodes. Inserts the nodes before the first selected row in the list of merged nodes.
+     *
+     */
+    class CopyBeforeCurrentLeftAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyBeforeCurrentLeftAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentleft.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, "> before");
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements before the first selected element in the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] myRows = myEntriesTable.getSelectedRows();
+            int [] mergedRows = mergedEntriesTable.getSelectedRows();
+            if (mergedRows == null || mergedRows.length == 0)
+                return;
+            int current = mergedRows[0];
+            model.copyMyBeforeCurrent(myRows, current);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(
+                    !myEntriesTable.getSelectionModel().isSelectionEmpty()
+                    && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
+            );
+        }
+    }
+
+    /**
+     * Action for copying selected nodes in the list of my nodes to the list of merged
+     * nodes. Inserts the nodes after the first selected row in the list of merged nodes.
+     *
+     */
+    class CopyAfterCurrentLeftAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyAfterCurrentLeftAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentleft.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, "> after");
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements after the first selected element in the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] myRows = myEntriesTable.getSelectedRows();
+            int [] mergedRows = mergedEntriesTable.getSelectedRows();
+            if (mergedRows == null || mergedRows.length == 0)
+                return;
+            int current = mergedRows[0];
+            model.copyMyAfterCurrent(myRows, current);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(
+                    !myEntriesTable.getSelectionModel().isSelectionEmpty()
+                    && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
+            );
+        }
+    }
+
+
+    class CopyStartRightAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyStartRightAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartright.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, "< top");
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element to the start of the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = theirEntriesTable.getSelectedRows();
+            model.copyTheirToTop(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty());
+        }
+    }
+
+
+    class CopyEndRightAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyEndRightAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendright.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, "< bottom");
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements to the end of the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = theirEntriesTable.getSelectedRows();
+            model.copyTheirToEnd(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty());
+        }
+    }
+
+    class CopyBeforeCurrentRightAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyBeforeCurrentRightAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentright.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, "< before");
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements before the first selected element in the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] myRows = theirEntriesTable.getSelectedRows();
+            int [] mergedRows = mergedEntriesTable.getSelectedRows();
+            if (mergedRows == null || mergedRows.length == 0)
+                return;
+            int current = mergedRows[0];
+            model.copyTheirBeforeCurrent(myRows, current);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(
+                    !theirEntriesTable.getSelectionModel().isSelectionEmpty()
+                    && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
+            );
+        }
+    }
+
+
+    class CopyAfterCurrentRightAction extends AbstractAction implements ListSelectionListener {
+
+        public CopyAfterCurrentRightAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentright.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, "< after");
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element after the first selected element in the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] myRows = theirEntriesTable.getSelectedRows();
+            int [] mergedRows = mergedEntriesTable.getSelectedRows();
+            if (mergedRows == null || mergedRows.length == 0)
+                return;
+            int current = mergedRows[0];
+            model.copyTheirAfterCurrent(myRows, current);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            setEnabled(
+                    !theirEntriesTable.getSelectionModel().isSelectionEmpty()
+                    && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
+            );
+        }
+    }
+
+
+    class MoveUpMergedAction extends AbstractAction implements ListSelectionListener {
+
+        public MoveUpMergedAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, tr("Up"));
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected elements by one position"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = mergedEntriesTable.getSelectedRows();
+            model.moveUpMerged(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            int [] rows = mergedEntriesTable.getSelectedRows();
+            setEnabled(
+                    rows != null
+                    && rows.length > 0
+                    && rows[0] != 0
+            );
+        }
+    }
+
+    /**
+     * Action for moving the currently selected entries in the list of merged entries
+     * one position down
+     *
+     */
+    class MoveDownMergedAction extends AbstractAction implements ListSelectionListener {
+
+        public MoveDownMergedAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, tr("Down"));
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = mergedEntriesTable.getSelectedRows();
+            model.moveDownMerged(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            int [] rows = mergedEntriesTable.getSelectedRows();
+            setEnabled(
+                    rows != null
+                    && rows.length > 0
+                    && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1
+            );
+        }
+    }
+
+    /**
+     * Action for removing the selected entries in the list of merged entries
+     * from the list of merged entries.
+     * 
+     */
+    class RemoveMergedAction extends AbstractAction implements ListSelectionListener {
+
+        public RemoveMergedAction() {
+            ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png");
+            putValue(Action.SMALL_ICON, icon);
+            if (icon == null) {
+                putValue(Action.NAME, tr("Remove"));
+            }
+            putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements"));
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            int [] rows = mergedEntriesTable.getSelectedRows();
+            model.removeMerged(rows);
+        }
+
+        public void valueChanged(ListSelectionEvent e) {
+            int [] rows = mergedEntriesTable.getSelectedRows();
+            setEnabled(
+                    rows != null
+                    && rows.length > 0
+            );
+        }
+    }
+
+    static public interface FreezeActionProperties {
+        String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected";
+    }
+
+    /**
+     * Action for freezing the current state of the list merger
+     *
+     */
+    class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties  {
+
+        public FreezeAction() {
+            putValue(Action.NAME, tr("Freeze"));
+            putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements"));
+            putValue(PROP_SELECTED, false);
+            setEnabled(true);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            // do nothing
+        }
+
+        /**
+         * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action
+         * such that the action gets notified about item state changes and the button gets
+         * notified about selection state changes of the action.
+         * 
+         * @param btn a toggle button
+         */
+        public void adapt(final JToggleButton btn) {
+            //            btn.addItemListener(
+            //                    new ItemListener() {
+            //                        public void itemStateChanged(ItemEvent e) {
+            //                            boolean isSelected = (Boolean)getValue(PROP_SELECTED);
+            //                            if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) {
+            //                                putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED);
+            //                            }
+            //                            model.setFrozen(e.getStateChange() == ItemEvent.SELECTED);
+            //                        }
+            //                    }
+            //            );
+            btn.addItemListener(this);
+            addPropertyChangeListener(
+                    new PropertyChangeListener() {
+                        public void propertyChange(PropertyChangeEvent evt) {
+                            if (evt.getPropertyName().equals(PROP_SELECTED)) {
+                                btn.setSelected((Boolean)evt.getNewValue());
+                            }
+                        }
+                    }
+            );
+        }
+
+        public void itemStateChanged(ItemEvent e) {
+            int state = e.getStateChange();
+            if (state == ItemEvent.SELECTED) {
+                model.setFrozen(true);
+                putValue(Action.NAME, tr("Unfreeze"));
+                putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging"));
+            } else if (state == ItemEvent.DESELECTED) {
+                model.setFrozen(false);
+                putValue(Action.NAME, tr("Freeze"));
+                putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements"));
+            }
+            boolean isSelected = (Boolean)getValue(PROP_SELECTED);
+            if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) {
+                putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED);
+            }
+
+        }
+    }
+
+    protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) {
+        myEntriesTable.getSelectionModel().clearSelection();
+        myEntriesTable.setEnabled(!newValue);
+        theirEntriesTable.getSelectionModel().clearSelection();
+        theirEntriesTable.setEnabled(!newValue);
+        mergedEntriesTable.getSelectionModel().clearSelection();
+        mergedEntriesTable.setEnabled(!newValue);
+        freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue);
+    }
+
+
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt.getPropertyName().equals(ListMergeModel.PROP_FROZEN)) {
+            handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue());
+        }
+    }
+
+    public ListMergeModel<T> getModel() {
+        return model;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListColumnModel.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListColumnModel.java	(revision 1631)
@@ -11,13 +11,13 @@
 
     protected void createColumns(TableCellRenderer renderer) {
-        
+
         TableColumn col = null;
-        
-        // column 0 - Node  
+
+        // column 0 - Node
         col = new TableColumn(0);
         col.setHeaderValue(tr("Node"));
         col.setResizable(true);
         col.setCellRenderer(renderer);
-        addColumn(col);        
+        addColumn(col);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListMergeModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListMergeModel.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListMergeModel.java	(revision 1631)
@@ -2,412 +2,24 @@
 package org.openstreetmap.josm.gui.conflict.nodes;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.logging.Logger;
 
-import javax.swing.DefaultListSelectionModel;
-import javax.swing.ListSelectionModel;
 import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableModel;
 
 import org.openstreetmap.josm.command.WayNodesConflictResolverCommand;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.conflict.ListMergeModel;
 
-public class NodeListMergeModel {
+public class NodeListMergeModel extends ListMergeModel<Node>{
+
     private static final Logger logger = Logger.getLogger(NodeListMergeModel.class.getName());
-    
-    public static final String PROP_FROZEN = NodeListMergeModel.class.getName() + ".frozen";
-    
 
-    private ArrayList<Node> myNodes;
-    private ArrayList<Node> theirNodes;
-    private ArrayList<Node> mergedNodes;
-    
-    
-    private DefaultTableModel myNodesTableModel;
-    private DefaultTableModel theirNodesTableModel;
-    private DefaultTableModel mergedNodesTableModel;
-    
-    private DefaultListSelectionModel myNodesSelectionModel;
-    private DefaultListSelectionModel theirNodesSelectionModel;
-    private DefaultListSelectionModel mergedNodesSelectionModel;
-    
-    private ArrayList<PropertyChangeListener> listeners;
-    private boolean isFrozen = false; 
-    
-    
-    public NodeListMergeModel() {
-        myNodes = new ArrayList<Node>();
-        theirNodes = new ArrayList<Node>();
-        mergedNodes = new ArrayList<Node>();
-        
-        myNodesTableModel = new NodeListTableModel(myNodes);
-        theirNodesTableModel = new NodeListTableModel(theirNodes);
-        mergedNodesTableModel = new NodeListTableModel(mergedNodes);
-        
-        myNodesSelectionModel = new DefaultListSelectionModel();
-        theirNodesSelectionModel = new DefaultListSelectionModel();
-        mergedNodesSelectionModel = new DefaultListSelectionModel();
-        
-        listeners = new ArrayList<PropertyChangeListener>();
-        
-        setFrozen(true);
-    }
-    
-    
-    public void addPropertyChangeListener(PropertyChangeListener listener) {
-        synchronized(listeners) {
-            if (listener != null && ! listeners.contains(listener)) {
-                listeners.add(listener);
-            }
-        }
-    }
-    
-    public void removePropertyChangeListener(PropertyChangeListener listener) {
-        synchronized(listeners) {
-            if (listener != null && listeners.contains(listener)) {
-                listeners.remove(listener);
-            }
-        }
-    }
-    
-    protected void fireFrozenChanged(boolean oldValue, boolean newValue) {
-        synchronized(listeners) {
-            PropertyChangeEvent evt = new PropertyChangeEvent(this, PROP_FROZEN, oldValue, newValue);
-            for (PropertyChangeListener listener: listeners) {
-                listener.propertyChange(evt);
-            }
-        }
-    }
-    
-    public void setFrozen(boolean isFrozen) {
-        boolean oldValue = this.isFrozen;
-        this.isFrozen = isFrozen;
-        fireFrozenChanged(oldValue, this.isFrozen);
-    }
-    
-    public boolean isFrozen() {
-        return isFrozen;
-    }
-    
-    public TableModel getMyNodesTableModel() {
-        return myNodesTableModel;
-    }
-    
-    public TableModel getTheirNodesTableModel() {
-        return theirNodesTableModel;
-    }
-    
-    public TableModel getMergedNodesTableModel() {
-        return mergedNodesTableModel;
-    }
-    
-    public ListSelectionModel getMyNodesSelectionModel() {
-        return myNodesSelectionModel;
-    }
 
-    public ListSelectionModel getTheirNodesSelectionModel() {
-        return theirNodesSelectionModel;
-    }
-    
-    public ListSelectionModel getMergedNodesSelectionModel() {
-        return mergedNodesSelectionModel;
-    }
-    
-    
-    protected void fireModelDataChanged() {
-        myNodesTableModel.fireTableDataChanged();
-        theirNodesTableModel.fireTableDataChanged();
-        mergedNodesTableModel.fireTableDataChanged();
-    }
-    
-    protected void copyNodesToTop(List<Node> source, int []rows) {
-        if (rows == null || rows.length == 0) {
-            return;
-        }
-        for (int i = rows.length - 1; i >= 0; i--) {
-            int row = rows[i];
-            Node n = source.get(row);
-            mergedNodes.add(0, n);
-        }
-        fireModelDataChanged();
-        mergedNodesSelectionModel.setSelectionInterval(0, rows.length -1);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of my nodes to the
-     * list of merged nodes. Inserts the nodes at the top of the list of merged
-     * nodes.  
-     * 
-     * @param rows the indices 
-     */
-    public void copyMyNodesToTop(int [] rows) {
-        copyNodesToTop(myNodes, rows);        
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of their nodes to the
-     * list of merged nodes. Inserts the nodes at the top of the list of merged
-     * nodes.  
-     * 
-     * @param rows the indices 
-     */
-    public void copyTheirNodesToTop(int [] rows) {
-        copyNodesToTop(theirNodes, rows);        
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of  nodes in source to the
-     * list of merged nodes. Inserts the nodes at the end of the list of merged
-     * nodes.  
-     * 
-     * @param source the list of nodes to copy from
-     * @param rows the indices 
-     */    
-
-    public void copyNodesToEnd(List<Node> source, int [] rows) {
-        if (rows == null || rows.length == 0) {
-            return;
-        }
-        for (int row : rows) {
-            Node n = source.get(row);
-            mergedNodes.add(n);
-        }
-        fireModelDataChanged();
-        mergedNodesSelectionModel.setSelectionInterval(mergedNodes.size()-rows.length, mergedNodes.size() -1);
-
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of my nodes to the
-     * list of merged nodes. Inserts the nodes at the end of the list of merged
-     * nodes.  
-     * 
-     * @param rows the indices 
-     */    
-    public void copyMyNodesToEnd(int [] rows) {
-        copyNodesToEnd(myNodes, rows);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of their nodes to the
-     * list of merged nodes. Inserts the nodes at the end of the list of merged
-     * nodes.  
-     * 
-     * @param rows the indices 
-     */    
-    public void copyTheirNodesToEnd(int [] rows) {
-        copyNodesToEnd(theirNodes, rows);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of  nodes <code>source</code> to the
-     * list of merged nodes. Inserts the nodes before row given by current.
-     * 
-     * @param source the list of nodes to copy from 
-     * @param rows the indices 
-     * @param current the row index before which the nodes are inserted
-     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 
-     * 
-     */  
-    protected void copyNodesBeforeCurrent(List<Node> source, int [] rows, int current) {
-        if (rows == null || rows.length == 0) {
-            return; 
-        }
-        if (current < 0 || current >= mergedNodes.size()) {
-            throw new IllegalArgumentException(tr("parameter current out of range: got {0}", current));
-        }
-        for (int i=rows.length -1; i>=0; i--) {
-            int row = rows[i];
-            Node n = source.get(row);
-            mergedNodes.add(current, n);
-        }
-        fireModelDataChanged();
-        mergedNodesSelectionModel.setSelectionInterval(current, current + rows.length-1);
-     }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of my nodes to the
-     * list of merged nodes. Inserts the nodes before row given by current.
-     * 
-     * @param rows the indices 
-     * @param current the row index before which the nodes are inserted
-     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 
-     * 
-     */       
-    public void copyMyNodesBeforeCurrent(int [] rows, int current) {
-        copyNodesBeforeCurrent(myNodes,rows,current);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of their nodes to the
-     * list of merged nodes. Inserts the nodes before row given by current.
-     * 
-     * @param rows the indices 
-     * @param current the row index before which the nodes are inserted
-     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 
-     * 
-     */       
-    public void copyTheirNodesBeforeCurrent(int [] rows, int current) {
-        copyNodesBeforeCurrent(theirNodes,rows,current);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of  nodes <code>source</code> to the
-     * list of merged nodes. Inserts the nodes after the row given by current.
-     * 
-     * @param source the list of nodes to copy from 
-     * @param rows the indices 
-     * @param current the row index after which the nodes are inserted
-     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 
-     * 
-     */       
-    protected void copyNodesAfterCurrent(List<Node> source, int [] rows, int current) {
-        if (rows == null || rows.length == 0) {
-            return;
-        }
-        if (current < 0 || current >= mergedNodes.size()) {
-            throw new IllegalArgumentException(tr("parameter current out of range: got {0}", current));
-        }
-        if (current == mergedNodes.size() -1) {
-            copyMyNodesToEnd(rows);
-        } else {
-            for (int i=rows.length -1; i>=0; i--) {
-                int row = rows[i];
-                Node n = source.get(row); 
-                mergedNodes.add(current+1, n);
-            }
-        }
-        fireModelDataChanged();   
-        mergedNodesSelectionModel.setSelectionInterval(current+1, current + rows.length-1);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of my nodes to the
-     * list of merged nodes. Inserts the nodes after the row given by current.
-     * 
-     * @param rows the indices 
-     * @param current the row index after which the nodes are inserted
-     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 
-     * 
-     */       
-    public void copyMyNodesAfterCurrent(int [] rows, int current) {
-        copyNodesAfterCurrent(myNodes, rows, current);
-    }
-    
-    /**
-     * Copies the nodes given by indices in rows from the list of my nodes to the
-     * list of merged nodes. Inserts the nodes after the row given by current.
-     * 
-     * @param rows the indices 
-     * @param current the row index after which the nodes are inserted
-     * @exception IllegalArgumentException thrown, if current < 0 or >= #nodes in list of merged nodes 
-     * 
-     */       
-    public void copyTheirNodesAfterCurrent(int [] rows, int current) {
-        copyNodesAfterCurrent(theirNodes, rows, current);
-    }
-
-    /**
-     * Moves the nodes given by indices in rows  up by one position in the list
-     * of merged nodes.
-     * 
-     * @param rows the indices 
-     * 
-     */
-    protected void moveUpMergedNodes(int [] rows) {
-        if (rows == null || rows.length == 0) {
-            return;
-        }
-        if (rows[0] == 0) {
-            // can't move up
-            return;
-        }
-        for (int row: rows) {
-           Node n = mergedNodes.get(row);
-           mergedNodes.remove(row);
-           mergedNodes.add(row -1, n);
-        }
-        fireModelDataChanged();
-        mergedNodesSelectionModel.clearSelection();
-        for (int row: rows) {
-            mergedNodesSelectionModel.addSelectionInterval(row-1, row-1);
-        }
-    }
-
-    /**
-     * Moves the nodes given by indices in rows down by one position in the list
-     * of merged nodes.
-     * 
-     * @param rows the indices 
-     */
-    protected void moveDownMergedNodes(int [] rows) {
-        if (rows == null || rows.length == 0) {
-            return;
-        }
-        if (rows[rows.length -1] == mergedNodes.size() -1) {
-            // can't move down
-            return;
-        }
-        for (int i = rows.length-1; i>=0;i--) {
-            int row = rows[i];
-            Node n = mergedNodes.get(row);
-            mergedNodes.remove(row);
-            mergedNodes.add(row +1, n);
-         }
-        fireModelDataChanged();
-        mergedNodesSelectionModel.clearSelection();
-        for (int row: rows) {
-            mergedNodesSelectionModel.addSelectionInterval(row+1, row+1);
-        }        
-    }
-    
-    /**
-     * Removes the nodes given by indices in rows from the list
-     * of merged nodes.
-     * 
-     * @param rows the indices 
-     */    
-    protected void removeMergedNodes(int [] rows) {
-        if (rows == null || rows.length == 0) {
-            return;
-        }
-        for (int i = rows.length-1; i>=0;i--) {
-            mergedNodes.remove(rows[i]);
-         }
-        fireModelDataChanged();
-        mergedNodesSelectionModel.clearSelection();
-    }
-    
-
-    /**
-     * Replies true if the list of my nodes and the list of their
-     * nodes are equal, i.e. if they consists of a list of nodes with
-     * identical ids in the same order.
-     * 
-     * @return true, if the lists are equal; false otherwise 
-     */
-    protected boolean myAndTheirNodesEqual() {
-        if (myNodes.size() != theirNodes.size()) {
-            return false;
-        }
-        for (int i=0; i < myNodes.size(); i++) {
-            if (myNodes.get(i).id != theirNodes.get(i).id) {
-                return false; 
-            }
-        }
-        return true; 
-    }
-    
     /**
      * Populates the model with the nodes in the two {@see Way}s <code>my</code> and
      * <code>their</code>.
-     *  
-     * @param my  my way (i.e. the way in the local dataset) 
+     * 
+     * @param my  my way (i.e. the way in the local dataset)
      * @param their their way (i.e. the way in the server dataset)
      * @exception IllegalArgumentException thrown, if my is null
@@ -415,73 +27,63 @@
      */
     public void populate(Way my, Way their) {
-        if (my == null) 
+        if (my == null)
             throw new IllegalArgumentException("parameter 'way' must not be null");
-        if (their == null) 
+        if (their == null)
             throw new IllegalArgumentException("parameter 'their' must not be null");
-        mergedNodes.clear();
-        myNodes.clear();
-        theirNodes.clear();
+        mergedEntries.clear();
+        myEntries.clear();
+        theirEntries.clear();
         for (Node n : my.nodes) {
-            myNodes.add(n);
+            myEntries.add(n);
         }
         for (Node n : their.nodes) {
-            theirNodes.add(n);
+            theirEntries.add(n);
         }
-        if (myAndTheirNodesEqual()) {
-            mergedNodes = new ArrayList<Node>(myNodes);
+        if (myAndTheirEntriesEqual()) {
+            mergedEntries = new ArrayList<Node>(myEntries);
             setFrozen(true);
         } else {
             setFrozen(false);
         }
-        
+
         fireModelDataChanged();
     }
-    
+
     /**
      * Builds the command to resolve conflicts in the node list of a way
      * 
-     * @param my  my way. Must not be null. 
+     * @param my  my way. Must not be null.
      * @param their  their way. Must not be null
      * @return the command
      * @exception IllegalArgumentException thrown, if my is null or not a {@see Way}
      * @exception IllegalArgumentException thrown, if their is null or not a {@see Way}
-     * @exception IllegalStateException thrown, if the merge is not yet frozen 
+     * @exception IllegalStateException thrown, if the merge is not yet frozen
      */
-    public WayNodesConflictResolverCommand buildResolveCommand(Way my, Way their) {        
-        if (my == null) {
-            throw new IllegalArgumentException("parameter my most not be null");            
-        }
-        if (their == null) {
-            throw new IllegalArgumentException("parameter my most not be null");            
-        }
-        if (! isFrozen()) {
+    public WayNodesConflictResolverCommand buildResolveCommand(Way my, Way their) {
+        if (my == null)
+            throw new IllegalArgumentException("parameter my most not be null");
+        if (their == null)
+            throw new IllegalArgumentException("parameter my most not be null");
+        if (! isFrozen())
             throw new IllegalArgumentException("merged nodes not frozen yet. Can't build resolution command");
-        }
-        return new WayNodesConflictResolverCommand(my, their, mergedNodes);
+        return new WayNodesConflictResolverCommand(my, their, mergedEntries);
     }
-    
-    class NodeListTableModel extends DefaultTableModel {
-        private ArrayList<Node> nodes;
-        
-        public NodeListTableModel(ArrayList<Node> nodes) {
-            this.nodes = nodes; 
-        }
-        
-        @Override
-        public int getRowCount() {
-            return nodes == null ? 0 : nodes.size();
-        }
-
-        @Override
-        public Object getValueAt(int row, int column) {
-            return nodes.get(row);           
-        }
-
-        @Override
-        public boolean isCellEditable(int row, int column) {
-            return false;
-        }  
-    }    
 
 
+    @Override
+    public boolean isEqualEntry(Node e1, Node e2) {
+        return e1.id == e2.id;
+    }
+
+    @Override
+    protected void setValueAt(DefaultTableModel model, Object value, int row, int col) {
+        // do nothing - node list tables are not editable
+    }
+
+    @Override
+    protected Node cloneEntry(Node entry) {
+        Node n = new Node(entry.id);
+        n.cloneFrom(entry);
+        return n;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListMerger.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListMerger.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListMerger.java	(revision 1631)
@@ -1,29 +1,12 @@
 package org.openstreetmap.josm.gui.conflict.nodes;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.net.URL;
 import java.util.logging.Logger;
 
-import javax.swing.AbstractAction;
-import javax.swing.Action;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
-import javax.swing.JToggleButton;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
 
+import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.conflict.ListMerger;
 
 /**
@@ -31,710 +14,54 @@
  * 
  */
-public class NodeListMerger extends JPanel implements PropertyChangeListener {
+public class NodeListMerger extends ListMerger<Node> {
     private static final Logger logger = Logger.getLogger(NodeListMerger.class.getName());
-    
-    private JTable myNodes;
-    private JTable mergedNodes;
-    private JTable theirNodes;
-    
-    private NodeListMergeModel model;
-    
-    
-    private CopyStartLeftAction copyStartLeftAction;
-    private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction; 
-    private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction;
-    private CopyEndLeftAction copyEndLeftAction;
 
-    private CopyStartRightAction copyStartRightAction;
-    private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction; 
-    private CopyAfterCurrentRightAction copyAfterCurrentRightAction;
-    private CopyEndRightAction copyEndRightAction;
-    
-    private MoveUpMergedAction moveUpMergedAction;
-    private MoveDownMergedAction moveDownMergedAction;
-    private RemoveMergedAction removeMergedAction;
-    private FreezeAction freezeAction;
-    
 
-    
-    protected JScrollPane embeddInScrollPane(JTable table) {
-        JScrollPane pane = new JScrollPane(table);
-        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
-       return pane;
-    }
-    
-    protected JScrollPane buildMyNodesTable() {
-        myNodes  = new JTable(
-            model.getMyNodesTableModel(),
-            new NodeListColumnModel(
-               new NodeListTableCellRenderer()
-            ),
-            model.getMyNodesSelectionModel()
-         );
-         myNodes.setName("table.mynodes");
-         return embeddInScrollPane(myNodes);
+    public NodeListMerger() {
+        super(new NodeListMergeModel());
     }
 
-    protected JScrollPane buildMergedNodesTable() {
-        mergedNodes  = new JTable(
-            model.getMergedNodesTableModel(),
-            new NodeListColumnModel(
-                new NodeListTableCellRenderer()
-            ),
-            model.getMergedNodesSelectionModel()
-         );
-         mergedNodes.setName("table.mergednodes");
-         return embeddInScrollPane(mergedNodes);
-    }
-    
-    protected JScrollPane buildTheirNodesTable() {
-        theirNodes  = new JTable(
-            model.getTheirNodesTableModel(),
-            new NodeListColumnModel(
-                new NodeListTableCellRenderer()
-            ),
-            model.getTheirNodesSelectionModel()
-         );
-        theirNodes.setName("table.theirnodes");
-        return embeddInScrollPane(theirNodes);
-    }
-    
-    protected void wireActionsToSelectionModels() {
-        myNodes.getSelectionModel().addListSelectionListener(copyStartLeftAction);
-        
-        myNodes.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
-        mergedNodes.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
-        
-        myNodes.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
-        mergedNodes.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
-        
-        myNodes.getSelectionModel().addListSelectionListener(copyEndLeftAction);
-        
-        
-        theirNodes.getSelectionModel().addListSelectionListener(copyStartRightAction);
-        
-        theirNodes.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
-        mergedNodes.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
-        
-        theirNodes.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
-        mergedNodes.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
-        
-        theirNodes.getSelectionModel().addListSelectionListener(copyEndRightAction);      
-        
-        mergedNodes.getSelectionModel().addListSelectionListener(moveUpMergedAction);
-        mergedNodes.getSelectionModel().addListSelectionListener(moveDownMergedAction);
-        mergedNodes.getSelectionModel().addListSelectionListener(removeMergedAction);
-    }
-    
-    
-    
-    protected JPanel buildLeftButtonPanel() {
-        JPanel pnl = new JPanel();
-        pnl.setLayout(new GridBagLayout());
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 0;
-        gc.gridy = 0;     
-        copyStartLeftAction = new CopyStartLeftAction();   
-        JButton btn = new JButton(copyStartLeftAction);
-        btn.setName("button.copystartleft");
-        pnl.add(btn, gc);
-        
-        gc.gridx = 0;
-        gc.gridy = 1;
-        copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction();
-        btn = new JButton(copyBeforeCurrentLeftAction);
-        btn.setName("button.copybeforecurrentleft");
-        pnl.add(btn, gc);
-
-        gc.gridx = 0;
-        gc.gridy = 2;        
-        copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction();
-        btn = new JButton(copyAfterCurrentLeftAction);
-        btn.setName("button.copyaftercurrentleft");
-        pnl.add(btn, gc);
-        
-        gc.gridx = 0;
-        gc.gridy = 3;
-        copyEndLeftAction = new CopyEndLeftAction();
-        btn = new JButton(copyEndLeftAction);
-        btn.setName("button.copyendleft");
-        pnl.add(btn, gc);
-
-        
-        return pnl;
-    }
-    
-    protected JPanel buildRightButtonPanel() {
-        JPanel pnl = new JPanel();
-        pnl.setLayout(new GridBagLayout());
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 0;
-        gc.gridy = 0;     
-        copyStartRightAction = new CopyStartRightAction();
-        pnl.add(new JButton(copyStartRightAction), gc);
-        
-        gc.gridx = 0;
-        gc.gridy = 1;
-        copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction();
-        pnl.add(new JButton(copyBeforeCurrentRightAction), gc);
-
-        gc.gridx = 0;
-        gc.gridy = 2;       
-        copyAfterCurrentRightAction = new CopyAfterCurrentRightAction();
-        pnl.add(new JButton(copyAfterCurrentRightAction), gc);
-        
-        gc.gridx = 0;
-        gc.gridy = 3;
-        copyEndRightAction = new CopyEndRightAction();
-        pnl.add(new JButton(copyEndRightAction), gc);
-        
-        return pnl;
-    }
-    
-    protected JPanel buildMergedListControlButtons() {
-        JPanel pnl = new JPanel();
-        pnl.setLayout(new GridBagLayout());
-        GridBagConstraints gc = new GridBagConstraints();
-
-        gc.gridx = 0;
-        gc.gridy = 0;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.HORIZONTAL;
-        gc.anchor = GridBagConstraints.CENTER;  
-        gc.weightx = 0.3;
-        gc.weighty = 0.0;
-        moveUpMergedAction = new MoveUpMergedAction();
-        pnl.add(new JButton(moveUpMergedAction), gc);
-
-        gc.gridx = 1;
-        gc.gridy = 0;
-        moveDownMergedAction = new MoveDownMergedAction();
-        pnl.add(new JButton(moveDownMergedAction), gc);
-
-        gc.gridx = 2;
-        gc.gridy = 0;
-        removeMergedAction = new RemoveMergedAction();
-        pnl.add(new JButton(removeMergedAction), gc);
-
-        gc.gridx = 0;
-        gc.gridy = 1;
-        gc.gridwidth = 3;
-        gc.weightx = 1.0;
-        freezeAction = new FreezeAction();
-        JToggleButton btn = new JToggleButton(freezeAction);
-        btn.setName("button.freeze");
-        btn.addItemListener(freezeAction);
-        pnl.add(btn, gc);
-        
-        return pnl;
-    }
-        
-    
-    protected void build() {
-        
-        setLayout(new GridBagLayout());
-        GridBagConstraints gc = new GridBagConstraints();
-        
-        gc.gridx = 0;
-        gc.gridy = 0;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;  
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        JLabel lbl = new JLabel(tr("Nodes in my version (local dataset)"));
-        add(lbl, gc);
-
-        gc.gridx = 2;
-        gc.gridy = 0;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        lbl = new JLabel(tr("Merged version"));
-        add(lbl, gc);
-
-        gc.gridx = 4;
-        gc.gridy = 0;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        lbl = new JLabel(tr("Nodes in their version (server dataset)"));
-        add(lbl, gc);
-        
-
-        gc.gridx = 0;
-        gc.gridy = 1;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.FIRST_LINE_START;
-        gc.weightx = 0.3;
-        gc.weighty = 1.0;
-        add(buildMyNodesTable(), gc);
-        
-        gc.gridx = 1;
-        gc.gridy = 1;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        add(buildLeftButtonPanel(), gc);
-        
-        gc.gridx = 2;
-        gc.gridy = 1;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.FIRST_LINE_START;
-        gc.weightx = 0.3;
-        gc.weighty = 0.0;
-        add(buildMergedNodesTable(), gc);
-        
-        gc.gridx = 3;
-        gc.gridy = 1;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
-        add(buildRightButtonPanel(), gc);
-        
-        gc.gridx = 4;
-        gc.gridy = 1;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.FIRST_LINE_START;
-        gc.weightx = 0.3;
-        gc.weighty = 0.0;
-        add(buildTheirNodesTable(), gc);
-        
-        gc.gridx = 2;
-        gc.gridy = 2;
-        gc.gridwidth = 1;
-        gc.gridheight = 1;
-        gc.fill = GridBagConstraints.BOTH;
-        gc.anchor = GridBagConstraints.CENTER;
-        gc.weightx = 0.3;
-        gc.weighty = 0.0;
-        add(buildMergedListControlButtons(), gc);
-                
-        wireActionsToSelectionModels();
-    }
-        
-    public NodeListMerger() {
-        model = new NodeListMergeModel();        
-        build();
-        model.addPropertyChangeListener(this);
-    }
-    
-    public void populate(Way my, Way their) {
-        model.populate(my, their);
-    }
-    
-    /**
-     * Action for copying selected nodes in the list of my nodes to the list of merged
-     * nodes. Inserts the nodes at the beginning of the list of merged nodes. 
-     *
-     */  
-    abstract class AbstractNodeManipulationAction extends AbstractAction {
-
-        /**
-         * load an icon given by iconName 
-         * 
-         * @param iconName  the name of the icon (without path, i.e. <tt>copystartleft.png</tt>
-         * @return the icon; null, if the icon was not found 
-         */
-        protected ImageIcon getIcon(String iconName) {
-            String fullIconName  = "/images/dialogs/conflict/" + iconName;
-            URL imageURL   = this.getClass().getResource(fullIconName);            
-            if (imageURL == null) {
-                System.out.println(tr("WARNING: failed to load resource {0}", fullIconName));
-                return null;
-            }
-            return new ImageIcon(imageURL);
-        }
+    @Override
+    protected JScrollPane buildMyElementsTable() {
+        myEntriesTable  = new JTable(
+                model.getMyTableModel(),
+                new NodeListColumnModel(
+                        new NodeListTableCellRenderer()
+                ),
+                model.getMySelectionModel()
+        );
+        myEntriesTable.setName("table.mynodes");
+        return embeddInScrollPane(myEntriesTable);
     }
 
-    /**
-     * Action for copying selected nodes in the list of my nodes to the list of merged
-     * nodes. Inserts the nodes at the beginning of the list of merged nodes. 
-     *
-     */ 
-    class CopyStartLeftAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyStartLeftAction() {            
-            ImageIcon icon = getIcon("copystartleft.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, tr("> top"));
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes to the start of the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = myNodes.getSelectedRows();
-            model.copyMyNodesToTop(rows);            
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(!myNodes.getSelectionModel().isSelectionEmpty());            
-        }
-    }
-    
-    /**
-     * Action for copying selected nodes in the list of my nodes to the list of merged
-     * nodes. Inserts the nodes at the end of the list of merged nodes. 
-     *
-     */ 
-    class CopyEndLeftAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyEndLeftAction() {            
-            ImageIcon icon = getIcon("copyendleft.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, tr("> bottom"));
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes to the end of the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = myNodes.getSelectedRows();
-            model.copyMyNodesToEnd(rows);  
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(!myNodes.getSelectionModel().isSelectionEmpty());            
-        }
-    }
-    
-    /**
-     * Action for copying selected nodes in the list of my nodes to the list of merged
-     * nodes. Inserts the nodes before the first selected row in the list of merged nodes. 
-     *
-     */
-    class CopyBeforeCurrentLeftAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyBeforeCurrentLeftAction() {            
-            ImageIcon icon = getIcon("copybeforecurrentleft.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, "> before");
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes before the first selected node in the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] myRows = myNodes.getSelectedRows();
-            int [] mergedRows = mergedNodes.getSelectedRows();
-            if (mergedRows == null || mergedRows.length == 0) {
-                return;
-            }
-            int current = mergedRows[0];            
-            model.copyMyNodesBeforeCurrent(myRows, current);            
-        }
-
-        public void valueChanged(ListSelectionEvent e) {        
-            setEnabled(
-                    !myNodes.getSelectionModel().isSelectionEmpty()
-                  && ! mergedNodes.getSelectionModel().isSelectionEmpty()
-            );            
-        }
-    }
-    
-    /**
-     * Action for copying selected nodes in the list of my nodes to the list of merged
-     * nodes. Inserts the nodes after the first selected row in the list of merged nodes. 
-     *
-     */    
-    class CopyAfterCurrentLeftAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyAfterCurrentLeftAction() {            
-            ImageIcon icon = getIcon("copyaftercurrentleft.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, "> after");
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes after the first selected node in the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] myRows = myNodes.getSelectedRows();
-            int [] mergedRows = mergedNodes.getSelectedRows();
-            if (mergedRows == null || mergedRows.length == 0) {
-                return;
-            }
-            int current = mergedRows[0];            
-            model.copyMyNodesAfterCurrent(myRows, current);                        
-        }
-
-        public void valueChanged(ListSelectionEvent e) {        
-            setEnabled(
-                    !myNodes.getSelectionModel().isSelectionEmpty()
-                  && ! mergedNodes.getSelectionModel().isSelectionEmpty()
-            );            
-        }
-    }
-    
-    
-    class CopyStartRightAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyStartRightAction() {            
-            ImageIcon icon = getIcon("copystartright.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, "< top");
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected nodes to the start of the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = theirNodes.getSelectedRows();
-            model.copyTheirNodesToTop(rows);                        
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(!theirNodes.getSelectionModel().isSelectionEmpty());            
-        }
-    }
-    
-    
-    class CopyEndRightAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyEndRightAction() {            
-            ImageIcon icon = getIcon("copyendright.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, "< bottom");
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected nodes to the end of the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = theirNodes.getSelectedRows();
-            model.copyTheirNodesToEnd(rows);  
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            setEnabled(!theirNodes.getSelectionModel().isSelectionEmpty());            
-        }
-    }
-    
-    class CopyBeforeCurrentRightAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyBeforeCurrentRightAction() {            
-            ImageIcon icon = getIcon("copybeforecurrentright.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, "< before");
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected nodes before the first selected node in the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] myRows = theirNodes.getSelectedRows();
-            int [] mergedRows = mergedNodes.getSelectedRows();
-            if (mergedRows == null || mergedRows.length == 0) {
-                return;
-            }
-            int current = mergedRows[0];            
-            model.copyTheirNodesBeforeCurrent(myRows, current);            
-        }
-
-        public void valueChanged(ListSelectionEvent e) {        
-            setEnabled(
-                    !theirNodes.getSelectionModel().isSelectionEmpty()
-                  && ! mergedNodes.getSelectionModel().isSelectionEmpty()
-            );            
-        }
-    }
-    
-    
-    class CopyAfterCurrentRightAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public CopyAfterCurrentRightAction() {            
-            ImageIcon icon = getIcon("copyaftercurrentright.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, "< after");
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected nodes after the first selected node in the merged node list"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] myRows = theirNodes.getSelectedRows();
-            int [] mergedRows = mergedNodes.getSelectedRows();
-            if (mergedRows == null || mergedRows.length == 0) {
-                return;
-            }
-            int current = mergedRows[0];            
-            model.copyTheirNodesAfterCurrent(myRows, current);                        
-        }
-
-        public void valueChanged(ListSelectionEvent e) {        
-            setEnabled(
-                    !theirNodes.getSelectionModel().isSelectionEmpty()
-                  && ! mergedNodes.getSelectionModel().isSelectionEmpty()
-            );            
-        }
-    }
-    
-    
-    class MoveUpMergedAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public MoveUpMergedAction() {            
-            ImageIcon icon = getIcon("moveup.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, tr("Up"));
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected nodes by one position"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = mergedNodes.getSelectedRows();
-            model.moveUpMergedNodes(rows);            
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            int [] rows = mergedNodes.getSelectedRows();
-            setEnabled(
-                    rows != null
-                  && rows.length > 0
-                  && rows[0] != 0
-            );            
-        }
-    }
-    
-    class MoveDownMergedAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public MoveDownMergedAction() {            
-            ImageIcon icon = getIcon("movedown.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, tr("Down"));
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected nodes by one position"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = mergedNodes.getSelectedRows();
-            model.moveDownMergedNodes(rows);                        
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            int [] rows = mergedNodes.getSelectedRows();
-            setEnabled(
-                    rows != null
-                  && rows.length > 0
-                  && rows[rows.length -1] != mergedNodes.getRowCount() -1
-            );            
-        }
-    }
-    
-    class RemoveMergedAction extends AbstractNodeManipulationAction implements ListSelectionListener {
-
-        public RemoveMergedAction() {            
-            ImageIcon icon = getIcon("remove.png");
-            putValue(Action.SMALL_ICON, icon);
-            if (icon == null) {
-                putValue(Action.NAME, tr("Remove"));
-            }
-            putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected nodes from the list of merged nodes"));
-            setEnabled(false);
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = mergedNodes.getSelectedRows();
-            model.removeMergedNodes(rows);                        
-        }
-
-        public void valueChanged(ListSelectionEvent e) {
-            int [] rows = mergedNodes.getSelectedRows();
-            setEnabled(
-                    rows != null
-                  && rows.length > 0
-            );            
-        }
-    }
-    
-    class FreezeAction extends AbstractNodeManipulationAction implements ItemListener  {
-
-        public FreezeAction() {            
-            // FIXME 
-//            ImageIcon icon = getIcon("remove.png");
-//            putValue(Action.SMALL_ICON, icon);
-//            if (icon == null) {
-//                putValue(Action.NAME, tr("Remove"));
-//            }
-            putValue(Action.NAME, tr("Freeze"));
-            putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged nodes."));
-//            putValue(Action.SELECTED_KEY, false);
-            setEnabled(true);
-            
-        }
-
-        public void actionPerformed(ActionEvent arg0) {
-            int [] rows = mergedNodes.getSelectedRows();
-            model.removeMergedNodes(rows);                        
-        }
-        
-        public void itemStateChanged(ItemEvent e) {
-            int state = e.getStateChange();
-            if (state == ItemEvent.SELECTED) {
-                model.setFrozen(true);
-                putValue(Action.NAME, tr("Unfreeze"));
-                putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged nodes and start merging"));
-            } else if (state == ItemEvent.DESELECTED) {
-                model.setFrozen(false);
-                putValue(Action.NAME, tr("Freeze"));
-                putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged nodes"));
-            }            
-        }  
+    @Override
+    protected JScrollPane buildMergedElementsTable() {
+        mergedEntriesTable  = new JTable(
+                model.getMergedTableModel(),
+                new NodeListColumnModel(
+                        new NodeListTableCellRenderer()
+                ),
+                model.getMergedSelectionModel()
+        );
+        mergedEntriesTable.setName("table.mergednodes");
+        return embeddInScrollPane(mergedEntriesTable);
     }
 
-    protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) {
-        myNodes.getSelectionModel().clearSelection();
-        myNodes.setEnabled(!newValue);        
-        theirNodes.getSelectionModel().clearSelection();
-        theirNodes.setEnabled(!newValue);
-        mergedNodes.getSelectionModel().clearSelection();
-        mergedNodes.setEnabled(!newValue);
-//        freezeAction.putValue(Action.SELECTED_KEY, newValue);
+    @Override
+    protected JScrollPane buildTheirElementsTable() {
+        theirEntriesTable  = new JTable(
+                model.getTheirTableModel(),
+                new NodeListColumnModel(
+                        new NodeListTableCellRenderer()
+                ),
+                model.getTheirSelectionModel()
+        );
+        theirEntriesTable.setName("table.theirnodes");
+        return embeddInScrollPane(theirEntriesTable);
     }
-    
-    public void propertyChange(PropertyChangeEvent evt) {
-        if (evt.getPropertyName().equals(NodeListMergeModel.PROP_FROZEN)) {
-            handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue());
-        }
-        
-    }
-    
-    public NodeListMergeModel getModel() {
-        return model;
+
+
+    public void populate(Way my, Way their) {
+        ((NodeListMergeModel)model).populate(my, their);
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListTableCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListTableCellRenderer.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/nodes/NodeListTableCellRenderer.java	(revision 1631)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.gui.conflict.nodes;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.Color;
@@ -13,5 +15,4 @@
 
 import org.openstreetmap.josm.data.osm.Node;
-import static org.openstreetmap.josm.tools.I18n.tr;
 
 /**
@@ -25,7 +26,7 @@
 
     /**
-     * Load the image icon for an OSM primitive of type node 
+     * Load the image icon for an OSM primitive of type node
      * 
-     * @return the icon; null, if not found 
+     * @return the icon; null, if not found
      */
     protected ImageIcon loadIcon() {
@@ -37,7 +38,7 @@
         return new ImageIcon(url);
     }
-    
+
     /**
-     * constructor 
+     * constructor
      */
     public NodeListTableCellRenderer() {
@@ -45,36 +46,36 @@
         setOpaque(true);
     }
-    
+
     /**
      * creates the display name for a node. The name is derived from the nodes id,
      * its name (i.e. the value of the tag with key name) and its coordinates.
-     *  
-     * @param node  the node 
-     * @return the display name 
+     * 
+     * @param node  the node
+     * @return the display name
      */
     protected String getDisplayName(Node node) {
-       StringBuilder sb = new StringBuilder();
-       if (node.get("name") != null) {
-           sb.append(node.get("name"));
-           sb.append("/");
-           sb.append(node.id);
-       } else {
-           sb.append(node.id);
-       }
-       sb.append(" (");
-        
-       if (node.coor != null) {
-           sb.append(COORD_FORMATTER.format(node.coor.lat()));
-           sb.append(",");
-           sb.append(COORD_FORMATTER.format(node.coor.lon()));
-       } else {
-           sb.append("?,?");
-       }
-       sb.append(")");
-       return sb.toString();
+        StringBuilder sb = new StringBuilder();
+        if (node.get("name") != null) {
+            sb.append(node.get("name"));
+            sb.append("/");
+            sb.append(node.id);
+        } else {
+            sb.append(node.id);
+        }
+        sb.append(" (");
+
+        if (node.coor != null) {
+            sb.append(COORD_FORMATTER.format(node.coor.lat()));
+            sb.append(",");
+            sb.append(COORD_FORMATTER.format(node.coor.lon()));
+        } else {
+            sb.append("?,?");
+        }
+        sb.append(")");
+        return sb.toString();
     }
-    
+
     /**
-     * reset the renderer 
+     * reset the renderer
      */
     protected void reset() {
@@ -82,8 +83,8 @@
         setForeground(Color.BLACK);
     }
-    
+
     /**
-     * render a node 
-     * @param node the node 
+     * render a node
+     * @param node the node
      * @param isSelected
      */
@@ -91,11 +92,11 @@
         if (isSelected) {
             setBackground(BGCOLOR_SELECTED);
-        } 
+        }
         setText(getDisplayName(node));
     }
-    
+
     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
             int row, int column) {
-        
+
         Node node = (Node)value;
         reset();
Index: trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberListColumnModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberListColumnModel.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberListColumnModel.java	(revision 1631)
@@ -0,0 +1,34 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.relation;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+public class RelationMemberListColumnModel extends DefaultTableColumnModel{
+
+    protected void createColumns() {
+        TableColumn col = null;
+        RelationMemberTableCellRenderer renderer = new RelationMemberTableCellRenderer();
+
+        // column 0 - Role
+        col = new TableColumn(0);
+        col.setHeaderValue(tr("Role"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        col.setCellEditor(new RelationMemberTableCellEditor());
+        addColumn(col);
+
+        // column 1 - Primitive
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("Primitive"));
+        col.setResizable(true);
+        col.setCellRenderer(renderer);
+        addColumn(col);
+    }
+
+    public RelationMemberListColumnModel() {
+        createColumns();
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberListMergeModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberListMergeModel.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberListMergeModel.java	(revision 1631)
@@ -0,0 +1,130 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.relation;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.logging.Logger;
+
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.command.RelationMemberConflictResolverCommand;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.conflict.ListMergeModel;
+/**
+ * The model for merging two lists of relation members
+ * 
+ *
+ */
+public class RelationMemberListMergeModel extends ListMergeModel<RelationMember>{
+
+    private static final Logger logger = Logger.getLogger(RelationMemberListMergeModel.class.getName());
+
+    @Override
+    public boolean isEqualEntry(RelationMember e1, RelationMember e2) {
+        return
+        (    (e1.role == null && e2.role == null)
+                || (e1.role != null && e1.role.equals(e2.role))
+        )
+        && e1.member.id == e2.member.id;
+    }
+
+    @Override
+    protected void buildMergedEntriesTableModel() {
+        // the table model for merged entries is different because it supports
+        // editing cells in the first column
+        //
+        mergedEntriesTableModel = this.new ListTableModel<RelationMember>(mergedEntries) {
+            @Override
+            public boolean isCellEditable(int row, int column) {
+                switch(column) {
+                case 0: return true;
+                default: return false;
+                }
+            }
+        };
+    }
+
+    @Override
+    protected void setValueAt(DefaultTableModel model, Object value, int row, int col) {
+        if (model == getMergedTableModel() && col == 0) {
+            RelationMember member = mergedEntries.get(row);
+            member.role = (String)value;
+            fireModelDataChanged();
+        }
+    }
+
+    /**
+     * populates the model with the relation members in relation my and their
+     * 
+     * @param my my relation. Must not be null.
+     * @param their their relation. Must not be null.
+     * 
+     * @throws IllegalArgumentException if my is null
+     * @throws IllegalArgumentException if their is null
+     */
+    public void populate(Relation my, Relation their) {
+        if (my == null)
+            throw new IllegalArgumentException(tr("parameter way must not be null"));
+        if (their == null)
+            throw new IllegalArgumentException(tr("parameter their must not be null"));
+
+        mergedEntries.clear();
+        myEntries.clear();
+        theirEntries.clear();
+
+        for (RelationMember n : my.members) {
+            myEntries.add(n);
+        }
+        for (RelationMember n : their.members) {
+            theirEntries.add(n);
+        }
+        if (myAndTheirEntriesEqual()) {
+            for (RelationMember m : myEntries) {
+                mergedEntries.add(cloneEntry(m));
+            }
+            setFrozen(true);
+        } else {
+            setFrozen(false);
+        }
+
+        fireModelDataChanged();
+    }
+
+    @Override
+    protected RelationMember cloneEntry(RelationMember entry) {
+        RelationMember member = new RelationMember();
+        member.role = entry.role;
+        if (entry.member instanceof Node) {
+            member.member = new Node(entry.member.id);
+        } else if (entry.member instanceof Way) {
+            member.member = new Way(entry.member.id);
+        } else if (entry.member instanceof Relation) {
+            member.member = new Relation(entry.member.id);
+        }
+        member.member.cloneFrom(entry.member);
+        return member;
+    }
+
+    /**
+     * Builds the command to resolve conflicts in the node list of a way
+     * 
+     * @param my  my relation. Must not be null.
+     * @param their  their relation. Must not be null
+     * @return the command
+     * @exception IllegalArgumentException thrown, if my is null
+     * @exception IllegalArgumentException thrown, if their is null
+     * @exception IllegalStateException thrown, if the merge is not yet frozen
+     */
+    public RelationMemberConflictResolverCommand buildResolveCommand(Relation my, Relation their) {
+        if (my == null)
+            throw new IllegalArgumentException(tr("parameter my most not be null"));
+        if (their == null)
+            throw new IllegalArgumentException(tr("parameter my most not be null"));
+        if (! isFrozen())
+            throw new IllegalArgumentException(tr("merged nodes not frozen yet. Can't build resolution command"));
+        return new RelationMemberConflictResolverCommand(my, their, mergedEntries);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberMerger.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberMerger.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberMerger.java	(revision 1631)
@@ -0,0 +1,62 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.relation;
+
+import java.util.logging.Logger;
+
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.gui.conflict.ListMerger;
+
+/**
+ * A UI component for resolving conflicts in the member lists of two {@see Relation}
+ */
+public class RelationMemberMerger extends ListMerger<RelationMember> {
+    private static final Logger logger = Logger.getLogger(RelationMemberMerger.class.getName());
+
+    @Override
+    protected JScrollPane buildMyElementsTable() {
+        myEntriesTable  = new JTable(
+                model.getMyTableModel(),
+                new RelationMemberListColumnModel(),
+                model.getMySelectionModel()
+        );
+        myEntriesTable.setName("table.mynodes");
+        return embeddInScrollPane(myEntriesTable);
+    }
+
+    @Override
+    protected JScrollPane buildMergedElementsTable() {
+        logger.info(model.getMergedTableModel().toString());
+        mergedEntriesTable  = new JTable(
+                model.getMergedTableModel(),
+                new RelationMemberListColumnModel(),
+                model.getMergedSelectionModel()
+        );
+        mergedEntriesTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+        mergedEntriesTable.setName("table.mergednodes");
+        return embeddInScrollPane(mergedEntriesTable);
+    }
+
+    @Override
+    protected JScrollPane buildTheirElementsTable() {
+        theirEntriesTable  = new JTable(
+                model.getTheirTableModel(),
+                new RelationMemberListColumnModel(),
+                model.getTheirSelectionModel()
+        );
+        theirEntriesTable.setName("table.theirnodes");
+        return embeddInScrollPane(theirEntriesTable);
+    }
+
+    public void populate(Relation my, Relation their) {
+        RelationMemberListMergeModel model = (RelationMemberListMergeModel)getModel();
+        model.populate(my,their);
+    }
+
+    public RelationMemberMerger() {
+        super(new RelationMemberListMergeModel());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberTableCellEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberTableCellEditor.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberTableCellEditor.java	(revision 1631)
@@ -0,0 +1,49 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.relation;
+
+import java.awt.Component;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.table.TableCellEditor;
+
+import org.openstreetmap.josm.data.osm.RelationMember;
+
+/**
+ * {@see TableCellEditor} for the the role column in a table for {@see RelationMember}s.
+ * 
+ */
+public class RelationMemberTableCellEditor extends AbstractCellEditor implements TableCellEditor{
+
+
+    private final JTextField editor;
+
+    public RelationMemberTableCellEditor() {
+        editor = new JTextField();
+        editor.addFocusListener(
+                new FocusAdapter() {
+                    @Override
+                    public void focusGained(FocusEvent arg0) {
+                        editor.selectAll();
+                    }
+                }
+        );
+    }
+
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+        RelationMember member = (RelationMember)value;
+
+        editor.setText(member.role == null ? "" : member.role);
+        editor.selectAll();
+        return editor;
+    }
+
+
+    public Object getCellEditorValue() {
+        return editor.getText();
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberTableCellRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberTableCellRenderer.java	(revision 1631)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/relation/RelationMemberTableCellRenderer.java	(revision 1631)
@@ -0,0 +1,150 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.relation;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.text.DecimalFormat;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This is the {@see TableCellRenderer} used in the tables of {@see RelationMemberMerger}.
+ * 
+ *
+ */
+public  class RelationMemberTableCellRenderer extends JLabel implements TableCellRenderer {
+    private static DecimalFormat COORD_FORMATTER = new DecimalFormat("###0.0000");
+    public final static Color BGCOLOR_SELECTED = new Color(143,170,255);
+
+    private ImageIcon nodeIcon;
+    private ImageIcon wayIcon;
+    private ImageIcon relationIcon;
+
+    /**
+     * Load the image icon for an OSM primitive of type node
+     * 
+     * @return the icon; null, if not found
+     */
+    protected void loadIcons() {
+        nodeIcon = ImageProvider.get("data", "node");
+        wayIcon = ImageProvider.get("data", "way");
+        relationIcon = ImageProvider.get("data", "relation");
+    }
+
+    /**
+     * constructor
+     */
+    public RelationMemberTableCellRenderer() {
+        setIcon(null);
+        setOpaque(true);
+        loadIcons();
+    }
+
+    /**
+     * creates the display name for a node. The name is derived from the nodes id,
+     * its name (i.e. the value of the tag with key name) and its coordinates.
+     * 
+     * @param node  the node
+     * @return the display name
+     */
+    protected String getDisplayName(RelationMember member) {
+        StringBuilder sb = new StringBuilder();
+        OsmPrimitive primitive = member.member;
+        if (primitive instanceof Node) {
+            sb.append(tr("Node"));
+        } else if (primitive instanceof Way) {
+            sb.append(tr("Way"));
+        } else if (primitive instanceof Relation) {
+            sb.append(tr("Relation"));
+        }
+        sb.append(" ");
+        if (primitive.get("name") != null) {
+            sb.append(primitive.get("name"));
+            sb.append("/");
+            sb.append(primitive.id);
+        } else {
+            sb.append(primitive.id);
+        }
+
+        if (primitive instanceof Node) {
+            Node n = (Node)primitive;
+            sb.append(" (");
+            if (n.coor != null) {
+                sb.append(COORD_FORMATTER.format(n.coor.lat()));
+                sb.append(",");
+                sb.append(COORD_FORMATTER.format(n.coor.lon()));
+            } else {
+                sb.append("?,?");
+            }
+            sb.append(")");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * reset the renderer
+     */
+    protected void reset() {
+        setBackground(Color.WHITE);
+        setForeground(Color.BLACK);
+    }
+
+
+    protected void setBackground(boolean isSelected) {
+        Color bgc = isSelected ?  BGCOLOR_SELECTED : Color.WHITE;
+        setBackground(bgc);
+    }
+
+    protected void renderRole(RelationMember member) {
+        setText(member.role == null ? "" : member.role);
+        setIcon(null);
+    }
+
+    protected void renderPrimitive(RelationMember member) {
+        String displayName = getDisplayName(member);
+        setText(displayName);
+        setToolTipText(displayName);
+        if (member.member instanceof Node) {
+            setIcon(nodeIcon);
+        } else if (member.member instanceof Way) {
+            setIcon(wayIcon);
+        } else if (member.member instanceof Relation) {
+            setIcon(relationIcon);
+        } else {
+            // should not happen
+            setIcon(null);
+        }
+    }
+
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+
+        RelationMember member = (RelationMember)value;
+        reset();
+        setBackground(isSelected);
+        switch(column) {
+        case 0:
+            renderRole(member);
+            break;
+        case 1:
+            renderPrimitive(member);
+            break;
+        default:
+            // should not happen
+        }
+        return this;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagMerger.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagMerger.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagMerger.java	(revision 1631)
@@ -7,4 +7,5 @@
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
+import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.AdjustmentEvent;
@@ -29,7 +30,7 @@
 
     private JTable mineTable;
-    private JTable undecidedTable;
+    private JTable mergedTable;
     private JTable theirTable;
-    private TagMergeModel model; 
+    private final TagMergeModel model;
     private JButton btnKeepMine;
     private JButton btnKeepTheir;
@@ -40,16 +41,17 @@
         pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
         pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
-        
+
         adjustmentSynchronizer.synchronizeAdjustment(pane.getVerticalScrollBar());
         return pane;
     }
-    
+
     protected JScrollPane buildMineTagTable() {
         mineTable  = new JTable(
-           model,
-           new TagMergeColumnModel(
-              new MineTableCellRenderer()
-           )
+                model,
+                new TagMergeColumnModel(
+                        new MineTableCellRenderer()
+                )
         );
+        mineTable.setName("table.my");
         return embeddInScrollPane(mineTable);
     }
@@ -57,28 +59,30 @@
     protected JScrollPane buildTheirTable() {
         theirTable  = new JTable(
-            model,
-            new TagMergeColumnModel(
-               new TheirTableCellRenderer()
-            )
-         );
-         return embeddInScrollPane(theirTable);
-    }
-    
+                model,
+                new TagMergeColumnModel(
+                        new TheirTableCellRenderer()
+                )
+        );
+        theirTable.setName("table.their");
+        return embeddInScrollPane(theirTable);
+    }
+
     protected JScrollPane buildUndecidedTable() {
-        undecidedTable  = new JTable(
-            model,
-            new TagMergeColumnModel(
-               new UndecidedTableCellRenderer()
-            )
-         );
-         return embeddInScrollPane(undecidedTable);
-    }
-    
+        mergedTable  = new JTable(
+                model,
+                new TagMergeColumnModel(
+                        new UndecidedTableCellRenderer()
+                )
+        );
+        mergedTable.setName("table.merged");
+        return embeddInScrollPane(mergedTable);
+    }
+
     protected void build() {
         GridBagConstraints gc = new GridBagConstraints();
         setLayout(new GridBagLayout());
-        
+
         adjustmentSynchronizer = new AdjustmentSynchronizer();
-        
+
         gc.gridx = 0;
         gc.gridy = 0;
@@ -86,7 +90,8 @@
         gc.gridheight = 1;
         gc.fill = GridBagConstraints.NONE;
-        gc.anchor = GridBagConstraints.CENTER;  
-        gc.weightx = 0.0;
-        gc.weighty = 0.0;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(10,0,10,0);
         JLabel lbl = new JLabel(tr("My version (local dataset)"));
         add(lbl, gc);
@@ -111,4 +116,5 @@
         gc.weightx = 0.0;
         gc.weighty = 0.0;
+        gc.insets = new Insets(0,0,0,0);
         lbl = new JLabel(tr("Their version (server dataset)"));
         add(lbl, gc);
@@ -123,5 +129,5 @@
         gc.weighty = 1.0;
         add(buildMineTagTable(), gc);
-        
+
         gc.gridx = 1;
         gc.gridy = 1;
@@ -135,6 +141,7 @@
         mineTable.getSelectionModel().addListSelectionListener(keepMineAction);
         btnKeepMine = new JButton(keepMineAction);
+        btnKeepMine.setName("button.keepmine");
         add(btnKeepMine, gc);
-        
+
         gc.gridx = 2;
         gc.gridy = 1;
@@ -146,5 +153,5 @@
         gc.weighty = 1.0;
         add(buildUndecidedTable(), gc);
-        
+
         gc.gridx = 3;
         gc.gridy = 1;
@@ -157,6 +164,7 @@
         KeepTheirAction keepTheirAction = new KeepTheirAction();
         btnKeepTheir = new JButton(keepTheirAction);
+        btnKeepTheir.setName("button.keeptheir");
         add(btnKeepTheir, gc);
-        
+
         gc.gridx = 4;
         gc.gridy = 1;
@@ -169,11 +177,11 @@
         add(buildTheirTable(), gc);
         theirTable.getSelectionModel().addListSelectionListener(keepTheirAction);
-        
-        
+
+
         DoubleClickAdapter dblClickAdapter = new DoubleClickAdapter();
         mineTable.addMouseListener(dblClickAdapter);
         theirTable.addMouseListener(dblClickAdapter);
-        
-        
+
+
         gc.gridx = 2;
         gc.gridy = 2;
@@ -185,33 +193,33 @@
         gc.weighty = 0.0;
         UndecideAction undecidedAction = new UndecideAction();
-        undecidedTable.getSelectionModel().addListSelectionListener(undecidedAction);
+        mergedTable.getSelectionModel().addListSelectionListener(undecidedAction);
         JButton btnUndecide = new JButton(undecidedAction);
+        btnUndecide.setName("button.undecide");
         add(btnUndecide, gc);
-        
-    }
-    
+
+    }
+
     public TagMerger() {
         model = new TagMergeModel();
         build();
     }
-    
-    
+
     public TagMergeModel getModel() {
         return model;
     }
-    
+
     protected ImageIcon loadIcon(String name) {
-       String path = "/images/dialogs/conflict/" + name;
-       URL url = this.getClass().getResource(path);
-       if (url == null) {
-           System.out.println(tr("WARNING: failed to load resource {0}", path));
-           return null;
-       }
-       return new ImageIcon(url);
-    }
-    
+        String path = "/images/dialogs/conflict/" + name;
+        URL url = this.getClass().getResource(path);
+        if (url == null) {
+            System.out.println(tr("WARNING: failed to load resource {0}", path));
+            return null;
+        }
+        return new ImageIcon(url);
+    }
+
     class KeepMineAction extends AbstractAction implements ListSelectionListener {
 
-       
+
         public KeepMineAction() {
             ImageIcon icon = loadIcon("tagkeepmine.png");
@@ -225,20 +233,19 @@
             setEnabled(false);
         }
-        
+
         public void actionPerformed(ActionEvent arg0) {
             int rows[] = mineTable.getSelectedRows();
-            if (rows == null || rows.length == 0) {
-                return; 
-            }
-            model.decide(rows, MergeDecisionType.KEEP_MINE);     
+            if (rows == null || rows.length == 0)
+                return;
+            model.decide(rows, MergeDecisionType.KEEP_MINE);
         }
 
         public void valueChanged(ListSelectionEvent e) {
-           setEnabled(mineTable.getSelectedRowCount() > 0);            
-        }
-    }
-    
+            setEnabled(mineTable.getSelectedRowCount() > 0);
+        }
+    }
+
     class KeepTheirAction extends AbstractAction implements ListSelectionListener {
-        
+
         public KeepTheirAction() {
             ImageIcon icon = loadIcon("tagkeeptheir.png");
@@ -252,32 +259,29 @@
             setEnabled(false);
         }
-        
+
         public void actionPerformed(ActionEvent arg0) {
             int rows[] = theirTable.getSelectedRows();
-            if (rows == null || rows.length == 0) {
-                return; 
-            }
-            model.decide(rows, MergeDecisionType.KEEP_THEIR);     
+            if (rows == null || rows.length == 0)
+                return;
+            model.decide(rows, MergeDecisionType.KEEP_THEIR);
         }
 
         public void valueChanged(ListSelectionEvent e) {
-           setEnabled(theirTable.getSelectedRowCount() > 0);            
-        }
-    }
-    
+            setEnabled(theirTable.getSelectedRowCount() > 0);
+        }
+    }
+
     class AdjustmentSynchronizer implements AdjustmentListener {
-        private ArrayList<Adjustable> synchronizedAdjustables;
-        
+        private final ArrayList<Adjustable> synchronizedAdjustables;
+
         public AdjustmentSynchronizer() {
             synchronizedAdjustables = new ArrayList<Adjustable>();
         }
-        
+
         public void synchronizeAdjustment(Adjustable adjustable) {
-            if (adjustable == null) {
-                return;
-            }
-            if (synchronizedAdjustables.contains(adjustable)) {
-                return;
-            }
+            if (adjustable == null)
+                return;
+            if (synchronizedAdjustables.contains(adjustable))
+                return;
             synchronizedAdjustables.add(adjustable);
             adjustable.addAdjustmentListener(this);
@@ -292,33 +296,32 @@
         }
     }
-    
+
     class DoubleClickAdapter extends MouseAdapter {
 
+        @Override
         public void mouseClicked(MouseEvent e) {
-            if (e.getClickCount() != 2) {
-                return;
-            }
+            if (e.getClickCount() != 2)
+                return;
             JTable table = null;
             MergeDecisionType mergeDecision;
-            
+
             if (e.getSource() == mineTable) {
-                table = (JTable)mineTable;
+                table = mineTable;
                 mergeDecision = MergeDecisionType.KEEP_MINE;
             } else if (e.getSource() == theirTable) {
-                table = (JTable)theirTable;
+                table = theirTable;
                 mergeDecision = MergeDecisionType.KEEP_THEIR;
-            } else if (e.getSource() == undecidedTable) {
-                table = (JTable)undecidedTable;
+            } else if (e.getSource() == mergedTable) {
+                table = mergedTable;
                 mergeDecision = MergeDecisionType.UNDECIDED;
-            } else {
+            } else
                 // double click in another component; shouldn't happen,
-                // but just in case 
-                return;
-            }
+                // but just in case
+                return;
             int row = table.rowAtPoint(e.getPoint());
             model.decide(row, mergeDecision);
         }
-    } 
-    
+    }
+
     class UndecideAction extends AbstractAction implements ListSelectionListener  {
 
@@ -334,16 +337,15 @@
             setEnabled(false);
         }
-        
+
         public void actionPerformed(ActionEvent arg0) {
-            int rows[] = undecidedTable.getSelectedRows();
-            if (rows == null || rows.length == 0) {
-                return; 
-            }
-            model.decide(rows, MergeDecisionType.UNDECIDED);     
+            int rows[] = mergedTable.getSelectedRows();
+            if (rows == null || rows.length == 0)
+                return;
+            model.decide(rows, MergeDecisionType.UNDECIDED);
         }
 
         public void valueChanged(ListSelectionEvent e) {
-           setEnabled(undecidedTable.getSelectedRowCount() > 0);            
-        }    
+            setEnabled(mergedTable.getSelectedRowCount() > 0);
+        }
     }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java	(revision 1630)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java	(revision 1631)
@@ -10,5 +10,4 @@
 import java.awt.Point;
 import java.awt.event.ActionEvent;
-import java.net.URL;
 import java.util.logging.Logger;
 
@@ -16,5 +15,4 @@
 import javax.swing.Action;
 import javax.swing.BorderFactory;
-import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JDialog;
@@ -25,11 +23,22 @@
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.gui.conflict.ConflictResolver;
+import org.openstreetmap.josm.tools.ImageProvider;
 
+/**
+ * This is an extended dialog for resolving conflict between {@see OsmPrimitive}.
+ * 
+ *
+ */
 public class ConflictResolutionDialog extends JDialog {
     private static final Logger logger = Logger.getLogger(ConflictResolutionDialog.class.getName());
     public final static Dimension DEFAULT_SIZE = new Dimension(600,400);
 
+    /** the conflict resolver component */
     private ConflictResolver resolver;
-        
+
+    /**
+     * restore position and size on screen from preference settings
+     * 
+     */
     protected void restorePositionAndDimension() {
         Point p = new Point();
@@ -39,5 +48,5 @@
             p.x = Math.max(0,p.x);
         } catch(Exception e) {
-            logger.warning("unexpected value for preference conflictresolutiondialog.x, assuming 0"); 
+            logger.warning("unexpected value for preference conflictresolutiondialog.x, assuming 0");
             p.x = 0;
         }
@@ -46,5 +55,5 @@
             p.y = Math.max(0,p.y);
         } catch(Exception e) {
-            logger.warning("unexpected value for preference conflictresolutiondialog.x, assuming 0"); 
+            logger.warning("unexpected value for preference conflictresolutiondialog.x, assuming 0");
             p.y = 0;
         }
@@ -53,5 +62,5 @@
             d.width = Math.max(0,d.width);
         } catch(Exception e) {
-            logger.warning("unexpected value for preference conflictresolutiondialog.width, assuming " + DEFAULT_SIZE.width); 
+            logger.warning("unexpected value for preference conflictresolutiondialog.width, assuming " + DEFAULT_SIZE.width);
             p.y = 0;
         }
@@ -60,22 +69,28 @@
             d.height = Math.max(0,d.height);
         } catch(Exception e) {
-            logger.warning("unexpected value for preference conflictresolutiondialog.height, assuming " +  + DEFAULT_SIZE.height); 
+            logger.warning("unexpected value for preference conflictresolutiondialog.height, assuming " +  + DEFAULT_SIZE.height);
             p.y = 0;
         }
-        
+
         setLocation(p);
         setSize(d);
     }
-    
+
+    /**
+     * remember position and size on screen in the preferences
+     * 
+     */
     protected void rememberPositionAndDimension() {
         Point p = getLocation();
         Main.pref.put("conflictresolutiondialog.x", Integer.toString(p.x));
         Main.pref.put("conflictresolutiondialog.y", Integer.toString(p.y));
-        
+
         Dimension d = getSize();
         Main.pref.put("conflictresolutiondialog.width", Integer.toString(d.width));
         Main.pref.put("conflictresolutiondialog.height", Integer.toString(d.height));
     }
-    
+
+
+    @Override
     public void setVisible(boolean isVisible) {
         if (isVisible){
@@ -86,79 +101,76 @@
         super.setVisible(isVisible);
     }
-    
+
+    /**
+     * builds the sub panel with the control buttons
+     * 
+     * @return the panel
+     */
     protected JPanel buildButtonRow() {
         JPanel pnl = new JPanel();
         pnl.setLayout(new FlowLayout(FlowLayout.RIGHT));
-        
+
         JButton btn = new JButton(new CancelAction());
         btn.setName("button.cancel");
         pnl.add(btn);
-        
+
         btn = new JButton(new ApplyResolutionAction());
         btn.setName("button.apply");
         pnl.add(btn);
-        
+
         pnl.setBorder(BorderFactory.createLoweredBevelBorder());
         return pnl;
     }
-    
+
+    /**
+     * builds the GUI
+     */
     protected void build() {
-       setTitle(tr("Resolve conflicts"));
-       getContentPane().setLayout(new BorderLayout());     
-       
-       resolver = new ConflictResolver();
-       getContentPane().add(resolver, BorderLayout.CENTER);       
-       getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);       
+        setTitle(tr("Resolve conflicts"));
+        getContentPane().setLayout(new BorderLayout());
+
+        resolver = new ConflictResolver();
+        resolver.setName("panel.conflictresolver");
+        getContentPane().add(resolver, BorderLayout.CENTER);
+        getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
     }
-    
-    
+
+
     public ConflictResolutionDialog(Component parent) {
         super(JOptionPane.getFrameForComponent(parent), true /* modal */);
         build();
     }
-    
+
     public ConflictResolver getConflictResolver() {
         return resolver;
     }
-    
-    protected ImageIcon getIcon(String iconPath) {
-        URL imageURL   = this.getClass().getResource(iconPath);            
-        if (imageURL == null) {
-            System.out.println(tr("WARNING: failed to load resource {0}", iconPath));
-            return null;
-        }
-        return new ImageIcon(imageURL);
-    }
 
-    
     class CancelAction extends AbstractAction {
-        
-        public CancelAction() {            
+        public CancelAction() {
             putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog"));
             putValue(Action.NAME, tr("Cancel"));
-            putValue(Action.SMALL_ICON, getIcon("/images/cancel.png"));
+            putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
             setEnabled(true);
         }
 
-        
+
         public void actionPerformed(ActionEvent arg0) {
             setVisible(false);
-        }        
-    }   
-    
-    class ApplyResolutionAction extends AbstractAction {        
+        }
+    }
+
+    class ApplyResolutionAction extends AbstractAction {
         public ApplyResolutionAction() {
             putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog"));
             putValue(Action.NAME, tr("Apply Resolution"));
-            putValue(Action.SMALL_ICON, getIcon("/images/dialogs/conflict.png"));
-            setEnabled(true);            
+            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs", "conflict"));
+            setEnabled(true);
         }
 
-        
         public void actionPerformed(ActionEvent arg0) {
             Command cmd = resolver.buildResolveCommand();
             Main.main.undoRedo.add(cmd);
             setVisible(false);
-        }        
+        }
     }
 }
Index: trunk/test/unit/org/openstreetmap/josm/gui/conflict/nodes/NodeListMergeModelTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/conflict/nodes/NodeListMergeModelTest.java	(revision 1630)
+++ trunk/test/unit/org/openstreetmap/josm/gui/conflict/nodes/NodeListMergeModelTest.java	(revision 1631)
@@ -2,4 +2,5 @@
 package org.openstreetmap.josm.gui.conflict.nodes;
 
+import static org.fest.reflect.core.Reflection.field;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -8,5 +9,4 @@
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
@@ -17,23 +17,19 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Way;
-import static org.fest.reflect.core.Reflection.field;
 
 public class NodeListMergeModelTest {
-    
-    protected List<Node> inspectNodeList(NodeListMergeModel model, String name) throws NoSuchFieldException, IllegalAccessException{
-        Field f = NodeListMergeModel.class.getDeclaredField(name);
-        f.setAccessible(true);
-        Object o = f.get(model);
-        return (List<Node>)o;
-    }
-    
+
+    protected List<Node> inspectNodeList(NodeListMergeModel model, String name) {
+        return field(name).ofType(ArrayList.class)
+        .in(model)
+        .get();
+    }
+
     protected DefaultListSelectionModel inspectListSelectionModel(NodeListMergeModel model, String name) throws NoSuchFieldException, IllegalAccessException {
-        Field f = NodeListMergeModel.class.getDeclaredField(name);
-        f.setAccessible(true);
-        Object o = f.get(model);
-        return (DefaultListSelectionModel)o;
-        
-    }
-    
+        return field(name).ofType(DefaultListSelectionModel.class)
+        .in(model)
+        .get();
+    }
+
     protected void ensureSelected(DefaultListSelectionModel model, Object... idx) {
         if (idx == null) return;
@@ -43,107 +39,107 @@
                 assertTrue("expected row " + j + " to be selected", model.isSelectedIndex(j));
                 break;
-            } 
+            }
             try {
-              int rows[] = (int[])idx[i];
-              if (rows == null || rows.length != 2) {
-                  fail("illegal selection range. Either null or not length 2: " + rows);
-              }
-              if (rows[0] > rows[1]) {
-                  fail("illegal selection range. lower bound > upper bound ");
-              }
-              for (int j = rows[0]; j <= rows[1]; j++) {
-                  assertTrue("expected row " + j + " to be selected", model.isSelectedIndex(j));
-              }
+                int rows[] = (int[])idx[i];
+                if (rows == null || rows.length != 2) {
+                    fail("illegal selection range. Either null or not length 2: " + rows);
+                }
+                if (rows[0] > rows[1]) {
+                    fail("illegal selection range. lower bound > upper bound ");
+                }
+                for (int j = rows[0]; j <= rows[1]; j++) {
+                    assertTrue("expected row " + j + " to be selected", model.isSelectedIndex(j));
+                }
             } catch(ClassCastException e) {
                 fail("illegal selection range:" + idx[i]);
-            }            
+            }
         }
     }
-    
+
     @Test
     public void test_copyMyNodesToTop_1() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        model.copyMyNodesToTop(new int[]{0});
-        
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        model.copyMyToTop(new int[]{0});
+
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+
         assertEquals(1, mergedNodes.size());
         assertEquals(2, mergedNodes.get(0).id);
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0);        
-
-    }
-    
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0);
+
+    }
+
+
     @Test
     public void test_copyMyNodesToTop_2() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
 
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(1));
 
-        model.copyMyNodesToTop(new int[]{0});
-        
-        mergedNodes = inspectNodeList(model, "mergedNodes");
+        model.copyMyToTop(new int[]{0});
+
+        mergedNodes = inspectNodeList(model, "mergedEntries");
         assertEquals(2, mergedNodes.size());
         assertEquals(2, mergedNodes.get(0).id);
         assertEquals(1, mergedNodes.get(1).id);
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0);        
-
-    }
-    
-    
-
-    
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0);
+
+    }
+
+
+
+
+
     @Test
     public void test_copyMyNodesToTop_3() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
 
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(1));
 
-        model.copyMyNodesToTop(new int[]{1}); // copy node 3
-        
-        mergedNodes = inspectNodeList(model, "mergedNodes");
+        model.copyMyToTop(new int[]{1}); // copy node 3
+
+        mergedNodes = inspectNodeList(model, "mergedEntries");
         assertEquals(2, mergedNodes.size());
         assertEquals(3, mergedNodes.get(0).id); // my node 3 at position 0
         assertEquals(1, mergedNodes.get(1).id); // already merged node 1 at position 1
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0);        
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0);
+    }
+
     @Test
     public void test_copyMyNodesToTop_4() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
 
-        
+
         Way myWay = new Way(1);
         myWay.nodes.add(new Node(2));
@@ -154,98 +150,98 @@
         model.populate(myWay, theirWay);
 
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(1));
 
-        model.copyMyNodesToTop(new int[]{1,2}); // copy node 3 and 4
-        
-        mergedNodes = inspectNodeList(model, "mergedNodes");
+        model.copyMyToTop(new int[]{1,2}); // copy node 3 and 4
+
+        mergedNodes = inspectNodeList(model, "mergedEntries");
         assertEquals(3, mergedNodes.size());
         assertEquals(3, mergedNodes.get(0).id); // my node 3 at position 0
         assertEquals(4, mergedNodes.get(1).id); // my node 4 at position 1
         assertEquals(1, mergedNodes.get(2).id); // already merged node 1 at position 2
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0,1); // first two rows selected        
-    }
-    
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0,1); // first two rows selected
+    }
+
+
     @Test
     public void test_copyMyNodesToEnd_1() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        model.copyMyNodesToEnd(new int[]{0});
-        
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        model.copyMyToEnd(new int[]{0});
+
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+
         assertEquals(1, mergedNodes.size());
         assertEquals(2, mergedNodes.get(0).id);
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0);        
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0);
+    }
+
     @Test
     public void test_copyMyNodesToEnd_2() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(1));
 
-        model.copyMyNodesToEnd(new int[]{0});
-        
-        mergedNodes = inspectNodeList(model, "mergedNodes");
+        model.copyMyToEnd(new int[]{0});
+
+        mergedNodes = inspectNodeList(model, "mergedEntries");
         assertEquals(2, mergedNodes.size());
         assertEquals(1, mergedNodes.get(0).id); // already merged node 1 at position 0
         assertEquals(2, mergedNodes.get(1).id); // copied node 2 at position 1
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 1);        
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 1);
+    }
+
     @Test
     public void test_copyMyNodesToEnd_3() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
 
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(1));
 
-        model.copyMyNodesToEnd(new int[]{1}); // copy node 3
-        
-        mergedNodes = inspectNodeList(model, "mergedNodes");
+        model.copyMyToEnd(new int[]{1}); // copy node 3
+
+        mergedNodes = inspectNodeList(model, "mergedEntries");
         assertEquals(2, mergedNodes.size());
         assertEquals(1, mergedNodes.get(0).id); // already merged node 1 at position 0
         assertEquals(3, mergedNodes.get(1).id); // my node 3 at position 1
-        
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 1);        
-    }
-    
+
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 1);
+    }
+
     @Test
     public void test_copyMyNodesToEnd_4() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
 
-        
+
         Way myWay = new Way(1);
         myWay.nodes.add(new Node(2));
@@ -256,10 +252,10 @@
         model.populate(myWay, theirWay);
 
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(1));
 
-        model.copyMyNodesToEnd(new int[]{1,2}); // copy node 3 and 4
-        
-        mergedNodes = inspectNodeList(model, "mergedNodes");
+        model.copyMyToEnd(new int[]{1,2}); // copy node 3 and 4
+
+        mergedNodes = inspectNodeList(model, "mergedEntries");
         assertEquals(3, mergedNodes.size());
         assertEquals(1, mergedNodes.get(0).id); // already merged node 1 at position 0
@@ -267,99 +263,99 @@
         assertEquals(4, mergedNodes.get(2).id); // my node 4 at position 2
 
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 1,2); // last two rows selected        
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 1,2); // last two rows selected
+    }
+
     /* ----------------------------------------------------------------------------- */
     /* copyMyNodesBeforeCurrent                                                      */
     /* ----------------------------------------------------------------------------- */
-    
+
     @Test
     public void test_copyMyNodesBeforeCurrent_1() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.copyMyNodesBeforeCurrent(new int[]{0}, 1);
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.copyMyBeforeCurrent(new int[]{0}, 1);
+
         assertEquals(4, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id); // already merged node 
+        assertEquals(10, mergedNodes.get(0).id); // already merged node
         assertEquals(1, mergedNodes.get(1).id);  // copied node 1 at position 1
-        assertEquals(11, mergedNodes.get(2).id); // already merged node 
-        assertEquals(12, mergedNodes.get(3).id); // already merged node 
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 1); // position 1 selected        
-    }
-    
-    
+        assertEquals(11, mergedNodes.get(2).id); // already merged node
+        assertEquals(12, mergedNodes.get(3).id); // already merged node
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 1); // position 1 selected
+    }
+
+
     @Test
     public void test_copyMyNodesBeforeCurrent_2() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.copyMyNodesBeforeCurrent(new int[]{0,1}, 0);
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.copyMyBeforeCurrent(new int[]{0,1}, 0);
+
         assertEquals(5, mergedNodes.size());
         assertEquals(1, mergedNodes.get(0).id);  // copied node 1 at position 0
         assertEquals(2, mergedNodes.get(1).id);  // copied node 2 at position 1
-        assertEquals(10, mergedNodes.get(2).id); // already merged node 
-        assertEquals(11, mergedNodes.get(3).id); // already merged node 
-        assertEquals(12, mergedNodes.get(4).id); // already merged node 
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0,1); // position 0 and 1 selected        
-    }
-    
+        assertEquals(10, mergedNodes.get(2).id); // already merged node
+        assertEquals(11, mergedNodes.get(3).id); // already merged node
+        assertEquals(12, mergedNodes.get(4).id); // already merged node
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0,1); // position 0 and 1 selected
+    }
+
     @Test
     public void test_copyMyNodesBeforeCurrent_3() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        try {                
-            model.copyMyNodesBeforeCurrent(new int[]{0,1}, -1);
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        try {
+            model.copyMyBeforeCurrent(new int[]{0,1}, -1);
             fail("expected IllegalArgumentException");
         } catch(IllegalArgumentException e) {
             // OK
         }
-                
-        try {                
-            model.copyMyNodesBeforeCurrent(new int[]{0,1}, 3);
+
+        try {
+            model.copyMyBeforeCurrent(new int[]{0,1}, 3);
             fail("expected IllegalArgumentException");
         } catch(IllegalArgumentException e) {
             // OK
-        }     
-    }
-    
+        }
+    }
+
     /* ----------------------------------------------------------------------------- */
     /* copyMyNodesAfterCurrent                                                       */
@@ -368,118 +364,118 @@
     public void test_copyMyNodesAfterCurrent_1() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.copyMyNodesAfterCurrent(new int[]{0}, 1);
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.copyMyAfterCurrent(new int[]{0}, 1);
+
         assertEquals(4, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id); // already merged node 
-        assertEquals(11, mergedNodes.get(1).id); // already merged node 
+        assertEquals(10, mergedNodes.get(0).id); // already merged node
+        assertEquals(11, mergedNodes.get(1).id); // already merged node
         assertEquals(1, mergedNodes.get(2).id);  // copied node 1 at position 2
-        assertEquals(12, mergedNodes.get(3).id); // already merged node 
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 2); // position 1 selected        
-    }
-    
-    
+        assertEquals(12, mergedNodes.get(3).id); // already merged node
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 2); // position 1 selected
+    }
+
+
     @Test
     public void test_copyMyNodesAfterCurrent_2() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.copyMyNodesAfterCurrent(new int[]{0,1}, 2);
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.copyMyAfterCurrent(new int[]{0,1}, 2);
+
         assertEquals(5, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id); // already merged node 
+        assertEquals(10, mergedNodes.get(0).id); // already merged node
         assertEquals(11, mergedNodes.get(1).id); // already merged node
         assertEquals(12, mergedNodes.get(2).id); // already merged node
         assertEquals(1, mergedNodes.get(3).id);  // copied node 1 at position 3
         assertEquals(2, mergedNodes.get(4).id);  // copied node 2 at position 4
-         
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 3,4); // position 3,4 selected        
-    }
-    
+
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 3,4); // position 3,4 selected
+    }
+
     @Test
     public void test_copyMyNodesAfterCurrent_3() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        myWay.nodes.add(new Node(3));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.copyMyNodesAfterCurrent(new int[]{0,2}, 0);
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        myWay.nodes.add(new Node(3));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.copyMyAfterCurrent(new int[]{0,2}, 0);
+
         assertEquals(5, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id); // already merged node 
+        assertEquals(10, mergedNodes.get(0).id); // already merged node
         assertEquals(1, mergedNodes.get(1).id);  // copied node 1 at position 1
         assertEquals(3, mergedNodes.get(2).id);  // copied node 3 at position 2
         assertEquals(11, mergedNodes.get(3).id); // already merged node
         assertEquals(12, mergedNodes.get(4).id); // already merged node
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 1,2); // position 1,2 selected        
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 1,2); // position 1,2 selected
+    }
+
     @Test
     public void test_copyMyNodesAfterCurrent_4() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        try {                
-            model.copyMyNodesAfterCurrent(new int[]{0,1}, -1);
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        try {
+            model.copyMyAfterCurrent(new int[]{0,1}, -1);
             fail("expected IllegalArgumentException");
         } catch(IllegalArgumentException e) {
             // OK
         }
-                
-        try {                
-            model.copyMyNodesAfterCurrent(new int[]{0,1}, 3);
+
+        try {
+            model.copyMyAfterCurrent(new int[]{0,1}, 3);
             fail("expected IllegalArgumentException");
         } catch(IllegalArgumentException e) {
             // OK
-        }     
-    }
-    
+        }
+    }
+
     /* ----------------------------------------------------------------------------- */
     /* moveUpMergedNodes                                                       */
@@ -488,38 +484,38 @@
     public void test_moveUpMergedNodes_1() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.moveUpMergedNodes(new int[]{1});
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.moveUpMerged(new int[]{1});
+
         assertEquals(3, mergedNodes.size());
-        assertEquals(11, mergedNodes.get(0).id);  
-        assertEquals(10, mergedNodes.get(1).id);  
-        assertEquals(12, mergedNodes.get(2).id);   
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0); // position 1 selecte0        
-    }
-    
+        assertEquals(11, mergedNodes.get(0).id);
+        assertEquals(10, mergedNodes.get(1).id);
+        assertEquals(12, mergedNodes.get(2).id);
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0); // position 1 selecte0
+    }
+
     @Test
     public void test_moveUpMergedNodes_2() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(10));
         mergedNodes.add(new Node(11));
@@ -527,29 +523,29 @@
         mergedNodes.add(new Node(13));
         mergedNodes.add(new Node(14));
-        
-        model.moveUpMergedNodes(new int[]{1,4});
-                
+
+        model.moveUpMerged(new int[]{1,4});
+
         assertEquals(5, mergedNodes.size());
-        assertEquals(11, mergedNodes.get(0).id);  
-        assertEquals(10, mergedNodes.get(1).id);  
-        assertEquals(12, mergedNodes.get(2).id);   
+        assertEquals(11, mergedNodes.get(0).id);
+        assertEquals(10, mergedNodes.get(1).id);
+        assertEquals(12, mergedNodes.get(2).id);
         assertEquals(14, mergedNodes.get(3).id);
         assertEquals(13, mergedNodes.get(4).id);
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0,3); // position 0 and 3 selecte0        
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0,3); // position 0 and 3 selecte0
+    }
+
     @Test
     public void test_moveUpMergedNodes_3() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(10));
         mergedNodes.add(new Node(11));
@@ -557,18 +553,18 @@
         mergedNodes.add(new Node(13));
         mergedNodes.add(new Node(14));
-        
-        model.moveUpMergedNodes(new int[]{1,2,3,4});
-                
+
+        model.moveUpMerged(new int[]{1,2,3,4});
+
         assertEquals(5, mergedNodes.size());
-        assertEquals(11, mergedNodes.get(0).id);  
-        assertEquals(12, mergedNodes.get(1).id);   
+        assertEquals(11, mergedNodes.get(0).id);
+        assertEquals(12, mergedNodes.get(1).id);
         assertEquals(13, mergedNodes.get(2).id);
         assertEquals(14, mergedNodes.get(3).id);
-        assertEquals(10, mergedNodes.get(4).id);  
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 0,1,2,3);         
-    }
-    
+        assertEquals(10, mergedNodes.get(4).id);
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 0,1,2,3);
+    }
+
     /* ----------------------------------------------------------------------------- */
     /* moveDownMergedNodes                                                       */
@@ -577,38 +573,38 @@
     public void test_moveDownMergedNodes_1() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
-        mergedNodes.add(new Node(10));
-        mergedNodes.add(new Node(11));
-        mergedNodes.add(new Node(12));
-        
-        model.moveDownMergedNodes(new int[]{1});
-                
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
+        mergedNodes.add(new Node(10));
+        mergedNodes.add(new Node(11));
+        mergedNodes.add(new Node(12));
+
+        model.moveDownMerged(new int[]{1});
+
         assertEquals(3, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id);  
-        assertEquals(12, mergedNodes.get(1).id);  
-        assertEquals(11, mergedNodes.get(2).id);   
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 2);        
-    }
-    
+        assertEquals(10, mergedNodes.get(0).id);
+        assertEquals(12, mergedNodes.get(1).id);
+        assertEquals(11, mergedNodes.get(2).id);
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 2);
+    }
+
     @Test
     public void test_moveDownMergedNodes_2() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(10));
         mergedNodes.add(new Node(11));
@@ -616,29 +612,29 @@
         mergedNodes.add(new Node(13));
         mergedNodes.add(new Node(14));
-        
-        model.moveDownMergedNodes(new int[]{1,3});
-                
+
+        model.moveDownMerged(new int[]{1,3});
+
         assertEquals(5, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id);  
-        assertEquals(12, mergedNodes.get(1).id);  
-        assertEquals(11, mergedNodes.get(2).id);   
+        assertEquals(10, mergedNodes.get(0).id);
+        assertEquals(12, mergedNodes.get(1).id);
+        assertEquals(11, mergedNodes.get(2).id);
         assertEquals(14, mergedNodes.get(3).id);
         assertEquals(13, mergedNodes.get(4).id);
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 2,4);         
-    }
-    
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 2,4);
+    }
+
     @Test
     public void test_moveDownMergedNodes_3() throws IllegalAccessException, NoSuchFieldException {
         NodeListMergeModel model = new NodeListMergeModel();
-        
-        Way myWay = new Way(1);
-        myWay.nodes.add(new Node(1));
-        myWay.nodes.add(new Node(2));
-        Way theirWay = new Way(1);
-
-        model.populate(myWay, theirWay);
-        List<Node> mergedNodes = inspectNodeList(model, "mergedNodes");
+
+        Way myWay = new Way(1);
+        myWay.nodes.add(new Node(1));
+        myWay.nodes.add(new Node(2));
+        Way theirWay = new Way(1);
+
+        model.populate(myWay, theirWay);
+        List<Node> mergedNodes = inspectNodeList(model, "mergedEntries");
         mergedNodes.add(new Node(10));
         mergedNodes.add(new Node(11));
@@ -646,18 +642,18 @@
         mergedNodes.add(new Node(13));
         mergedNodes.add(new Node(14));
-        
-        model.moveDownMergedNodes(new int[]{1,2,3});
-                
+
+        model.moveDownMerged(new int[]{1,2,3});
+
         assertEquals(5, mergedNodes.size());
-        assertEquals(10, mergedNodes.get(0).id);  
-        assertEquals(14, mergedNodes.get(1).id);   
+        assertEquals(10, mergedNodes.get(0).id);
+        assertEquals(14, mergedNodes.get(1).id);
         assertEquals(11, mergedNodes.get(2).id);
         assertEquals(12, mergedNodes.get(3).id);
-        assertEquals(13, mergedNodes.get(4).id);  
-        
-        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedNodesSelectionModel");
-        ensureSelected(mergedSelection, 2,3,4);         
-    }
-    
+        assertEquals(13, mergedNodes.get(4).id);
+
+        DefaultListSelectionModel mergedSelection = inspectListSelectionModel(model, "mergedEntriesSelectionModel");
+        ensureSelected(mergedSelection, 2,3,4);
+    }
+
     /* ----------------------------------------------------------------------------- */
     /* PropertyChangeListener                                                        */
@@ -666,43 +662,43 @@
     public void addPropertyChangeListener() {
         NodeListMergeModel model = new NodeListMergeModel();
-        
+
         PropertyChangeListener listener = new PropertyChangeListener() {
-            @Override
+
             public void propertyChange(PropertyChangeEvent evt) {
             }
         };
-        
+
         model.addPropertyChangeListener(listener);
-        
+
         ArrayList<PropertyChangeListener> listeners = field("listeners")
-          .ofType(ArrayList.class)
-          .in(model)
-          .get();
-        
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(1, listeners.size());
         assertEquals(listener, listeners.get(0));
     }
-    
+
     @Test
     public void removePropertyChangeListener() {
         NodeListMergeModel model = new NodeListMergeModel();
-        
+
         PropertyChangeListener listener = new PropertyChangeListener() {
-            @Override
+
             public void propertyChange(PropertyChangeEvent evt) {
             }
         };
-        
+
         model.addPropertyChangeListener(listener);
         model.removePropertyChangeListener(listener);
-        
+
         ArrayList<PropertyChangeListener> listeners = field("listeners")
-          .ofType(ArrayList.class)
-          .in(model)
-          .get();
-        
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(0, listeners.size());
     }
-    
+
     /* ----------------------------------------------------------------------------- */
     /* property frozen                                                               */
@@ -721,8 +717,8 @@
     public void setFrozenWithPropertyChangeNotification() {
         NodeListMergeModel model = new NodeListMergeModel();
-        
+
         class MyListener implements PropertyChangeListener {
             public ArrayList<PropertyChangeEvent> events = new ArrayList<PropertyChangeEvent>();
-            @Override
+
             public void propertyChange(PropertyChangeEvent evt) {
                 events.add(evt);
@@ -734,5 +730,5 @@
         model.setFrozen(!oldValue);
         assertEquals(!oldValue, model.isFrozen());
-        
+
         assertEquals(1, listener.events.size());
         assertEquals(oldValue, listener.events.get(0).getOldValue());
Index: trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/TagMergeModelTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/TagMergeModelTest.java	(revision 1630)
+++ trunk/test/unit/org/openstreetmap/josm/gui/conflict/tags/TagMergeModelTest.java	(revision 1631)
@@ -19,28 +19,28 @@
         TagMergeModel model = new TagMergeModel();
     }
-    
+
     @Test
     public void addPropertyChangeListener() {
         TagMergeModel model = new TagMergeModel();
         PropertyChangeListener listener = new PropertyChangeListener() {
-            @Override
+
             public void propertyChange(PropertyChangeEvent evt) {
             }
         };
         model.addPropertyChangeListener(listener);
-        
+
         ArrayList list = field("listeners").ofType(ArrayList.class)
-           .in(model)
-           .get();
-        
-        assertEquals(1, list.size());
-        assertEquals(listener, list.get(0));        
-    }
-    
+        .in(model)
+        .get();
+
+        assertEquals(1, list.size());
+        assertEquals(listener, list.get(0));
+    }
+
     @Test
     public void removePropertyChangeListener() {
         TagMergeModel model = new TagMergeModel();
         PropertyChangeListener listener = new PropertyChangeListener() {
-            @Override
+
             public void propertyChange(PropertyChangeEvent evt) {
             }
@@ -48,13 +48,13 @@
         model.addPropertyChangeListener(listener);
         model.removePropertyChangeListener(listener);
-        
+
         ArrayList list = field("listeners")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-     
-         assertEquals(0, list.size());        
-    }
-    
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
+        assertEquals(0, list.size());
+    }
+
     @Test
     public void populateNoConflichts() {
@@ -63,13 +63,13 @@
         TagMergeModel model = new TagMergeModel();
         model.populate(my, their);
-        
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(0, list.size());
     }
-    
+
     @Test
     public void populateNoConflicts1() {
@@ -80,13 +80,13 @@
         TagMergeModel model = new TagMergeModel();
         model.populate(my, their);
-        
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(0, list.size());
     }
-    
+
     @Test
     public void populateMissingKeyMine() {
@@ -96,10 +96,10 @@
         TagMergeModel model = new TagMergeModel();
         model.populate(my, their);
-        
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(1, list.size());
         TagMergeItem item = list.get(0);
@@ -109,18 +109,18 @@
         assertEquals("value", item.getTheirTagValue());
     }
-    
+
     @Test
     public void populateMissingKeyTheir() {
         Node my = new Node(1);
         my.put("key", "value");
-        Node their = new Node(1);        
-        TagMergeModel model = new TagMergeModel();
-        model.populate(my, their);
-        
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+        Node their = new Node(1);
+        TagMergeModel model = new TagMergeModel();
+        model.populate(my, their);
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(1, list.size());
         TagMergeItem item = list.get(0);
@@ -130,5 +130,5 @@
         assertEquals("value", item.getMyTagValue());
     }
-    
+
     @Test
     public void populateConflictingValues() {
@@ -139,10 +139,10 @@
         TagMergeModel model = new TagMergeModel();
         model.populate(my, their);
-        
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(1, list.size());
         TagMergeItem item = list.get(0);
@@ -152,5 +152,5 @@
         assertEquals("theirvalue", item.getTheirTagValue());
     }
-    
+
     @Test
     public void addItem() {
@@ -158,10 +158,10 @@
         TagMergeModel model = new TagMergeModel();
         model.addItem(item);
-        
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(1, list.size());
         item = list.get(0);
@@ -171,5 +171,5 @@
         assertEquals("theirvalue", item.getTheirTagValue());
     }
-    
+
     @Test
     public void decide() {
@@ -177,29 +177,29 @@
         TagMergeModel model = new TagMergeModel();
         model.addItem(item);
-             
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
-        model.decide(0, MergeDecisionType.KEEP_MINE);  
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
+        model.decide(0, MergeDecisionType.KEEP_MINE);
         assertEquals(1, list.size());
         item = list.get(0);
         assertEquals(MergeDecisionType.KEEP_MINE, item.getMergeDecision());
-        
-        model.decide(0, MergeDecisionType.KEEP_THEIR);  
+
+        model.decide(0, MergeDecisionType.KEEP_THEIR);
         assertEquals(1, list.size());
         item = list.get(0);
         assertEquals(MergeDecisionType.KEEP_THEIR, item.getMergeDecision());
-        
-        model.decide(0, MergeDecisionType.UNDECIDED);  
-        assertEquals(1, list.size());
-        item = list.get(0);
-        assertEquals(MergeDecisionType.UNDECIDED, item.getMergeDecision());
-    }
-    
+
+        model.decide(0, MergeDecisionType.UNDECIDED);
+        assertEquals(1, list.size());
+        item = list.get(0);
+        assertEquals(MergeDecisionType.UNDECIDED, item.getMergeDecision());
+    }
+
     @Test
     public void decideMultiple() {
-       
+
         TagMergeItem item = new TagMergeItem("key", "myvalue", "theirvalue");
         TagMergeModel model = new TagMergeModel();
@@ -207,13 +207,13 @@
             model.addItem(new TagMergeItem("key-" + i, "myvalue-" + i, "theirvalue-" +i));
         }
-             
-        ArrayList<TagMergeItem> list = field("tagMergeItems")
-            .ofType(ArrayList.class)
-            .in(model)
-            .get();
-        
+
+        ArrayList<TagMergeItem> list = field("tagMergeItems")
+        .ofType(ArrayList.class)
+        .in(model)
+        .get();
+
         assertEquals(10, list.size());
-        
-        model.decide(new int[] {0, 3, 5}, MergeDecisionType.KEEP_MINE);  
+
+        model.decide(new int[] {0, 3, 5}, MergeDecisionType.KEEP_MINE);
         for (int i = 0; i< 10; i++) {
             item = list.get(i);
