Index: /trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java	(revision 1690)
@@ -6,8 +6,7 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.swing.JOptionPane;
@@ -15,19 +14,8 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.command.PurgePrimitivesCommand;
-import org.openstreetmap.josm.command.UndeletePrimitivesCommand;
 import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-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.PleaseWaitRunnable;
-import org.openstreetmap.josm.io.OsmApi;
-import org.openstreetmap.josm.io.OsmApiException;
-import org.openstreetmap.josm.io.OsmServerObjectReader;
-import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
 import org.openstreetmap.josm.tools.Shortcut;
-import org.xml.sax.SAXException;
 
 /**
@@ -41,294 +29,4 @@
 
     /**
-     * Undelete a node which is already deleted on the server. The API
-     * doesn't offer a call for "undeleting" a node. We therefore create
-     * a clone of the node which we flag as new. On the next upload the
-     * server will assign the node a new id.
-     * 
-     * @param node the node to undelete
-     */
-    protected void  undeleteNode(Node node) {
-        UndeletePrimitivesCommand cmd = new UndeletePrimitivesCommand(node);
-        Main.main.undoRedo.add(cmd);
-    }
-
-    /**
-     * Undelete a way which is already deleted on the server.
-     * 
-     * This method also checks whether there are additional nodes referred to by
-     * this way which are deleted on the server too.
-     * 
-     * @param way the way to undelete
-     * @see #undeleteNode(Node)
-     */
-    protected void undeleteWay(final Way way) {
-        class NodeGoneChecker extends PleaseWaitRunnable {
-
-            UndeletePrimitivesCommand cmd = null;
-
-            public NodeGoneChecker() {
-                super(tr("Undeleting Way..."), false);
-            }
-
-            @Override
-            protected void cancel() {
-                OsmApi.getOsmApi().cancel();
-            }
-
-            @Override
-            protected void finish() {
-                if (cmd != null) {
-                    Main.main.undoRedo.add(cmd);
-                }
-            }
-
-            /**
-             * replies the subset of the node list which already
-             * have an assigned id
-             * 
-             * @param way  the way
-             * @return the node list
-             */
-            protected ArrayList<Node> getCandidateNodes(Way way) {
-                ArrayList<Node> candidates = new ArrayList<Node>();
-                for (Node n : way.nodes) {
-                    if (n.id > 0 && ! candidates.contains(n)) {
-                        candidates.add(n);
-                    }
-                }
-                return candidates;
-            }
-
-            /**
-             * checks whether a specific node is deleted on the server
-             * 
-             * @param n the node
-             * @return true, if the node is deleted; false, otherwise
-             * @throws OsmTransferException thrown, if an error occurs while communicating with the API
-             */
-            protected boolean isGone(Node n) throws OsmTransferException {
-                OsmServerObjectReader reader = new OsmServerObjectReader(n.id, OsmPrimitiveType.from(n), true);
-                try {
-                    reader.parseOsm();
-                } catch(OsmApiException e) {
-                    if (e.getResponseCode() == HttpURLConnection.HTTP_GONE)
-                        return true;
-                    throw e;
-                } catch(OsmTransferException e) {
-                    throw e;
-                }
-                return false;
-            }
-
-            /**
-             * displays a confirmation message. The user has to confirm that additional dependent
-             * nodes should be undeleted too.
-             * 
-             * @param way  the way
-             * @param dependent a list of dependent nodes which have to be undelete too
-             * @return true, if the user confirms; false, otherwise
-             */
-            protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) {
-                String [] options = {
-                        tr("Yes, undelete them too"),
-                        tr("No, cancel operation")
-                };
-                int ret = JOptionPane.showOptionDialog(
-                        Main.parent,
-                        tr("<html>There are {0} additional nodes used by way {1}<br>"
-                                + "which are deleted on the server.<br>"
-                                + "<br>"
-                                + "Do you want to undelete these nodes too?</html>",
-                                Long.toString(dependent.size()), Long.toString(way.id)),
-                                tr("Undelete additional nodes?"),
-                                JOptionPane.YES_NO_OPTION,
-                                JOptionPane.QUESTION_MESSAGE,
-                                null,
-                                options,
-                                options[0]
-                );
-
-                switch(ret) {
-                case JOptionPane.CLOSED_OPTION: return false;
-                case JOptionPane.YES_OPTION: return true;
-                case JOptionPane.NO_OPTION: return false;
-                }
-                return false;
-
-            }
-
-            @Override
-            protected void realRun() throws SAXException, IOException, OsmTransferException {
-                ArrayList<Node> candidate = getCandidateNodes(way);
-                ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
-                Main.pleaseWaitDlg.progress.setMinimum(0);
-                Main.pleaseWaitDlg.progress.setMaximum(candidate.size());
-
-                for (int i=0; i<candidate.size();i++) {
-                    Node n = candidate.get(i);
-                    Main.pleaseWaitDlg.progress.setValue(i);
-                    Main.pleaseWaitDlg.currentAction.setText(tr("Checking whether node {0} is gone ...", n.id));
-                    if (isGone(n)) {
-                        toDelete.add(n);
-                    }
-                }
-                if (toDelete.size() > 0) {
-                    if (!confirmUndeleteDependentPrimitives(way, toDelete))
-                        return;
-                }
-
-                toDelete.add(way);
-                cmd = new UndeletePrimitivesCommand(toDelete);
-            }
-        }
-
-        Main.worker.submit(new NodeGoneChecker());
-    }
-
-    /**
-     * Undelete a relation which is already deleted on the server.
-     * 
-     * This method  checks whether there are additional primitives referred to by
-     * this relation which are already deleted on the server.
-     * 
-     * @param r the relation
-     * @see #undeleteNode(Node)
-     */
-    protected void undeleteRelation(final Relation r) {
-        class RelationMemberGoneChecker extends PleaseWaitRunnable {
-
-            UndeletePrimitivesCommand cmd = null;
-
-            public RelationMemberGoneChecker() {
-                super(tr("Undeleting relation..."), false);
-            }
-
-            @Override
-            protected void cancel() {
-                OsmApi.getOsmApi().cancel();
-            }
-
-            @Override
-            protected void finish() {
-                if (cmd != null) {
-                    Main.main.undoRedo.add(cmd);
-                }
-            }
-
-            protected ArrayList<OsmPrimitive> getCandidateRelationMembers(Relation r) {
-                ArrayList<OsmPrimitive> candidates = new ArrayList<OsmPrimitive>();
-                for (RelationMember m : r.members) {
-                    if (m.member.id > 0 && !candidates.contains(m.member)) {
-                        candidates.add(m.member);
-                    }
-                }
-                return candidates;
-            }
-
-            protected boolean isGone(OsmPrimitive primitive) throws OsmTransferException {
-                OsmServerObjectReader reader = new OsmServerObjectReader(
-                        primitive.id,
-                        OsmPrimitiveType.from(primitive),
-                        true);
-                try {
-                    reader.parseOsm();
-                } catch(OsmApiException e) {
-                    if (e.getResponseCode() == HttpURLConnection.HTTP_GONE)
-                        return true;
-                    throw e;
-                } catch(OsmTransferException e) {
-                    throw e;
-                }
-                return false;
-            }
-
-            protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) {
-                String [] options = {
-                        tr("Yes, undelete them too"),
-                        tr("No, cancel operation")
-                };
-                int ret = JOptionPane.showOptionDialog(
-                        Main.parent,
-                        tr("<html>There are {0} additional primitives referred to by relation {1}<br>"
-                                + "which are deleted on the server.<br>"
-                                + "<br>"
-                                + "Do you want to undelete them too?</html>",
-                                Long.toString(dependent.size()), Long.toString(r.id)),
-                                tr("Undelete dependent primitives?"),
-                                JOptionPane.YES_NO_OPTION,
-                                JOptionPane.QUESTION_MESSAGE,
-                                null,
-                                options,
-                                options[0]
-                );
-
-                switch(ret) {
-                case JOptionPane.CLOSED_OPTION: return false;
-                case JOptionPane.YES_OPTION: return true;
-                case JOptionPane.NO_OPTION: return false;
-                }
-                return false;
-
-            }
-
-            @Override
-            protected void realRun() throws SAXException, IOException, OsmTransferException {
-                ArrayList<OsmPrimitive> candidate = getCandidateRelationMembers(r);
-                ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
-                Main.pleaseWaitDlg.progress.setMinimum(0);
-                Main.pleaseWaitDlg.progress.setMaximum(candidate.size());
-
-                for (int i=0; i<candidate.size();i++) {
-                    OsmPrimitive primitive = candidate.get(i);
-                    Main.pleaseWaitDlg.progress.setValue(i);
-                    Main.pleaseWaitDlg.currentAction.setText(tr("Checking whether primitive {0} is gone ...", primitive.id));
-                    if (isGone(primitive)) {
-                        toDelete.add(primitive);
-                    }
-                }
-                if (toDelete.size() > 0) {
-                    if (!confirmUndeleteDependentPrimitives(r, toDelete))
-                        return;
-                }
-
-                toDelete.add(r);
-                cmd = new UndeletePrimitivesCommand(toDelete);
-            }
-        }
-
-        Main.worker.submit(new RelationMemberGoneChecker());
-    }
-
-    /**
-     * User has decided to keep his local version of a primitive which had been deleted
-     * on the server
-     * 
-     * @param id the primitive id
-     */
-    protected void handlePrimitiveGoneKeepMine(long id) {
-        OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
-        if (primitive instanceof Node) {
-            undeleteNode((Node)primitive);
-        } else if (primitive instanceof Way) {
-            undeleteWay((Way)primitive);
-        } else if (primitive instanceof Relation) {
-            undeleteRelation((Relation)primitive);
-        }
-    }
-
-    /**
-     * User has decided to delete his local version of a primitive which had been deleted
-     * on the server
-     * 
-     * @param id the primitive id
-     */
-    protected void handlePrimitiveGoneDeleteMine(long id) {
-        OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
-        PurgePrimitivesCommand cmd = new PurgePrimitivesCommand(primitive);
-        Main.main.undoRedo.add(cmd);
-        Main.map.mapView.repaint();
-    }
-
-    /**
      * handle an exception thrown because a primitive was deleted on the server
      * 
@@ -336,38 +34,14 @@
      */
     protected void handlePrimitiveGoneException(long id) {
-        Object[] options = new Object[] {
-                tr("Keep mine"),
-                tr("Delete mine"),
-                tr("Cancel")
-        };
-        Object defaultOption = options[0];
-        String msg =  tr("<html>The OSM primitive with id <strong>{0}</strong> has been deleted<br>"
-                + "on the server by another mapper.<br>"
-                + "<br>"
-                + "Click <strong>{1}</strong> to keep your primitive and ignore the deleted state.<br>"
-                + "Your primitive will be assigend a new id.<br>"
-                + "Click <strong>{2}</strong> to accept the state on the server and to delete your primitive.<br>"
-                + "Click <strong>{3}</strong> to cancel.<br>",
-                id, options[0], options[1], options[2]
-        );
-        int ret = JOptionPane.showOptionDialog(
-                null,
-                msg,
-                tr("Primitive deleted on the server"),
-                JOptionPane.YES_NO_CANCEL_OPTION,
-                JOptionPane.ERROR_MESSAGE,
-                null,
-                options,
-                defaultOption
-        );
-        switch(ret) {
-        case JOptionPane.CLOSED_OPTION: return;
-        case JOptionPane.CANCEL_OPTION: return;
-        case 0: handlePrimitiveGoneKeepMine(id); break;
-        case 1: handlePrimitiveGoneDeleteMine(id); break;
-        default:
-            // should not happen
-            throw new IllegalStateException(tr("unexpected return value. Got {0}", ret));
+        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
+        reader.append(Main.main.editLayer().data,id);
+        DataSet ds = null;
+        try {
+            ds = reader.parseOsm();
+        } catch(Exception e) {
+            handleUpdateException(e);
+            return;
         }
+        Main.main.editLayer().mergeFrom(ds);
     }
 
@@ -378,16 +52,5 @@
      * @param e the exception
      */
-    protected void handleUpdateException(long id, Exception e) {
-        if (e instanceof OsmApiException) {
-            OsmApiException ex = (OsmApiException)e;
-            // if the primitive was deleted on the server ask the user
-            // whether he wants to undelete it
-            //
-            if (ex.getResponseCode() == HttpURLConnection.HTTP_GONE) {
-                handlePrimitiveGoneException(id);
-                return;
-            }
-        }
-
+    protected void handleUpdateException(Exception e) {
         e.printStackTrace();
         JOptionPane.showMessageDialog(
@@ -406,5 +69,5 @@
         JOptionPane.showMessageDialog(
                 Main.parent,
-                tr("Could not find primitive with id {0} in the current dataset", id),
+                tr("Could not find primitive with id {0} in the current dataset", new Long(id).toString()),
                 tr("Missing primitive"),
                 JOptionPane.ERROR_MESSAGE
@@ -413,23 +76,16 @@
 
     /**
-     * Updates the primitive with id <code>id</code> with the current state kept on the server.
      * 
-     * @param id
+     * 
+     * 
      */
-    public void updatePrimitive(long id) {
-        OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
-        if (primitive == null) {
-            handleMissingPrimitive(id);
-            return;
-        }
-        OsmServerObjectReader reader = new OsmServerObjectReader(
-                id,
-                OsmPrimitiveType.from(primitive),
-                true);
+    public void updatePrimitives(Collection<OsmPrimitive> selection) {
+        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
+        reader.append(selection);
         DataSet ds = null;
         try {
             ds = reader.parseOsm();
         } catch(Exception e) {
-            handleUpdateException(id, e);
+            handleUpdateException(e);
             return;
         }
@@ -437,4 +93,10 @@
     }
 
+    public void updatePrimitive(long id) {
+        OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);
+        Set<OsmPrimitive> s = new HashSet<OsmPrimitive>();
+        s.add(primitive);
+        updatePrimitives(s);
+    }
 
     public UpdateSelectionAction() {
@@ -461,27 +123,5 @@
             return;
         }
-
-        // check whether the current selection has an acceptable range.
-        // We don't want to hammer the API with hundreds of individual
-        // GET requests.
-        //
-        if (selection.size() > DEFAULT_MAX_SIZE_UPDATE_SELECTION) {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("<html>There are  <strong>{0}</strong> primitives <br>"
-                            + "selected for individual update. Please reduce the selection<br>"
-                            + "to max. {1} primitives.</html>",
-                            selection.size(), DEFAULT_MAX_SIZE_UPDATE_SELECTION
-                    ),
-                    tr("Selection too big"),
-                    JOptionPane.WARNING_MESSAGE
-            );
-            return;
-        }
-        for(OsmPrimitive primitive : selection) {
-            // FIXME: users should be able to abort this loop
-            //
-            updatePrimitive(primitive.id);
-        }
+        updatePrimitives(selection);
     }
 }
Index: /trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTaskList.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTaskList.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTaskList.java	(revision 1690)
@@ -128,5 +128,5 @@
     protected void handlePotentiallyDeletedPrimitives(Set<Long> potentiallyDeleted) {
         String [] options = {
-                "Check individually",
+                "Check on the server",
                 "Ignore"
         };
@@ -138,5 +138,6 @@
                 + "conflict.<br>"
                 + "<br>"
-                + "Click <strong>{1}</strong> to check these primitives individually.<br>"
+                + "Click <strong>{1}</strong> to check the state of these primitives<br>"
+                + "on the server.<br>"
                 + "Click <strong>{2}</strong> to ignore.<br>"
                 + "</html>",
Index: /trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java	(revision 1690)
@@ -66,7 +66,17 @@
 
         if (decision.equals(MergeDecisionType.KEEP_MINE)) {
-            // do nothing
+            if (my.deleted) {
+                // because my was involved in a conflict it my still be referred
+                // to from a way or a relation. Fix this now.
+                //
+                Main.main.editLayer().data.unlinkReferencesToPrimitive(my);
+            }
         } else if (decision.equals(MergeDecisionType.KEEP_THEIR)) {
-            my.deleted = their.deleted;
+            if (their.deleted) {
+                Main.main.editLayer().data.unlinkReferencesToPrimitive(my);
+                my.delete(true);
+            } else {
+                my.deleted = their.deleted;
+            }
         } else
             // should not happen
Index: /trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java	(revision 1690)
@@ -6,5 +6,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.swing.JLabel;
@@ -33,4 +35,5 @@
  */
 public class PurgePrimitivesCommand extends Command{
+
 
     /**
@@ -141,4 +144,6 @@
     private ArrayList<OsmParentChildPair> pairs;
 
+    private Map<OsmPrimitive, OsmPrimitive> resolvedConflicts;
+
     /**
      * constructor
@@ -149,4 +154,5 @@
         purgedPrimitives = new ArrayList<OsmPrimitive>();
         pairs = new ArrayList<OsmParentChildPair>();
+        resolvedConflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
     }
 
@@ -180,5 +186,5 @@
             if (pair.getParent() instanceof Way) {
                 Way w = (Way)pair.getParent();
-                System.out.println("removing reference from way " + w.id);
+                System.out.println(tr("removing reference from way {0}",w.id));
                 w.nodes.remove(primitive);
                 // if a way ends up with less than two node we
@@ -194,5 +200,5 @@
             } else if (pair.getParent() instanceof Relation) {
                 Relation r = (Relation)pair.getParent();
-                System.out.println("removing reference from relation " + r.id);
+                System.out.println(tr("removing reference from relation {0}",r.id));
                 r.removeMembersFor(primitive);
             }
@@ -220,4 +226,8 @@
             }
             purgedPrimitives.add(toPurge);
+            if (Main.map.conflictDialog.conflicts.containsKey(toPurge)) {
+                resolvedConflicts.put(toPurge, Main.map.conflictDialog.conflicts.get(toPurge));
+                Main.map.conflictDialog.removeConflictForPrimitive(toPurge);
+            }
         }
         return super.executeCommand();
@@ -236,6 +246,15 @@
     @Override
     public void undoCommand() {
+
+        // restore purged primitives
+        //
         for (OsmPrimitive purged : purgedPrimitives) {
             Main.ds.addPrimitive(purged);
+        }
+
+        // restore conflicts
+        //
+        for (OsmPrimitive primitive : resolvedConflicts.keySet()) {
+            Main.map.conflictDialog.addConflict(primitive, resolvedConflicts.get(primitive));
         }
 
Index: /trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java	(revision 1690)
@@ -6,4 +6,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.swing.JLabel;
@@ -11,4 +13,5 @@
 import javax.swing.tree.MutableTreeNode;
 
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -24,5 +27,10 @@
     /** the node to undelete */
     private ArrayList<OsmPrimitive> toUndelete;
+    private Map<OsmPrimitive,OsmPrimitive> resolvedConflicts;
 
+    protected UndeletePrimitivesCommand() {
+        toUndelete = new ArrayList<OsmPrimitive>();
+        resolvedConflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
+    }
     /**
      * constructor
@@ -30,5 +38,5 @@
      */
     public UndeletePrimitivesCommand(OsmPrimitive node) {
-        toUndelete = new ArrayList<OsmPrimitive>();
+        this();
         toUndelete.add(node);
     }
@@ -39,5 +47,5 @@
      */
     public UndeletePrimitivesCommand(OsmPrimitive ... toUndelete) {
-        this.toUndelete = new ArrayList<OsmPrimitive>();
+        this();
         for (int i=0; i < toUndelete.length; i++) {
             this.toUndelete.add(toUndelete[i]);
@@ -50,5 +58,5 @@
      */
     public UndeletePrimitivesCommand(Collection<OsmPrimitive> toUndelete) {
-        this.toUndelete = new ArrayList<OsmPrimitive>();
+        this();
         this.toUndelete.addAll(toUndelete);
     }
@@ -70,4 +78,8 @@
         super.executeCommand();
         for(OsmPrimitive primitive: toUndelete) {
+            if (Main.map.conflictDialog.conflicts.containsKey(primitive)) {
+                resolvedConflicts.put(primitive, Main.map.conflictDialog.conflicts.get(primitive));
+                Main.map.conflictDialog.removeConflictForPrimitive(primitive);
+            }
             primitive.id = 0;
         }
@@ -80,3 +92,13 @@
         modified.addAll(toUndelete);
     }
+    @Override
+    public void undoCommand() {
+        super.undoCommand();
+
+        for (OsmPrimitive my: resolvedConflicts.keySet()) {
+            if (!Main.map.conflictDialog.conflicts.containsKey(my)) {
+                Main.map.conflictDialog.addConflict(my, resolvedConflicts.get(my));
+            }
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java	(revision 1690)
@@ -11,9 +11,6 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.tools.ImageProvider;
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 1690)
@@ -8,4 +8,5 @@
 import java.util.HashSet;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -80,5 +81,5 @@
         Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
         for (OsmPrimitive osm : allPrimitives())
-            if (!osm.deleted) {
+            if (osm.visible && !osm.deleted) {
                 o.add(osm);
             }
@@ -89,5 +90,5 @@
         Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
         for (OsmPrimitive osm : allPrimitives())
-            if (!osm.deleted && !osm.incomplete) {
+            if (osm.visible && !osm.deleted && !osm.incomplete) {
                 o.add(osm);
             }
@@ -98,5 +99,5 @@
         Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>();
         for (OsmPrimitive osm : allPrimitives())
-            if (!osm.deleted && !osm.incomplete && !(osm instanceof Relation)) {
+            if (osm.visible && !osm.deleted && !osm.incomplete && !(osm instanceof Relation)) {
                 o.add(osm);
             }
@@ -311,3 +312,56 @@
         return ret;
     }
+
+    protected void deleteWay(Way way) {
+        way.nodes.clear();
+        way.delete(true);
+    }
+
+    /**
+     * removes all references from ways in this dataset to a particular node
+     * 
+     * @param node the node
+     */
+    public void unlinkNodeFromWays(Node node) {
+        for (Way way: ways) {
+            if (way.nodes.contains(node)) {
+                way.nodes.remove(node);
+                if (way.nodes.size() < 2) {
+                    deleteWay(way);
+                }
+            }
+        }
+    }
+
+    /**
+     * removes all references from relations in this dataset  to this primitive
+     * 
+     * @param primitive the primitive
+     */
+    public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
+        for (Relation relation : relations) {
+            Iterator<RelationMember> it = relation.members.iterator();
+            while(it.hasNext()) {
+                RelationMember member = it.next();
+                if (member.member.equals(primitive)) {
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * removes all references from from other primitives  to the
+     * referenced primitive
+     * 
+     * @param referencedPrimitive the referenced primitive
+     */
+    public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
+        if (referencedPrimitive instanceof Node) {
+            unlinkNodeFromWays((Node)referencedPrimitive);
+            unlinkPrimitiveFromRelations(referencedPrimitive);
+        } else {
+            unlinkPrimitiveFromRelations(referencedPrimitive);
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/Node.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Node.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Node.java	(revision 1690)
@@ -31,6 +31,6 @@
 
     public final void setEastNorth(EastNorth eastNorth) {
-       this.eastNorth = eastNorth;
-       this.coor = Main.proj.eastNorth2latlon(eastNorth);
+        this.eastNorth = eastNorth;
+        this.coor = Main.proj.eastNorth2latlon(eastNorth);
     }
 
@@ -87,4 +87,10 @@
     }
 
+    /**
+     * @deprecated
+     * @see #hasEqualSemanticAttributes(OsmPrimitive)
+     * @see #hasEqualTechnicalAttributes(OsmPrimitive)
+     */
+    @Deprecated
     @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
         if (osm instanceof Node) {
@@ -99,8 +105,24 @@
     }
 
+    @Override
+    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
+        if (other == null || ! (other instanceof Node) )
+            return false;
+        if (! super.hasEqualSemanticAttributes(other))
+            return false;
+        Node n = (Node)other;
+        if (coor == null && n.coor == null)
+            return true;
+        else if (coor != null && n.coor != null)
+            return coor.equals(n.coor);
+        else
+            return false;
+    }
+
     public int compareTo(OsmPrimitive o) {
         return o instanceof Node ? Long.valueOf(id).compareTo(o.id) : 1;
     }
 
+    @Override
     public String getName() {
         String name;
@@ -109,6 +131,7 @@
         } else {
             name = get("name");
-            if (name == null)
+            if (name == null) {
                 name = id == 0 ? tr("node") : ""+id;
+            }
             name += " (" + coor.latToString(mCord) + ", " + coor.lonToString(mCord) + ")";
         }
Index: /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 1690)
@@ -43,6 +43,7 @@
     public void putError(String text, Boolean isError)
     {
-        if(errors == null)
+        if(errors == null) {
             errors = new ArrayList<String>();
+        }
         String s = isError ? tr("Error: {0}", text) : tr("Warning: {0}", text);
         errors.add(s);
@@ -91,6 +92,5 @@
      * Visibility status as specified by the server. The visible attribute was
      * introduced with the 0.4 API to be able to communicate deleted objects
-     * (they will have visible=false). Currently JOSM does never deal with
-     * these, so this is really for future use only.
+     * (they will have visible=false).
      */
     public boolean visible = true;
@@ -195,7 +195,6 @@
     @Override public boolean equals(Object obj) {
         if (id == 0) return obj == this;
-        if (obj instanceof OsmPrimitive) { // not null too
+        if (obj instanceof OsmPrimitive)
             return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
-        }
         return false;
     }
@@ -226,9 +225,10 @@
      */
     public final void put(String key, String value) {
-        if (value == null)
+        if (value == null) {
             remove(key);
-        else {
-            if (keys == null)
+        } else {
+            if (keys == null) {
                 keys = new HashMap<String, String>();
+            }
             keys.put(key, value);
         }
@@ -241,6 +241,7 @@
         if (keys != null) {
             keys.remove(key);
-            if (keys.isEmpty())
+            if (keys.isEmpty()) {
                 keys = null;
+            }
         }
         mappaintStyle = null;
@@ -280,4 +281,5 @@
         version = osm.version;
         incomplete = osm.incomplete;
+        visible = osm.visible;
         clearCached();
         clearErrors();
@@ -288,15 +290,70 @@
      * but for the whole object (for conflict resolving)
      * @param semanticOnly if <code>true</code>, modified flag and timestamp are not compared
-     */
+     * 
+     * @deprecated
+     * @see #hasEqualSemanticAttributes(OsmPrimitive)
+     * @see #hasEqualTechnicalAttributes(OsmPrimitive)
+     */
+    @Deprecated
     public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
         return id == osm.id
         && incomplete == osm.incomplete
         && deleted == osm.deleted
-        && (semanticOnly || (modified == osm.modified
-         && timestamp == osm.timestamp
-         && version == osm.version
-         && visible == osm.visible
-         && (user == null ? osm.user==null : user==osm.user)))
+        && (semanticOnly || (
+                modified == osm.modified
+                && timestamp == osm.timestamp
+                && version == osm.version
+                && visible == osm.visible
+                && (user == null ? osm.user==null : user==osm.user))
+        )
         && (keys == null ? osm.keys==null : keys.equals(osm.keys));
+    }
+
+    /**
+     * Replies true if this primitive and other are equal with respect to their
+     * semantic attributes.
+     * <ol>
+     *   <li>equal id</ol>
+     *   <li>both are complete or both are incomplete</li>
+     *   <li>both have the same tags</li>
+     * </ol>
+     * @param other
+     * @return true if this primitive and other are equal with respect to their
+     * semantic attributes.
+     */
+    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
+        if (id != other.id)
+            return false;
+        if (incomplete && ! other.incomplete || !incomplete  && other.incomplete)
+            return false;
+        return (keys == null ? other.keys==null : keys.equals(other.keys));
+    }
+
+    /**
+     * Replies true if this primitive and other are equal with respect to their
+     * technical attributes. The attributes:
+     * <ol>
+     *   <li>deleted</ol>
+     *   <li>modified</ol>
+     *   <li>timestamp</ol>
+     *   <li>version</ol>
+     *   <li>visible</ol>
+     *   <li>user</ol>
+     * </ol>
+     * have to be equal
+     * @param other the other primitive
+     * @return true if this primitive and other are equal with respect to their
+     * technical attributes
+     */
+    public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
+        if (other == null) return false;
+
+        return
+        deleted == other.deleted
+        && modified == other.modified
+        && timestamp == other.timestamp
+        && version == other.version
+        && visible == other.visible
+        && (user == null ? other.user==null : user==other.user);
     }
 
@@ -312,7 +369,6 @@
         if (keys != null) {
             for (Entry<String,String> e : keys.entrySet()) {
-                if (!uninteresting.contains(e.getKey())) {
+                if (!uninteresting.contains(e.getKey()))
                     return true;
-                }
             }
         }
@@ -327,7 +383,6 @@
         if (keys != null) {
             for (Entry<String,String> e : keys.entrySet()) {
-                if (directionKeys.contains(e.getKey())) {
+                if (directionKeys.contains(e.getKey()))
                     return true;
-                }
             }
         }
Index: /trunk/src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 1690)
@@ -70,6 +70,17 @@
     }
 
+    @Deprecated
     @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
         return osm instanceof Relation ? super.realEqual(osm, semanticOnly) && members.equals(((Relation)osm).members) : false;
+    }
+
+    @Override
+    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
+        if (other == null || ! (other instanceof Relation) )
+            return false;
+        if (! super.hasEqualSemanticAttributes(other))
+            return false;
+        Relation r = (Relation)other;
+        return members.equals(r.members);
     }
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java	(revision 1690)
@@ -33,5 +33,5 @@
 
     @Override public boolean equals(Object other) {
-        if (!(other instanceof RelationMember)) return false;
+        if (other == null || !(other instanceof RelationMember)) return false;
         RelationMember otherMember = (RelationMember) other;
         return otherMember.role.equals(role) && otherMember.member.equals(member);
Index: /trunk/src/org/openstreetmap/josm/data/osm/Way.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 1690)
@@ -39,6 +39,7 @@
     public void visitNodes(Visitor v) {
         if (incomplete) return;
-        for (Node n : this.nodes)
+        for (Node n : this.nodes) {
             v.visit(n);
+        }
     }
 
@@ -100,6 +101,17 @@
     }
 
+    @Deprecated
     @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
         return osm instanceof Way ? super.realEqual(osm, semanticOnly) && nodes.equals(((Way)osm).nodes) : false;
+    }
+
+    @Override
+    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
+        if (other == null || ! (other instanceof Way) )
+            return false;
+        if (! super.hasEqualSemanticAttributes(other))
+            return false;
+        Way w = (Way)other;
+        return nodes.equals(w.nodes);
     }
 
@@ -110,4 +122,5 @@
     }
 
+    @Override
     public String getName() {
         String name;
@@ -116,11 +129,13 @@
         } else {
             name = get("name");
-            if (name == null) name = get("ref");
+            if (name == null) {
+                name = get("ref");
+            }
             if (name == null) {
                 name =
                     (get("highway") != null) ? tr("highway") :
-                    (get("railway") != null) ? tr("railway") :
-                    (get("waterway") != null) ? tr("waterway") :
-                    (get("landuse") != null) ? tr("landuse") : "";
+                        (get("railway") != null) ? tr("railway") :
+                            (get("waterway") != null) ? tr("waterway") :
+                                (get("landuse") != null) ? tr("landuse") : "";
             }
 
@@ -128,6 +143,7 @@
             String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
             name += (name.length() > 0) ? " ("+nodes+")" : nodes;
-            if(errors != null)
+            if(errors != null) {
                 name = "*"+name;
+            }
         }
         return name;
@@ -138,12 +154,13 @@
         boolean closed = (lastNode() == n && firstNode() == n);
         int i;
-        while ((i = nodes.indexOf(n)) >= 0)
+        while ((i = nodes.indexOf(n)) >= 0) {
             nodes.remove(i);
+        }
         i = nodes.size();
-        if (closed && i > 2) // close again
+        if (closed && i > 2) {
             addNode(firstNode());
-        // prevent closed ways with less than 3 different nodes
-        else if (i >= 2 && i <= 3 && nodes.get(0) == nodes.get(i-1))
+        } else if (i >= 2 && i <= 3 && nodes.get(0) == nodes.get(i-1)) {
             nodes.remove(i-1);
+        }
     }
 
@@ -151,8 +168,8 @@
         if (incomplete) return;
         for(OsmPrimitive p : selection) {
-           if (p instanceof Node) {
-               removeNode((Node)p);
-           }
-       }
+            if (p instanceof Node) {
+                removeNode((Node)p);
+            }
+        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java	(revision 1690)
@@ -2,25 +2,29 @@
 package org.openstreetmap.josm.data.osm.visitor;
 
+import static org.openstreetmap.josm.tools.I18n.tr;
+
 import java.util.Collection;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.Map;
+import java.util.logging.Logger;
 
 import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
 
 /**
- * A visitor that get a data set at construction time and merge every visited object
+ * A visitor that gets a data set at construction time and merges every visited object
  * into it.
  *
  * @author imi
+ * @author Gubaer
  */
 public class MergeVisitor extends AbstractVisitor {
+    private static Logger logger = Logger.getLogger(MergeVisitor.class.getName());
 
     /**
@@ -28,8 +32,8 @@
      * round than merged)
      */
-    public Map<OsmPrimitive, OsmPrimitive> conflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
-
-    private final DataSet ds;
-    private final DataSet mergeds;
+    private Map<OsmPrimitive, OsmPrimitive> conflicts;
+
+    private final DataSet myDataSet;
+    private final DataSet theirDataSet;
 
     private final HashMap<Long, Node> nodeshash = new HashMap<Long, Node>();
@@ -42,50 +46,102 @@
      * in ds.nodes instead.
      */
-    private final Map<OsmPrimitive, OsmPrimitive> merged
-        = new HashMap<OsmPrimitive, OsmPrimitive>();
-
-    public MergeVisitor(DataSet ds, DataSet mergeds) {
-        this.ds = ds;
-        this.mergeds = mergeds;
-
-        for (Node n : ds.nodes) if (n.id != 0) nodeshash.put(n.id, n);
-        for (Way w : ds.ways) if (w.id != 0) wayshash.put(w.id, w);
-        for (Relation r : ds.relations) if (r.id != 0) relshash.put(r.id, r);
-    }
-
-    private <P extends OsmPrimitive> void genMerge(P other,
-            Collection<P> myprims, Collection<P> mergeprims,
-            HashMap<Long, P> primhash) {
-        // 1. Try to find an identical prim with the same id.
-        if (mergeById(myprims, primhash, other))
-            return;
-
-        // 2. Try to find a prim we can merge with the prim from the other ds.
-        for (P my : myprims) {
-            // LinkedList.contains calls equal, and OsmPrimitive.equal
-            // compares just the id.
-            if (match(my, other) && !mergeprims.contains(my)) {
-                merged.put(other, my);
-                mergeCommon(my, other);
+    private Map<OsmPrimitive, OsmPrimitive> merged;
+
+    /**
+     * constructor
+     * 
+     * The visitor will merge <code>theirDataSet</code> onto <code>myDataSet</code>
+     * 
+     * @param myDataSet  dataset with my primitives
+     * @param theirDataSet dataset with their primitives.
+     */
+    public MergeVisitor(DataSet myDataSet, DataSet theirDataSet) {
+        this.myDataSet = myDataSet;
+        this.theirDataSet = theirDataSet;
+
+        for (Node n : myDataSet.nodes) if (n.id != 0) {
+            nodeshash.put(n.id, n);
+        }
+        for (Way w : myDataSet.ways) if (w.id != 0) {
+            wayshash.put(w.id, w);
+        }
+        for (Relation r : myDataSet.relations) if (r.id != 0) {
+            relshash.put(r.id, r);
+        }
+        conflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
+        merged = new HashMap<OsmPrimitive, OsmPrimitive>();
+    }
+
+    /**
+     * Merges a primitive <code>other</code> of type <P> onto my primitives.
+     * 
+     * If other.id != 0 it tries to merge it with an corresponding primitive from
+     * my dataset with the same id. If this is not possible a conflict is remembered
+     * in {@see #conflicts}.
+     * 
+     * If other.id == 0 it tries to find a primitive in my dataset with id == 0 which
+     * is semantically equal. If it finds one it merges its technical attributes onto
+     * my primitive.
+     * 
+     * @param <P>  the type of the other primitive
+     * @param other  the other primitive
+     * @param myPrimitives the collection of my relevant primitives (i.e. only my
+     *    primitives of the same type)
+     * @param otherPrimitives  the collection of the other primitives
+     * @param primitivesWithDefinedIds the collection of my primitives with an
+     *   assigned id (i.e. id != 0)
+     */
+    protected <P extends OsmPrimitive> void mergePrimitive(P other,
+            Collection<P> myPrimitives, Collection<P> otherPrimitives,
+            HashMap<Long, P> primitivesWithDefinedIds) {
+
+        if (other.id > 0 ) {
+            // try to merge onto a matching primitive with the same
+            // defined id
+            //
+            if (mergeById(myPrimitives, primitivesWithDefinedIds, other))
                 return;
-            }
-        }
-
-        // 3. No idea how to merge that.  Simply add it unchanged.
-        myprims.add(other);
+        } else {
+            // try to merge onto a primitive with which has no id assigned
+            // yet but which is equal in its semantic attributes
+            //
+            for (P my : myPrimitives) {
+                if (my.id >0 ) {
+                    continue;
+                }
+                if (my.hasEqualSemanticAttributes(other)) {
+                    // copy the technical attributes from their
+                    // version
+                    if (other.deleted) {
+                        myDataSet.unlinkReferencesToPrimitive(my);
+                        my.delete(true);
+                    }
+                    my.visible = other.visible;
+                    my.user = other.user;
+                    my.setTimestamp(other.getTimestamp());
+                    my.modified = other.modified;
+                    merged.put(other, my);
+                    return;
+                }
+            }
+        }
+        // If we get here we didn't find a suitable primitive in
+        // my dataset. Just add other to my dataset.
+        //
+        myPrimitives.add(other);
     }
 
     public void visit(Node other) {
-        genMerge(other, ds.nodes, mergeds.nodes, nodeshash);
+        mergePrimitive(other, myDataSet.nodes, theirDataSet.nodes, nodeshash);
     }
 
     public void visit(Way other) {
         fixWay(other);
-        genMerge(other, ds.ways, mergeds.ways, wayshash);
+        mergePrimitive(other, myDataSet.ways, theirDataSet.ways, wayshash);
     }
 
     public void visit(Relation other) {
         fixRelation(other);
-        genMerge(other, ds.relations, mergeds.relations, relshash);
+        mergePrimitive(other, myDataSet.relations, theirDataSet.relations, relshash);
     }
 
@@ -95,11 +151,16 @@
      */
     public void fixReferences() {
-        for (Way w : ds.ways) fixWay(w);
-        for (Relation r : ds.relations) fixRelation(r);
+        for (Way w : myDataSet.ways) {
+            fixWay(w);
+        }
+        for (Relation r : myDataSet.relations) {
+            fixRelation(r);
+        }
         for (OsmPrimitive osm : conflicts.values())
-            if (osm instanceof Way)
+            if (osm instanceof Way) {
                 fixWay((Way)osm);
-            else if (osm instanceof Relation)
+            } else if (osm instanceof Relation) {
                 fixRelation((Relation) osm);
+            }
     }
 
@@ -110,6 +171,7 @@
             Node otherN = (Node) merged.get(n);
             newNodes.add(otherN == null ? n : otherN);
-            if (otherN != null)
+            if (otherN != null) {
                 replacedSomething = true;
+            }
         }
         if (replacedSomething) {
@@ -139,155 +201,111 @@
     }
 
-    private static <P extends OsmPrimitive> boolean match(P p1, P p2) {
-        if ((p1.id == 0 || p2.id == 0) && !p1.incomplete && !p2.incomplete) {
-            return realMatch(p1, p2);
-        }
-        return p1.id == p2.id;
-    }
-
-    /** @return true if the prims have pretty much the same data, i.e. the
-     * same position, the same members, ...
-     */
-    // Java cannot dispatch on generics...
-    private static boolean realMatch(OsmPrimitive p1, OsmPrimitive p2) {
-        if (p1 instanceof Node && p2 instanceof Node) {
-            return realMatch((Node) p1, (Node) p2);
-        } else if (p1 instanceof Way && p2 instanceof Way) {
-            return realMatch((Way) p1, (Way) p2);
-        } else if (p1 instanceof Relation && p2 instanceof Relation) {
-            return realMatch((Relation) p1, (Relation) p2);
-        } else {
-            throw new RuntimeException("arguments have unknown type");
-        }
-    }
-
-    private static boolean realMatch(Node n1, Node n2) {
-        return n1.getCoor().equalsEpsilon(n2.getCoor());
-    }
-
-    private static boolean realMatch(Way w1, Way w2) {
-        if (w1.nodes.size() != w2.nodes.size())
-            return false;
-        Iterator<Node> it = w1.nodes.iterator();
-        for (Node n : w2.nodes)
-            if (!match(n, it.next()))
-                return false;
-        return true;
-    }
-
-    private static boolean realMatch(Relation w1, Relation w2) {
-        // FIXME this is not perfect yet...
-        if (w1.members.size() != w2.members.size())
-            return false;
-        for (RelationMember em : w1.members) {
-            if (!w2.members.contains(em)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Merge the common parts of an osm primitive.
-     * @param my The object, the information gets merged into
-     * @param other The object, the information gets merged from
-     */
-    private void mergeCommon(OsmPrimitive my, OsmPrimitive other) {
-        if (other.deleted)
-            my.delete(true);
-        if (my.id == 0 || !my.modified || other.modified) {
-            if (my.id == 0 && other.id != 0) {
-                my.id = other.id;
-                my.modified = other.modified; // match a new node
-                my.version = other.version;
-            } else if (my.id != 0 && other.id != 0 && other.modified)
-                my.modified = true;
-        }
-        if (other.keys == null)
-            return;
-        if (my.keySet().containsAll(other.keys.keySet()))
-            return;
-        if (my.keys == null)
-            my.keys = other.keys;
-        else
-            my.keys.putAll(other.keys);
-
-        my.modified = true;
-    }
-
     /**
      * Tries to merge a primitive <code>other</code> into an existing primitive with the same id.
      *
      * @param myPrimitives the complete set of my primitives (potential merge targets)
-     * @param myPrimitivesWithID the map of primitives (potential merge targets) with an id <> 0, for faster lookup
+     * @param myPrimitivesWithDefinedIds the map of primitives (potential merge targets) with an id <> 0, for faster lookup
      *    by id. Key is the id, value the primitive with the given value. myPrimitives.valueSet() is a
      *    subset of primitives.
-     * @param other  the other primitive which is to be merged with a primitive in primitives if possible
+     * @param other  the other primitive which is to be merged onto a primitive in my primitives
      * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise
      */
     private <P extends OsmPrimitive> boolean mergeById(
-            Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWithID, P other) {
+            Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWithDefinedIds, P other) {
 
         // merge other into an existing primitive with the same id, if possible
         //
-        if (myPrimitivesWithID.containsKey(other.id)) {
-            P my = myPrimitivesWithID.get(other.id);
-            if (my.realEqual(other, true /* compare semantic fields only */)) {
-                // make sure the merge target becomes the higher version number
-                // and the later timestamp
-                //
-                my.version = Math.max(other.version, my.version);
-                if (other.getTimestamp().after(my.getTimestamp())) {
-                    my.setTimestamp(other.getTimestamp());
+        if (myPrimitivesWithDefinedIds.containsKey(other.id)) {
+            P my = myPrimitivesWithDefinedIds.get(other.id);
+            if (my.version <= other.version) {
+                if (! my.visible && other.visible) {
+                    // should not happen
+                    //
+                    logger.warning(tr("My primitive with id {0} and version {1} is visible although "
+                            + "their primitive with lower version {2} is not visible. "
+                            + "Can't deal with this inconsistency. Keeping my primitive. ",
+                            Long.toString(my.id),Long.toString(my.version), Long.toString(other.version)
+                    ));
+                    merged.put(other, my);
+                } else if (my.visible && ! other.visible) {
+                    // this is always a conflict because the user has to decide whether
+                    // he wants to create a clone of its local primitive or whether he
+                    // wants to purge my from the local dataset. He can't keep it unchanged
+                    // because it was deleted on the server.
+                    //
+                    conflicts.put(my,other);
+                } else if (! my.modified && other.modified) {
+                    // my not modified. We can assume that other is the most recent version.
+                    // clone it onto my. But check first, whether other is deleted. if so,
+                    // make sure that my is not references anymore in myDataSet.
+                    //
+                    if (other.deleted) {
+                        myDataSet.unlinkReferencesToPrimitive(my);
+                    }
+                    my.cloneFrom(other);
+                    merged.put(other, my);
+                } else if (! my.modified && !other.modified) {
+                    // nothing to merge
+                    //
+                    merged.put(other,my);
+                } else if (my.deleted != other.deleted) {
+                    // if we get here my is modified. Differences in deleted state
+                    // have to be resolved manually
+                    //
+                    conflicts.put(my,other);
+                } else if (! my.hasEqualSemanticAttributes(other)) {
+                    // my is modified and is not semantically equal with other. Can't automatically
+                    // resolve the differences
+                    // =>  create a conflict
+                    conflicts.put(my,other);
+                } else {
+                    // clone from other, but keep the modified flag. Clone will mainly copy
+                    // technical attributes like timestamp or user information. Semantic
+                    // attributes should already be equal if we get here.
+                    //
+                    my.cloneFrom(other);
+                    my.modified = true;
+                    merged.put(other, my);
                 }
+            } else {
+                // my.version > other.version => keep my version
                 merged.put(other, my);
-                return true;
-            }
-        }
-
-        // try to merge into one of the existing primitives
-        //
-        for (P my : myPrimitives) {
-            if (my.realEqual(other, false /* compare all fields */)) {
-                merged.put(other, my);
-                return true; // no merge needed.
-            }
-            if (my.realEqual(other, true)) {
-                // they differ in modified/version combination only. Auto-resolve it.
-                merged.put(other, my);
-                if (my.version < other.version) {
-                    my.version = other.version;
-                    my.modified = other.modified;
-                    my.setTimestamp(other.getTimestamp());
-                }
-                return true; // merge done.
-            }
-            if (my.id == other.id && my.id != 0) {
-                if (my.incomplete || other.incomplete) {
-                    if (my.incomplete) {
-                        my.cloneFrom(other);
-                    }
-                } else if (my.modified && other.modified) {
-                    conflicts.put(my, other);
-                } else if (!my.modified && !other.modified) {
-                    if (my.version < other.version) {
-                        my.cloneFrom(other);
-                    }
-                } else if (other.modified) {
-                    if (my.version > other.version) {
-                        conflicts.put(my, other);
-                    } else {
-                        my.cloneFrom(other);
-                    }
-                } else if (my.modified) {
-                    if (my.version < other.version) {
-                        conflicts.put(my, other);
-                    }
-                }
-                merged.put(other, my);
-                return true;
-            }
+            }
+            return true;
         }
         return false;
     }
+
+
+    /**
+     * Runs the merge operation. Successfully merged {@see OsmPrimitive}s are in
+     * {@see #getMyDataSet()}.
+     * 
+     * See {@see #getConflicts()} for a map of conflicts after the merge operation.
+     */
+    public void merge() {
+        for (final OsmPrimitive primitive : theirDataSet.allPrimitives()) {
+            primitive.visit(this);
+        }
+        fixReferences();
+    }
+
+    /**
+     * replies my dataset
+     * 
+     * @return
+     */
+    public DataSet getMyDataSet() {
+        return myDataSet;
+    }
+
+
+    /**
+     * replies the map of conflicts
+     * 
+     * @return the map of conflicts
+     */
+    public Map<OsmPrimitive, OsmPrimitive> getConflicts() {
+        return conflicts;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java	(revision 1690)
@@ -23,4 +23,5 @@
 import org.openstreetmap.josm.gui.conflict.nodes.NodeListMergeModel;
 import org.openstreetmap.josm.gui.conflict.nodes.NodeListMerger;
+import org.openstreetmap.josm.gui.conflict.properties.OperationCancelledException;
 import org.openstreetmap.josm.gui.conflict.properties.PropertiesMergeModel;
 import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger;
@@ -145,4 +146,10 @@
         this.their =  their;
         propertiesMerger.getModel().populate(my, their);
+        if (propertiesMerger.getModel().hasVisibleStateConflict()) {
+            tabbedPane.setEnabledAt(1, false);
+            tabbedPane.setEnabledAt(2, false);
+            tabbedPane.setEnabledAt(3, false);
+            return;
+        }
         tabbedPane.setEnabledAt(0, true);
         tagMerger.getModel().populate(my, their);
@@ -165,4 +172,5 @@
             tabbedPane.setEnabledAt(3, true);
         }
+
     }
 
@@ -173,21 +181,27 @@
      * @return the resolution command
      */
-    public Command buildResolveCommand() {
+    public Command buildResolveCommand() throws OperationCancelledException {
         ArrayList<Command> commands = new ArrayList<Command>();
-        if (tagMerger.getModel().getNumResolvedConflicts() > 0) {
-            commands.add(tagMerger.getModel().buildResolveCommand(my, their));
-        }
-        commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their));
-        if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
-            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 (isResolvedCompletely()) {
-            commands.add(
-                    new VersionConflictResolveCommand(my, their)
-            );
+        if (propertiesMerger.getModel().hasVisibleStateConflict()) {
+            if (propertiesMerger.getModel().isDecidedVisibleState()) {
+                commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their));
+            }
+        } else {
+            if (tagMerger.getModel().getNumResolvedConflicts() > 0) {
+                commands.add(tagMerger.getModel().buildResolveCommand(my, their));
+            }
+            commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their));
+            if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
+                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 (isResolvedCompletely()) {
+                commands.add(
+                        new VersionConflictResolveCommand(my, their)
+                );
+            }
         }
         return new SequenceCommand(tr("Conflict Resolution"), commands);
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/properties/OperationCancelledException.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/properties/OperationCancelledException.java	(revision 1690)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/properties/OperationCancelledException.java	(revision 1690)
@@ -0,0 +1,27 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.properties;
+
+public class OperationCancelledException extends Exception {
+
+    public OperationCancelledException() {
+        super();
+        // TODO Auto-generated constructor stub
+    }
+
+    public OperationCancelledException(String message, Throwable cause) {
+        super(message, cause);
+        // TODO Auto-generated constructor stub
+    }
+
+    public OperationCancelledException(String message) {
+        super(message);
+        // TODO Auto-generated constructor stub
+    }
+
+    public OperationCancelledException(Throwable cause) {
+        super(cause);
+        // TODO Auto-generated constructor stub
+    }
+
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java	(revision 1690)
@@ -3,23 +3,46 @@
 
 import static org.openstreetmap.josm.gui.conflict.MergeDecisionType.UNDECIDED;
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.net.HttpURLConnection;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Observable;
 
+import javax.swing.JOptionPane;
+import javax.swing.text.html.HTML;
+
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.CoordinateConflictResolveCommand;
 import org.openstreetmap.josm.command.DeletedStateConflictResolveCommand;
+import org.openstreetmap.josm.command.PurgePrimitivesCommand;
+import org.openstreetmap.josm.command.UndeletePrimitivesCommand;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+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.PleaseWaitRunnable;
 import org.openstreetmap.josm.gui.conflict.MergeDecisionType;
+import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger.KeepMyVisibleStateAction;
+import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.OsmApiException;
+import org.openstreetmap.josm.io.OsmServerObjectReader;
+import org.openstreetmap.josm.io.OsmTransferException;
+import org.xml.sax.SAXException;
 
 /**
  * This is the model for resolving conflicts in the properties of the
  * {@see OsmPrimitive}s. In particular, it represents conflicts in the coordiates of {@see Node}s and
- * the deleted state of {@see OsmPrimitive}s.
+ * the deleted or visible state of {@see OsmPrimitive}s.
  *
  * This model is an {@see Observable}. It notifies registered {@see Observer}s whenever the
@@ -31,4 +54,5 @@
  * @see Node#getCoor()
  * @see OsmPrimitive#deleted
+ * @see OsmPrimitive#visible
  *
  */
@@ -37,4 +61,6 @@
     static public final String RESOLVED_COMPLETELY_PROP = PropertiesMergeModel.class.getName() + ".resolvedCompletely";
 
+    private OsmPrimitive my;
+
     private LatLon myCoords;
     private LatLon theirCoords;
@@ -43,5 +69,8 @@
     private boolean myDeletedState;
     private boolean theirDeletedState;
+    private boolean myVisibleState;
+    private boolean theirVisibleState;
     private MergeDecisionType deletedMergeDecision;
+    private MergeDecisionType visibleMergeDecision;
     private final PropertyChangeSupport support;
     private boolean resolvedCompletely;
@@ -91,4 +120,15 @@
 
     /**
+     * replies true if there is a  conflict in the visible state and if this conflict is
+     * resolved
+     *
+     * @return true if there is a conflict in the visible state and if this conflict is
+     * resolved; false, otherwise
+     */
+    public boolean isDecidedVisibleState() {
+        return ! visibleMergeDecision.equals(UNDECIDED);
+    }
+
+    /**
      * replies true if the current decision for the coordinate conflict is <code>decision</code>
      *
@@ -111,4 +151,13 @@
 
     /**
+     * replies true if the current decision for the visible state conflict is <code>decision</code>
+     *
+     * @return true if the current decision for the visible state conflict is <code>decision</code>;
+     *  false, otherwise
+     */
+    public boolean isVisibleStateDecision(MergeDecisionType decision) {
+        return visibleMergeDecision.equals(decision);
+    }
+    /**
      * populates the model with the differences between my and their version
      *
@@ -117,4 +166,5 @@
      */
     public void populate(OsmPrimitive my, OsmPrimitive their) {
+        this.my = my;
         if (my instanceof Node) {
             myCoords = ((Node)my).getCoor();
@@ -128,6 +178,10 @@
         theirDeletedState = their.deleted;
 
+        myVisibleState = my.visible;
+        theirVisibleState = their.visible;
+
         coordMergeDecision = UNDECIDED;
         deletedMergeDecision = UNDECIDED;
+        visibleMergeDecision = UNDECIDED;
         setChanged();
         notifyObservers();
@@ -209,6 +263,62 @@
     }
 
-    public void decideDeletedStateConflict(MergeDecisionType decision) {
+
+    /**
+     * replies my visible state,
+     * @return my visible state
+     */
+    public Boolean getMyVisibleState() {
+        return myVisibleState;
+    }
+
+    /**
+     * replies their visible state,
+     * @return their visible state
+     */
+    public  Boolean getTheirVisibleState() {
+        return theirVisibleState;
+    }
+
+    /**
+     * replies the merged visible state; null, if the merge decision is
+     * {@see MergeDecisionType#UNDECIDED}.
+     * 
+     * @return the merged visible state
+     */
+    public Boolean getMergedVisibleState() {
+        switch(visibleMergeDecision) {
+        case KEEP_MINE: return myVisibleState;
+        case KEEP_THEIR: return theirVisibleState;
+        case UNDECIDED: return null;
+        }
+        // should not happen
+        return null;
+    }
+
+    /**
+     * decides the conflict between two deleted states
+     * @param decision the decision (must not be null)
+     * 
+     * @throws IllegalArgumentException thrown, if decision is null
+     */
+    public void decideDeletedStateConflict(MergeDecisionType decision) throws IllegalArgumentException{
+        if (decision == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision"));
         this.deletedMergeDecision = decision;
+        setChanged();
+        notifyObservers();
+        fireCompletelyResolved();
+    }
+
+    /**
+     * decides the conflict between two visible states
+     * @param decision the decision (must not be null)
+     * 
+     * @throws IllegalArgumentException thrown, if decision is null
+     */
+    public void decideVisibleStateConflict(MergeDecisionType decision) throws IllegalArgumentException {
+        if (decision == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision"));
+        this.visibleMergeDecision = decision;
         setChanged();
         notifyObservers();
@@ -242,4 +352,15 @@
 
     /**
+     * replies true if my and their primitive have a conflict between
+     * their visible states
+     *
+     * @return true if my and their primitive have a conflict between
+     * their visible states
+     */
+    public boolean hasVisibleStateConflict() {
+        return myVisibleState != theirVisibleState;
+    }
+
+    /**
      * replies true if all conflict in this model are resolved
      *
@@ -254,4 +375,7 @@
             ret = ret && ! deletedMergeDecision.equals(UNDECIDED);
         }
+        if (hasVisibleStateConflict()) {
+            ret = ret && ! visibleMergeDecision.equals(UNDECIDED);
+        }
         return ret;
     }
@@ -264,6 +388,21 @@
      * @return the list of commands
      */
-    public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) {
+    public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) throws OperationCancelledException{
         ArrayList<Command> cmds = new ArrayList<Command>();
+        if (hasVisibleStateConflict() && isDecidedVisibleState()) {
+            if (isVisibleStateDecision(MergeDecisionType.KEEP_MINE)) {
+                try {
+                    UndeletePrimitivesCommand cmd = createUndeletePrimitiveCommand(my);
+                    if (cmd == null)
+                        throw new OperationCancelledException();
+                    cmds.add(cmd);
+                } catch(OsmTransferException e) {
+                    handleExceptionWhileBuildingCommand(e);
+                    throw new OperationCancelledException(e);
+                }
+            } else if (isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)) {
+                cmds.add(new PurgePrimitivesCommand(my));
+            }
+        }
         if (hasCoordConflict() && isDecidedCoord()) {
             cmds.add(new CoordinateConflictResolveCommand((Node)my, (Node)their, coordMergeDecision));
@@ -274,3 +413,195 @@
         return cmds;
     }
+
+    public OsmPrimitive getMyPrimitive() {
+        return my;
+    }
+
+    /**
+     * 
+     * @param id
+     */
+    protected void handleExceptionWhileBuildingCommand(Exception e) {
+        e.printStackTrace();
+        String msg = e.getMessage() != null ? e.getMessage() : e.toString();
+        msg = msg.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                tr("<html>An error occurred while communicating with the server<br>"
+                        + "Details: {0}</html>",
+                        msg
+                ),
+                tr("Communication with server failed"),
+                JOptionPane.ERROR_MESSAGE
+        );
+    }
+
+    /**
+     * User has decided to keep his local version of a primitive which had been deleted
+     * on the server
+     * 
+     * @param id the primitive id
+     */
+    protected UndeletePrimitivesCommand createUndeletePrimitiveCommand(OsmPrimitive my) throws OsmTransferException {
+        if (my instanceof Node)
+            return createUndeleteNodeCommand((Node)my);
+        else if (my instanceof Way)
+            return createUndeleteWayCommand((Way)my);
+        else if (my instanceof Relation)
+            return createUndeleteRelationCommand((Relation)my);
+        return null;
+    }
+    /**
+     * Undelete a node which is already deleted on the server. The API
+     * doesn't offer a call for "undeleting" a node. We therefore create
+     * a clone of the node which we flag as new. On the next upload the
+     * server will assign the node a new id.
+     * 
+     * @param node the node to undelete
+     */
+    protected UndeletePrimitivesCommand  createUndeleteNodeCommand(Node node) {
+        return new UndeletePrimitivesCommand(node);
+    }
+
+    /**
+     * displays a confirmation message. The user has to confirm that additional dependent
+     * nodes should be undeleted too.
+     * 
+     * @param way  the way
+     * @param dependent a list of dependent nodes which have to be undelete too
+     * @return true, if the user confirms; false, otherwise
+     */
+    protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) {
+        String [] options = {
+                tr("Yes, undelete them too"),
+                tr("No, cancel operation")
+        };
+        int ret = JOptionPane.showOptionDialog(
+                Main.parent,
+                tr("<html>There are {0} additional nodes used by way {1}<br>"
+                        + "which are deleted on the server.<br>"
+                        + "<br>"
+                        + "Do you want to undelete these nodes too?</html>",
+                        Long.toString(dependent.size()), Long.toString(way.id)),
+                        tr("Undelete additional nodes?"),
+                        JOptionPane.YES_NO_OPTION,
+                        JOptionPane.QUESTION_MESSAGE,
+                        null,
+                        options,
+                        options[0]
+        );
+
+        switch(ret) {
+        case JOptionPane.CLOSED_OPTION: return false;
+        case JOptionPane.YES_OPTION: return true;
+        case JOptionPane.NO_OPTION: return false;
+        }
+        return false;
+
+    }
+
+    protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) {
+        String [] options = {
+                tr("Yes, undelete them too"),
+                tr("No, cancel operation")
+        };
+        int ret = JOptionPane.showOptionDialog(
+                Main.parent,
+                tr("<html>There are {0} additional primitives referred to by relation {1}<br>"
+                        + "which are deleted on the server.<br>"
+                        + "<br>"
+                        + "Do you want to undelete them too?</html>",
+                        Long.toString(dependent.size()), Long.toString(r.id)),
+                        tr("Undelete dependent primitives?"),
+                        JOptionPane.YES_NO_OPTION,
+                        JOptionPane.QUESTION_MESSAGE,
+                        null,
+                        options,
+                        options[0]
+        );
+
+        switch(ret) {
+        case JOptionPane.CLOSED_OPTION: return false;
+        case JOptionPane.YES_OPTION: return true;
+        case JOptionPane.NO_OPTION: return false;
+        }
+        return false;
+
+    }
+
+    /**
+     * Creates the undelete command for a way which is already deleted on the server.
+     * 
+     * This method also checks whether there are additional nodes referred to by
+     * this way which are deleted on the server too.
+     * 
+     * @param way the way to undelete
+     * @return the undelete command
+     * @see #createUndeleteNodeCommand(Node)
+     */
+    protected UndeletePrimitivesCommand createUndeleteWayCommand(final Way way) throws OsmTransferException {
+
+        HashMap<Long,OsmPrimitive> candidates = new HashMap<Long,OsmPrimitive>();
+        for (Node n : way.nodes) {
+            if (n.id > 0 && ! candidates.values().contains(n)) {
+                candidates.put(n.id, n);
+            }
+        }
+        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
+        reader.append(candidates.values());
+        DataSet ds = reader.parseOsm();
+
+        ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
+        for (OsmPrimitive their : ds.allPrimitives()) {
+            if (candidates.keySet().contains(their.id) && ! their.visible) {
+                toDelete.add(candidates.get(their.id));
+            }
+        }
+        if (!toDelete.isEmpty()) {
+            if (! confirmUndeleteDependentPrimitives(way, toDelete))
+                // FIXME: throw exception ?
+                return null;
+        }
+        toDelete.add(way);
+        return new UndeletePrimitivesCommand(toDelete);
+    }
+
+    /**
+     * Creates an undelete command for a relation which is already deleted on the server.
+     * 
+     * This method  checks whether there are additional primitives referred to by
+     * this relation which are already deleted on the server.
+     * 
+     * @param r the relation
+     * @return the undelete command
+     * @see #createUndeleteNodeCommand(Node)
+     */
+    protected UndeletePrimitivesCommand createUndeleteRelationCommand(final Relation r) throws OsmTransferException {
+
+        HashMap<Long,OsmPrimitive> candidates = new HashMap<Long, OsmPrimitive>();
+        for (RelationMember m : r.members) {
+            if (m.member.id > 0 && !candidates.values().contains(m.member)) {
+                candidates.put(m.member.id,m.member);
+            }
+        }
+
+        MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader();
+        reader.append(candidates.values());
+        DataSet ds = reader.parseOsm();
+
+        ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();
+        for (OsmPrimitive their : ds.allPrimitives()) {
+            if (candidates.keySet().contains(their.id) && ! their.visible) {
+                toDelete.add(candidates.get(their.id));
+            }
+        }
+        if (!toDelete.isEmpty()) {
+            if (! confirmUndeleteDependentPrimitives(r, toDelete))
+                // FIXME: throw exception ?
+                return null;
+        }
+        toDelete.add(r);
+        return new UndeletePrimitivesCommand(toDelete);
+    }
+
 }
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMerger.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMerger.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMerger.java	(revision 1690)
@@ -18,7 +18,11 @@
 import javax.swing.JButton;
 import javax.swing.JLabel;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
-
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.gui.conflict.MergeDecisionType;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -48,4 +52,8 @@
     private JLabel lblTheirDeletedState;
 
+    private JLabel lblMyVisibleState;
+    private JLabel lblMergedVisibleState;
+    private JLabel lblTheirVisibleState;
+
     private final PropertiesMergeModel model;
 
@@ -59,9 +67,7 @@
     }
 
-    protected void build() {
-        setLayout(new GridBagLayout());
+    protected void buildHeaderRow() {
         GridBagConstraints gc = new GridBagConstraints();
 
-        // ------------------
         gc.gridx = 1;
         gc.gridy = 0;
@@ -88,6 +94,9 @@
         lblTheirVersion.setToolTipText(tr("Properties in their dataset, i.e. the server dataset"));
         add(lblTheirVersion, gc);
-
-        // --------------------------------
+    }
+
+    protected void buildCoordinateConflictRows() {
+        GridBagConstraints gc = new GridBagConstraints();
+
         gc.gridx = 0;
         gc.gridy = 1;
@@ -161,5 +170,8 @@
         JButton btnUndecideCoordinates = new JButton(actUndecideCoordinates);
         add(btnUndecideCoordinates, gc);
-        // ---------------------------------------------------
+    }
+
+    protected void buildDeletedStateConflictRows() {
+        GridBagConstraints gc = new GridBagConstraints();
 
         gc.gridx = 0;
@@ -191,5 +203,5 @@
         model.addObserver(actKeepMyDeletedState);
         JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState);
-        btnKeepMyCoordinates.setName("button.keepmydeletedstate");
+        btnKeepMyDeletedState.setName("button.keepmydeletedstate");
         add(btnKeepMyDeletedState, gc);
 
@@ -212,5 +224,5 @@
         model.addObserver(actKeepTheirDeletedState);
         JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState);
-        btnKeepMyCoordinates.setName("button.keeptheirdeletedstate");
+        btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate");
         add(btnKeepTheirDeletedState, gc);
 
@@ -233,7 +245,90 @@
         model.addObserver(actUndecideDeletedState);
         JButton btnUndecideDeletedState = new JButton(actUndecideDeletedState);
-        btnKeepMyCoordinates.setName("button.undecidedeletedstate");
+        btnUndecideDeletedState.setName("button.undecidedeletedstate");
         add(btnUndecideDeletedState, gc);
-
+    }
+
+    protected void buildVisibleStateRows() {
+        GridBagConstraints gc = new GridBagConstraints();
+
+        gc.gridx = 0;
+        gc.gridy = 5;
+        gc.gridwidth = 1;
+        gc.gridheight = 1;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.LINE_START;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        gc.insets = new Insets(0,5,0,5);
+        add(new JLabel(tr("Visible State:")), gc);
+
+        gc.gridx = 1;
+        gc.gridy = 5;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.33;
+        gc.weighty = 0.0;
+        add(lblMyVisibleState = buildValueLabel("label.myvisiblestate"), gc);
+
+        gc.gridx = 2;
+        gc.gridy = 5;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        KeepMyVisibleStateAction actKeepMyVisibleState = new KeepMyVisibleStateAction();
+        model.addObserver(actKeepMyVisibleState);
+        JButton btnKeepMyVisibleState = new JButton(actKeepMyVisibleState);
+        btnKeepMyVisibleState.setName("button.keepmyvisiblestate");
+        add(btnKeepMyVisibleState, gc);
+
+        gc.gridx = 3;
+        gc.gridy = 5;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.33;
+        gc.weighty = 0.0;
+        add(lblMergedVisibleState = buildValueLabel("label.mergedvisiblestate"), gc);
+
+        gc.gridx = 4;
+        gc.gridy = 5;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        KeepTheirVisibleStateAction actKeepTheirVisibleState = new KeepTheirVisibleStateAction();
+        model.addObserver(actKeepTheirVisibleState);
+        JButton btnKeepTheirVisibleState = new JButton(actKeepTheirVisibleState);
+        btnKeepTheirVisibleState.setName("button.keeptheirvisiblestate");
+        add(btnKeepTheirVisibleState, gc);
+
+        gc.gridx = 5;
+        gc.gridy = 5;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.33;
+        gc.weighty = 0.0;
+        add(lblTheirVisibleState = buildValueLabel("label.theirvisiblestate"), gc);
+
+        // ---------------------------------------------------
+        gc.gridx = 3;
+        gc.gridy = 6;
+        gc.fill = GridBagConstraints.NONE;
+        gc.anchor = GridBagConstraints.CENTER;
+        gc.weightx = 0.0;
+        gc.weighty = 0.0;
+        UndecideVisibleStateConflictAction actUndecideVisibleState = new UndecideVisibleStateConflictAction();
+        model.addObserver(actUndecideVisibleState);
+        JButton btnUndecideVisibleState = new JButton(actUndecideVisibleState);
+        btnUndecideVisibleState.setName("button.undecidevisiblestate");
+        add(btnUndecideVisibleState, gc);
+    }
+
+    protected void build() {
+        setLayout(new GridBagLayout());
+        buildHeaderRow();
+        buildCoordinateConflictRows();
+        buildDeletedStateConflictRows();
+        buildVisibleStateRows();
     }
 
@@ -265,5 +360,23 @@
     }
 
-    protected void updateCoordiates() {
+    public String visibleStateToString(Boolean visible) {
+        if (visible == null)
+            return tr("(none)");
+        if (visible)
+            return tr("visible (on the server)");
+        else
+            return tr("not visible (on the server)");
+    }
+
+    public String visibleStateToStringMerged(Boolean visible) {
+        if (visible == null)
+            return tr("(none)");
+        if (visible)
+            return tr("Keep a clone of the local version");
+        else
+            return tr("Physically delete from local dataset");
+    }
+
+    protected void updateCoordinates() {
         lblMyCoordinates.setText(coordToString(model.getMyCoords()));
         lblMergedCoordinates.setText(coordToString(model.getMergedCoords()));
@@ -320,7 +433,36 @@
     }
 
+    protected void updateVisibleState() {
+        lblMyVisibleState.setText(visibleStateToString(model.getMyVisibleState()));
+        lblMergedVisibleState.setText(visibleStateToStringMerged(model.getMergedVisibleState()));
+        lblTheirVisibleState.setText(visibleStateToString(model.getTheirVisibleState()));
+
+        if (! model.hasVisibleStateConflict()) {
+            lblMyVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
+            lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
+            lblTheirVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
+        } else {
+            if (!model.isDecidedVisibleState()) {
+                lblMyVisibleState.setBackground(BGCOLOR_UNDECIDED);
+                lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
+                lblTheirVisibleState.setBackground(BGCOLOR_UNDECIDED);
+            } else {
+                lblMyVisibleState.setBackground(
+                        model.isVisibleStateDecision(MergeDecisionType.KEEP_MINE)
+                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
+                );
+                lblMergedVisibleState.setBackground(BGCOLOR_DECIDED);
+                lblTheirVisibleState.setBackground(
+                        model.isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)
+                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
+                );
+            }
+        }
+    }
+
     public void update(Observable o, Object arg) {
-        updateCoordiates();
+        updateCoordinates();
         updateDeletedState();
+        updateVisibleState();
     }
 
@@ -418,3 +560,99 @@
         }
     }
+
+    class KeepMyVisibleStateAction extends AbstractAction implements Observer {
+        public KeepMyVisibleStateAction() {
+            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
+            putValue(Action.SHORT_DESCRIPTION, tr("Keep my visible state"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if (confirmKeepMine()) {
+                model.decideVisibleStateConflict(MergeDecisionType.KEEP_MINE);
+            }
+        }
+
+        public void update(Observable o, Object arg) {
+            setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState());
+        }
+
+        protected boolean confirmKeepMine() {
+            String [] options = {
+                    tr("Yes, reset the id"),
+                    tr("No, abort")
+            };
+            int ret = JOptionPane.showOptionDialog(
+                    null,
+                    tr("<html>To keep your local version, JOSM<br>"
+                            + "has to reset the id of {0} {1} to 0.<br>"
+                            + "On the next upload the server will assign<br>"
+                            + "it a new id.<br>"
+                            + "Do yo agree?</html>",
+                            OsmPrimitiveType.from(model.getMyPrimitive()).getLocalizedDisplayNamePlural(),
+                            model.getMyPrimitive().id
+                    ),
+                    tr("Reset id to 0"),
+                    JOptionPane.YES_NO_OPTION,
+                    JOptionPane.QUESTION_MESSAGE,
+                    null,
+                    options,
+                    options[1]
+            );
+            return ret == JOptionPane.YES_OPTION;
+        }
+    }
+
+    class KeepTheirVisibleStateAction extends AbstractAction implements Observer {
+        public KeepTheirVisibleStateAction() {
+            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
+            putValue(Action.SHORT_DESCRIPTION, tr("Keep their visible state"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if (confirmKeepTheir()){
+                model.decideVisibleStateConflict(MergeDecisionType.KEEP_THEIR);
+            }
+        }
+
+        public void update(Observable o, Object arg) {
+            setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState());
+        }
+
+        protected boolean confirmKeepTheir() {
+            String [] options = {
+                    tr("Yes, purge it"),
+                    tr("No, abort")
+            };
+            int ret = JOptionPane.showOptionDialog(
+                    null,
+                    tr("<html>JOSM will have to remove your local primitive with id {0}<br>"
+                            + "from the dataset.<br>"
+                            + "Do you agree?</html>",
+                            model.getMyPrimitive().id
+                    ),
+                    tr("Remove from dataset"),
+                    JOptionPane.YES_NO_OPTION,
+                    JOptionPane.QUESTION_MESSAGE,
+                    null,
+                    options,
+                    options[1]
+            );
+            return ret == JOptionPane.YES_OPTION;
+        }
+    }
+
+    class UndecideVisibleStateConflictAction extends AbstractAction implements Observer {
+        public UndecideVisibleStateConflictAction() {
+            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
+            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between visible state"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            model.decideVisibleStateConflict(MergeDecisionType.UNDECIDED);
+        }
+
+        public void update(Observable o, Object arg) {
+            setEnabled(model.hasVisibleStateConflict() && model.isDecidedVisibleState());
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java	(revision 1690)
@@ -23,4 +23,5 @@
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.gui.conflict.ConflictResolver;
+import org.openstreetmap.josm.gui.conflict.properties.OperationCancelledException;
 import org.openstreetmap.josm.tools.ImageProvider;
 
@@ -190,6 +191,10 @@
                     return;
             }
-            Command cmd = resolver.buildResolveCommand();
-            Main.main.undoRedo.add(cmd);
+            try {
+                Command cmd = resolver.buildResolveCommand();
+                Main.main.undoRedo.add(cmd);
+            } catch(OperationCancelledException e) {
+                // do nothing. Exception already reported
+            }
             setVisible(false);
         }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java	(revision 1690)
@@ -527,8 +527,5 @@
                     final MergeVisitor visitor = new MergeVisitor(Main.main
                             .editLayer().data, dataSet);
-                    for (final OsmPrimitive osm : dataSet.allPrimitives()) {
-                        osm.visit(visitor);
-                    }
-                    visitor.fixReferences();
+                    visitor.merge();
 
                     // copy the merged layer's data source info
@@ -538,8 +535,8 @@
                     Main.main.editLayer().fireDataChange();
 
-                    if (visitor.conflicts.isEmpty())
+                    if (visitor.getConflicts().isEmpty())
                         return;
                     final ConflictDialog dlg = Main.map.conflictDialog;
-                    dlg.add(visitor.conflicts);
+                    dlg.add(visitor.getConflicts());
                     JOptionPane.showMessageDialog(Main.parent,
                             tr("There were conflicts during import."));
Index: /trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 1690)
@@ -245,8 +245,5 @@
     public void mergeFrom(final DataSet from) {
         final MergeVisitor visitor = new MergeVisitor(data,from);
-        for (final OsmPrimitive osm : from.allPrimitives()) {
-            osm.visit(visitor);
-        }
-        visitor.fixReferences();
+        visitor.merge();
 
         Area a = data.getDataSourceArea();
@@ -274,9 +271,9 @@
         Main.map.mapView.repaint();
 
-        if (visitor.conflicts.isEmpty())
+        if (visitor.getConflicts().isEmpty())
             return;
         final ConflictDialog dlg = Main.map.conflictDialog;
-        dlg.add(visitor.conflicts);
-        JOptionPane.showMessageDialog(Main.parent,tr("There were {0} conflicts during import.", visitor.conflicts.size()));
+        dlg.add(visitor.getConflicts());
+        JOptionPane.showMessageDialog(Main.parent,tr("There were {0} conflicts during import.", visitor.getConflicts().size()));
         if (!dlg.isVisible()) {
             dlg.action.actionPerformed(new ActionEvent(this, 0, ""));
Index: /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 1690)
+++ /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 1690)
@@ -0,0 +1,472 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+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.data.osm.visitor.MergeVisitor;
+import org.xml.sax.SAXException;
+
+/**
+ * Retrieves a set of {@see OsmPrimitive}s from an OSM server using the so called
+ * Multi Fetch API.
+ * 
+ * Usage:
+ * <pre>
+ *    MultiFetchServerObjectReader reader = MultiFetchServerObjectReader()
+ *         .append(2345,2334,4444)
+ *         .append(new Node(72343));
+ *    reader.parseOsm();
+ *    if (!reader.getMissingPrimitives().isEmpty()) {
+ *        System.out.println("There are missing primitives: " + reader.getMissingPrimitives());
+ *    }
+ *    if (!reader.getSkippedWays().isEmpty()) {
+ *       System.out.println("There are skipped ways: " + reader.getMissingPrimitives());
+ *    }
+ * </pre>
+ * 
+ *
+ */
+public class MultiFetchServerObjectReader extends OsmServerReader{
+
+    static private Logger logger = Logger.getLogger(MultiFetchServerObjectReader.class.getName());
+    /**
+     * the max. number of primitives retrieved in one step. Assuming IDs with 7 digits,
+     * this leads to a max. request URL of ~  1600 Bytes ((7 digits +  1 Seperator) * 200),
+     * which should be safe according to the
+     * <a href="http://www.boutell.com/newfaq/misc/urllength.html">WWW FAQ</a>.
+     * 
+     */
+    static private int MAX_IDS_PER_REQUEST = 200;
+
+
+    private HashSet<Long> nodes;
+    private HashSet<Long> ways;
+    private HashSet<Long> relations;
+    private HashSet<Long> missingPrimitives;
+    private HashSet<Long> skippedWayIds;
+    private DataSet outputDataSet;
+
+    /**
+     * constructor
+     *
+     */
+    public MultiFetchServerObjectReader() {
+        nodes = new HashSet<Long>();
+        ways = new HashSet<Long>();
+        relations = new HashSet<Long>();
+        this.outputDataSet = new DataSet();
+        this.missingPrimitives = new HashSet<Long>();
+    }
+
+    /**
+     * remembers an {@see OsmPrimitive}'s id and its type. The id will
+     * later be fetched as part of a Multi Get request.
+     * 
+     * Ignore the id if it id <= 0.
+     * 
+     * @param id  the id
+     * @param type  the type
+     */
+    protected void remember(long id, OsmPrimitiveType type) {
+        if (id <= 0) return;
+        if (type.equals(OsmPrimitiveType.NODE)) {
+            nodes.add(id);
+        } else if (type.equals(OsmPrimitiveType.WAY)) {
+            ways.add(id);
+        } if (type.equals(OsmPrimitiveType.RELATION)) {
+            relations.add(id);
+        }
+    }
+
+    /**
+     * remembers an {@see OsmPrimitive}'s id. <code>ds</code> must include
+     * an {@see OsmPrimitive} with id=<code>id</code>. The id will
+     * later we fetched as part of a Multi Get request.
+     * 
+     * Ignore the id if it id <= 0.
+     * 
+     * @param ds  the dataset (must not be null)
+     * @param id  the id
+     * @exception IllegalArgumentException thrown, if ds is null
+     * @exception NoSuchElementException thrown, if ds doesn't include an {@see OsmPrimitive} with
+     *   id=<code>id</code>
+     */
+    protected void remember(DataSet ds, long id) throws IllegalArgumentException, NoSuchElementException{
+        if (ds == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "ds"));
+        if (id <= 0) return;
+        OsmPrimitive primitive = ds.getPrimitiveById(id);
+        if (primitive == null)
+            throw new NoSuchElementException(tr("no primitive with id {0} in local dataset. Can't infer primitive type", id));
+        remember(id, OsmPrimitiveType.from(primitive));
+        return;
+    }
+
+    /**
+     * appends a list of  ids to the list of ids which will be fetched from the server. ds must
+     * include an {@see OsmPrimitive} for each id in ids.
+     * 
+     * id is ignored if id <= 0.
+     * 
+     * @param ds  the dataset
+     * @param ids  the list of ids
+     * @return this
+     * 
+     */
+    public MultiFetchServerObjectReader append(DataSet ds, long ... ids)  {
+        if (ids == null) return this;
+        for (int i=0; i < ids.length; i++) {
+            remember(ds, ids[i]);
+        }
+        return this;
+    }
+
+    /**
+     * appends a collection of  ids to the list of ids which will be fetched from the server. ds must
+     * include an {@see OsmPrimitive} for each id in ids.
+     * 
+     * id is ignored if id <= 0.
+     * 
+     * @param ds  the dataset
+     * @param ids  the collection of ids
+     * @return this
+     * 
+     */
+    public MultiFetchServerObjectReader append(DataSet ds, Collection<Long> ids) {
+        if (ids == null) return null;
+        for (long id: ids) {
+            append(ds,id);
+        }
+        return this;
+    }
+
+    /**
+     * appends a {@see Node}s id to the list of ids which will be fetched from the server.
+     *
+     * @param node  the node (ignored, if null)
+     * @return this
+     * 
+     */
+    public MultiFetchServerObjectReader append(Node node) {
+        if (node == null) return this;
+        if (node.id == 0) return this;
+        remember(node.id, OsmPrimitiveType.NODE);
+        return this;
+    }
+
+    /**
+     * appends a {@see Way}s id and the list of ids of nodes the way refers to to the list of ids which will be fetched from the server.
+     *
+     * @param way the way (ignored, if null)
+     * @return this
+     * 
+     */
+    public MultiFetchServerObjectReader append(Way way) {
+        if (way == null) return this;
+        if (way.id == 0) return this;
+        for (Node node: way.nodes) {
+            if (node.id > 0) {
+                remember(node.id, OsmPrimitiveType.NODE);
+            }
+        }
+        remember(way.id, OsmPrimitiveType.WAY);
+        return this;
+    }
+
+    /**
+     * appends a {@see Relation}s id to the list of ids which will be fetched from the server.
+     *
+     * @param relation  the relation (ignored, if null)
+     * @return this
+     * 
+     */
+    public MultiFetchServerObjectReader append(Relation relation) {
+        if (relation == null) return this;
+        if (relation.id == 0) return this;
+        remember(relation.id, OsmPrimitiveType.RELATION);
+        for (RelationMember member : relation.members) {
+            appendGeneric(member.member);
+        }
+        return this;
+    }
+
+
+    protected MultiFetchServerObjectReader appendGeneric(OsmPrimitive primitive) {
+        if (OsmPrimitiveType.from(primitive).equals(OsmPrimitiveType.NODE))
+            return append((Node)primitive);
+        else if (OsmPrimitiveType.from(primitive).equals(OsmPrimitiveType.WAY))
+            return append((Way)primitive);
+        else if (OsmPrimitiveType.from(primitive).equals(OsmPrimitiveType.RELATION))
+            return append((Relation)primitive);
+
+        return this;
+    }
+
+    /**
+     * appends a list of {@see OsmPrimitive} to the list of ids which will be fetched from the server.
+     *
+     * @param primitives  the list of primitives (ignored, if null)
+     * @return this
+     * 
+     * @see #append(Node)
+     * @see #append(Way)
+     * @see #append(Relation)
+     * 
+     */
+    public MultiFetchServerObjectReader append(Collection<OsmPrimitive> primitives) {
+        if (primitives == null) return this;
+        for (OsmPrimitive primitive : primitives) {
+            appendGeneric(primitive);
+        }
+        return this;
+    }
+
+    /**
+     * extracts a subset of max {@see #MAX_IDS_PER_REQUEST} ids from <code>ids</code> and
+     * replies the subset. The extracted subset is removed from <code>ids</code>.
+     * 
+     * @param ids a set of ids
+     * @return the subset of ids
+     */
+    protected Set<Long> extractIdPackage(Set<Long> ids) {
+        HashSet<Long> pkg = new HashSet<Long>();
+        if (ids.isEmpty())
+            return pkg;
+        if (ids.size() > MAX_IDS_PER_REQUEST) {
+            Iterator<Long> it = ids.iterator();
+            for (int i =0;i<MAX_IDS_PER_REQUEST;i++) {
+                pkg.add(it.next());
+            }
+            ids.removeAll(pkg);
+        } else {
+            pkg.addAll(ids);
+            ids.clear();
+        }
+        return pkg;
+    }
+
+
+    /**
+     * builds the Multi Get request string for a set of ids and a given
+     * {@see OsmPrimitiveType}.
+     * 
+     * @param type the type
+     * @param idPackage  the package of ids
+     * @return the request string
+     */
+    protected String buildRequestString(OsmPrimitiveType type, Set<Long> idPackage) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(type.getAPIName()).append("s?")
+        .append(type.getAPIName()).append("s=");
+
+        Iterator<Long> it = idPackage.iterator();
+        for (int i=0; i< idPackage.size();i++) {
+            sb.append(it.next());
+            if (i < idPackage.size()-1) {
+                sb.append(",");
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * builds the Multi Get request string for a single id and a given
+     * {@see OsmPrimitiveType}.
+     * 
+     * @param type the type
+     * @param id the id
+     * @return the request string
+     */
+    protected String buildRequestString(OsmPrimitiveType type, long id) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(type.getAPIName()).append("s?")
+        .append(type.getAPIName()).append("s=")
+        .append(id);
+        return sb.toString();
+    }
+
+    /**
+     * invokes a Multi Get for a set of ids and a given {@see OsmPrimitiveType}.
+     * The retrieved primitives are merged to {@see #outputDataSet}.
+     * 
+     * @param type the type
+     * @param pkg the package of ids
+     * @exception OsmTransferException thrown if an error occurs while communicating with the API server
+     * 
+     */
+    protected void multiGetIdPackage(OsmPrimitiveType type, Set<Long> pkg) throws OsmTransferException {
+        String request = buildRequestString(type, pkg);
+        final InputStream in = getInputStream(request, Main.pleaseWaitDlg);
+        if (in == null) return;
+        Main.pleaseWaitDlg.currentAction.setText(tr("Downloading OSM data..."));
+        try {
+            final OsmReader osm = OsmReader.parseDataSetOsm(in, outputDataSet, Main.pleaseWaitDlg);
+            skippedWayIds.addAll(osm.getSkippedWayIds());
+            merge(osm.getDs());
+        } catch(IOException e) {
+            throw new OsmTransferException(e);
+        } catch(SAXException e) {
+            throw new OsmTransferException(e);
+        }
+    }
+
+    /**
+     * invokes a Multi Get for a single id and a given {@see OsmPrimitiveType}.
+     * The retrieved primitive is merged to {@see #outputDataSet}.
+     * 
+     * @param type the type
+     * @param id the id
+     * @exception OsmTransferException thrown if an error occurs while communicating with the API server
+     * 
+     */
+    protected void singleGetId(OsmPrimitiveType type, long id) throws OsmTransferException {
+        String request = buildRequestString(type, id);
+        final InputStream in = getInputStream(request, Main.pleaseWaitDlg);
+        if (in == null)
+            return;
+        Main.pleaseWaitDlg.currentAction.setText(tr("Downloading OSM data..."));
+        try {
+            final OsmReader osm = OsmReader.parseDataSetOsm(in, null, Main.pleaseWaitDlg);
+            skippedWayIds.addAll(osm.getSkippedWayIds());
+            merge(osm.getDs());
+        } catch(IOException e) {
+            throw new OsmTransferException(e);
+        } catch(SAXException e) {
+            throw new OsmTransferException(e);
+        }
+    }
+
+    /**
+     * invokes a sequence of Multi Gets for individual ids in a set of ids and a given {@see OsmPrimitiveType}.
+     * The retrieved primitives are merged to {@see #outputDataSet}.
+     * 
+     * This method is used if one of the ids in pkg doesn't exist (the server replies with return code 404).
+     * If the set is fetched with this method it is possible to find out which of the ids doesn't exist.
+     * Unfortunatelly, the server does not provide an error header or an error body for a 404 reply.
+     * 
+     * @param type the type
+     * @param pkg the set of ids
+     * @exception OsmTransferException thrown if an error occurs while communicating with the API server
+     * 
+     */
+    protected void singleGetIdPackage(OsmPrimitiveType type, Set<Long> pkg) throws OsmTransferException {
+        for (long id : pkg) {
+            try {
+                singleGetId(type, id);
+            } catch(OsmApiException e) {
+                if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+                    logger.warning(tr("Server replied with response code 404 for id {0}. Skipping.", Long.toString(id)));
+                    missingPrimitives.add(id);
+                    continue;
+                }
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * merges the dataset <code>from</code> to {@see #outputDataSet}.
+     * 
+     * @param from the other dataset
+     * 
+     */
+    protected void merge(DataSet from) {
+        final MergeVisitor visitor = new MergeVisitor(outputDataSet,from);
+        visitor.merge();
+    }
+
+    /**
+     * fetches a set of ids of a given {@see OsmPrimitiveType} from the server
+     * 
+     * @param ids the set of ids
+     * @param type the  type
+     * @exception OsmTransferException thrown if an error occurs while communicating with the API server
+     */
+    protected void fetchPrimitives(Set<Long> ids, OsmPrimitiveType type) throws OsmTransferException{
+        Set<Long> toFetch = new HashSet<Long>(ids);
+        toFetch.addAll(ids);
+        while(! toFetch.isEmpty()) {
+            Set<Long> pkg = extractIdPackage(toFetch);
+            try {
+                multiGetIdPackage(type, pkg);
+            } catch(OsmApiException e) {
+                if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+                    logger.warning(tr("Server replied with response code 404, retrying with an individual request for each primitive"));
+                    singleGetIdPackage(type, pkg);
+                } else
+                    throw e;
+            }
+        }
+    }
+
+    /**
+     * invokes one or more Multi Gets to fetch the {@see OsmPrimitive}s and replies
+     * the dataset of retrieved primitives. Note that the dataset includes non visible primitives too!
+     * In contrast to a simple Get for a node, a way, or a relation, a Multi Get always replies
+     * the latest version of the primitive (if any), even if the primitive is not visible (i.e. if
+     * visible==false).
+     * 
+     * Invoke {@see #getMissingPrimitives()} to get a list of primitives which have not been
+     * found on  the server (the server response code was 404)
+     * 
+     * Invoke {@see #getSkippedWay()} to get a list of ways which this reader could not build from
+     * the fetched data because the ways refer to nodes which don't exist on the server.
+     * 
+     * @return the parsed data
+     * @exception OsmTransferException thrown if an error occurs while communicating with the API server
+     * @see #getMissingPrimitives()
+     * @see #getSkippedWays()
+     * 
+
+     */
+    @Override
+    public DataSet parseOsm() throws OsmTransferException {
+        skippedWayIds = new HashSet<Long>();
+        missingPrimitives = new HashSet<Long>();
+
+        fetchPrimitives(nodes,OsmPrimitiveType.NODE);
+        fetchPrimitives(ways,OsmPrimitiveType.WAY);
+        fetchPrimitives(relations,OsmPrimitiveType.RELATION);
+        return outputDataSet;
+    }
+
+    /**
+     * replies the set of {@see Way}s which were present in the data fetched from the
+     * server but which were not included in the JOSM dataset because they referred
+     * to nodes not present in the dataset
+     * 
+     * @return  the set of ids of skipped ways
+     */
+    public Set<Long> getSkippedWays() {
+        return skippedWayIds;
+    }
+
+    /**
+     * replies the set of ids of all primitives for which a fetch request to the
+     * server was submitted but which are not available from the server (the server
+     * replied a return code of 404)
+     * 
+     * @return the set of ids of missing primitives
+     */
+    public Set<Long> getMissingPrimitives() {
+        return missingPrimitives;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 1690)
@@ -4,4 +4,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.EventQueue;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
@@ -323,4 +324,5 @@
         }
         notifyStatusMessage(tr("Uploading..."));
+        setAutoProgressIndication(true);
 
         String diff = duv.getDocument();
@@ -330,4 +332,6 @@
         } catch(Exception e) {
             throw new OsmTransferException(e);
+        } finally {
+            setAutoProgressIndication(false);
         }
 
@@ -481,3 +485,14 @@
         Main.pleaseWaitDlg.progress.setValue(current + delta);
     }
+
+
+    protected void setAutoProgressIndication(final boolean enabled) {
+        EventQueue.invokeLater(
+                new Runnable() {
+                    public void run() {
+                        Main.pleaseWaitDlg.setIndeterminate(enabled);
+                    }
+                }
+        );
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/io/OsmReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmReader.java	(revision 1689)
+++ /trunk/src/org/openstreetmap/josm/io/OsmReader.java	(revision 1690)
@@ -9,7 +9,10 @@
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.Map;
+import java.util.Set;
 import java.util.Map.Entry;
 
@@ -29,5 +32,4 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
-import org.openstreetmap.josm.data.osm.visitor.Visitor;
 import org.openstreetmap.josm.gui.PleaseWaitDialog;
 import org.openstreetmap.josm.tools.DateUtils;
@@ -49,465 +51,481 @@
 public class OsmReader {
 
-//     static long tagsN = 0;
-//     static long nodesN = 0;
-//     static long waysN = 0;
-//     static long relationsN = 0;
-//     static long membersN = 0;
-
-     static InputStream currSource;
-
-     /**
-      * This is used as (readonly) source for finding missing references when not transferred in the
-      * file.
-      */
-     private DataSet references;
-
-     /**
-      * The dataset to add parsed objects to.
-      */
-     private DataSet ds = new DataSet();
-     public DataSet getDs() { return ds; }
-
-     /**
-      * Record warnings.  If there were any data inconsistencies, append
-      * a newline-terminated string.
-      */
-     private String parseNotes = new String();
-     private int parseNotesCount = 0;
-     public String getParseNotes() {
-         return parseNotes;
-     }
-
-     /**
-      * The visitor to use to add the data to the set.
-      */
-     private AddVisitor adder = new AddVisitor(ds);
-
-     /**
-      * All read nodes after phase 1.
-      */
-     private Map<Long, Node> nodes = new HashMap<Long, Node>();
-
-     // TODO: What the hack? Is this really from me? Please, clean this up!
-     private static class OsmPrimitiveData extends OsmPrimitive {
-          @Override public void visit(Visitor visitor) {}
-          public int compareTo(OsmPrimitive o) {return 0;}
-
-          public void copyTo(OsmPrimitive osm) {
-               osm.id = id;
-               osm.keys = keys;
-               osm.modified = modified;
-               osm.selected = selected;
-               osm.deleted = deleted;
-               osm.setTimestamp(getTimestamp());
-               osm.user = user;
-               osm.visible = visible;
-               osm.version = version;
-               osm.mappaintStyle = null;
-          }
-     }
-
-     /**
-      * Used as a temporary storage for relation members, before they
-      * are resolved into pointers to real objects.
-      */
-     private static class RelationMemberData {
-          public String type;
-          public long id;
-          public RelationMember relationMember;
-     }
-
-     /**
-      * Data structure for the remaining way objects
-      */
-     private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
-
-     /**
-      * Data structure for relation objects
-      */
-     private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
-
-     private class Parser extends DefaultHandler {
-          /**
-           * The current osm primitive to be read.
-           */
-          private OsmPrimitive current;
-          private String generator;
-          private Map<String, String> keys = new HashMap<String, String>();
-//          int n = 0;
-
-          @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
-               try {
-//                    if(n%100000 == 0) {
-//                        try {
-//                            FileInputStream fis = (FileInputStream)currSource;
-//                            FileChannel channel = fis.getChannel();
-//                            double perc = (((double)channel.position()) / ((double)channel.size()) * 100.0);
-//                            System.out.format(" " + (int)perc + "%%");
-//                        }
-//                        catch(java.lang.ClassCastException cce) {
-//                        }
-//                        catch(IOException e) {
-//                            System.out.format("Error reading file position " + e);
-//                        }
-//                    }
-//                    n++;
-
-                    if (qName.equals("osm")) {
-                         if (atts == null)
-                              throw new SAXException(tr("Unknown version"));
-                         String v = atts.getValue("version");
-                         if (v == null)
-                             throw new SAXException(tr("Version number missing from OSM data"));
-                         if (!(v.equals("0.5") || v.equals("0.6")))
-                             throw new SAXException(tr("Unknown version: {0}", v));
-                         // save generator attribute for later use when creating DataSource objects
-                         generator = atts.getValue("generator");
-                         ds.version = v;
-
-                    } else if (qName.equals("bounds")) {
-                         // new style bounds.
-                         String minlon = atts.getValue("minlon");
-                         String minlat = atts.getValue("minlat");
-                         String maxlon = atts.getValue("maxlon");
-                         String maxlat = atts.getValue("maxlat");
-                         String origin = atts.getValue("origin");
-                         if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
-                              if (origin == null) origin = generator;
-                              Bounds bounds = new Bounds(
-                                  new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
-                                  new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
-                              DataSource src = new DataSource(bounds, origin);
-                              ds.dataSources.add(src);
-                         }
+    /**
+     * This is used as (readonly) source for finding missing references when not transferred in the
+     * file.
+     */
+    private DataSet references;
+
+    /**
+     * The dataset to add parsed objects to.
+     */
+    private DataSet ds = new DataSet();
+    public DataSet getDs() { return ds; }
+
+    /**
+     * Record warnings.  If there were any data inconsistencies, append
+     * a newline-terminated string.
+     */
+    private String parseNotes = new String();
+    private int parseNotesCount = 0;
+    public String getParseNotes() {
+        return parseNotes;
+    }
+
+    /** the list of ids of skipped {@see Way}s, i.e. ways which referred to nodes
+     * not included in the parsed data
+     */
+    private Set<Long> skippedWayIds = new HashSet<Long>();
+
+    /**
+     * The visitor to use to add the data to the set.
+     */
+    private AddVisitor adder = new AddVisitor(ds);
+
+    /**
+     * All read nodes after phase 1.
+     */
+    private Map<Long, Node> nodes = new HashMap<Long, Node>();
+
+
+    private static class OsmPrimitiveData {
+        public long id = 0;
+        public Map<String,String> keys = new HashMap<String, String>();
+        public boolean modified = false;
+        public boolean selected = false;
+        public boolean deleted = false;
+        public Date timestamp = new Date();
+        public User user = null;
+        public boolean visible = true;
+        public int version = -1;
+        public LatLon latlon = new LatLon(0,0);
+
+        public void copyTo(OsmPrimitive osm) {
+            osm.id = id;
+            osm.keys = keys;
+            osm.modified = modified;
+            osm.selected = selected;
+            osm.deleted = deleted;
+            osm.setTimestamp(timestamp);
+            osm.user = user;
+            osm.visible = visible;
+            osm.version = version;
+            osm.mappaintStyle = null;
+        }
+
+        public Node createNode() {
+            Node node = new Node(latlon);
+            copyTo(node);
+            return node;
+        }
+
+        public Way createWay() {
+            Way way = new Way(id);
+            copyTo(way);
+            return way;
+        }
+
+        public Relation createRelation() {
+            Relation rel = new Relation(id);
+            copyTo(rel);
+            return rel;
+        }
+    }
+
+    /**
+     * Used as a temporary storage for relation members, before they
+     * are resolved into pointers to real objects.
+     */
+    private static class RelationMemberData {
+        public String type;
+        public long id;
+        public RelationMember relationMember;
+    }
+
+    /**
+     * Data structure for the remaining way objects
+     */
+    private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
+
+    /**
+     * Data structure for relation objects
+     */
+    private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
+
+    private class Parser extends DefaultHandler {
+        /**
+         * The current osm primitive to be read.
+         */
+        private OsmPrimitiveData current;
+        private String generator;
+
+        @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+            try {
+                if (qName.equals("osm")) {
+                    if (atts == null)
+                        throw new SAXException(tr("Unknown version"));
+                    String v = atts.getValue("version");
+                    if (v == null)
+                        throw new SAXException(tr("Version number missing from OSM data"));
+                    if (!(v.equals("0.5") || v.equals("0.6")))
+                        throw new SAXException(tr("Unknown version: {0}", v));
+                    // save generator attribute for later use when creating DataSource objects
+                    generator = atts.getValue("generator");
+                    ds.version = v;
+
+                } else if (qName.equals("bounds")) {
+                    // new style bounds.
+                    String minlon = atts.getValue("minlon");
+                    String minlat = atts.getValue("minlat");
+                    String maxlon = atts.getValue("maxlon");
+                    String maxlat = atts.getValue("maxlat");
+                    String origin = atts.getValue("origin");
+                    if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
+                        if (origin == null) {
+                            origin = generator;
+                        }
+                        Bounds bounds = new Bounds(
+                                new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
+                                new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
+                        DataSource src = new DataSource(bounds, origin);
+                        ds.dataSources.add(src);
+                    }
 
                     // ---- PARSING NODES AND WAYS ----
 
-                    } else if (qName.equals("node")) {
-//                         nodesN++;
-                         current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
-                         readCommon(atts, current);
-                         nodes.put(current.id, (Node)current);
-                    } else if (qName.equals("way")) {
-//                         waysN++;
-                         current = new OsmPrimitiveData();
-                         readCommon(atts, current);
-                         ways.put((OsmPrimitiveData)current, new ArrayList<Long>());
-                    } else if (qName.equals("nd")) {
-                         Collection<Long> list = ways.get(current);
-                         if (list == null)
-                              throw new SAXException(tr("Found <nd> element in non-way."));
-                         long id = getLong(atts, "ref");
-                         if (id == 0)
-                              throw new SAXException(tr("<nd> has zero ref"));
-                         list.add(id);
+                } else if (qName.equals("node")) {
+                    current = new OsmPrimitiveData();
+                    current.latlon = new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon"));
+                    readCommon(atts, current);
+                } else if (qName.equals("way")) {
+                    current = new OsmPrimitiveData();
+                    readCommon(atts, current);
+                    ways.put(current, new ArrayList<Long>());
+                } else if (qName.equals("nd")) {
+                    Collection<Long> list = ways.get(current);
+                    if (list == null)
+                        throw new SAXException(tr("Found <nd> element in non-way."));
+                    long id = getLong(atts, "ref");
+                    if (id == 0)
+                        throw new SAXException(tr("<nd> has zero ref"));
+                    list.add(id);
 
                     // ---- PARSING RELATIONS ----
 
-                    } else if (qName.equals("relation")) {
-                         current = new OsmPrimitiveData();
-                         readCommon(atts, current);
-                         relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>());
-                    } else if (qName.equals("member")) {
-                         Collection<RelationMemberData> list = relations.get(current);
-                         if (list == null)
-                              throw new SAXException(tr("Found <member> element in non-relation."));
-                         RelationMemberData emd = new RelationMemberData();
-                         emd.relationMember = new RelationMember();
-                         String value = atts.getValue("ref");
-                         if (value == null) {
-                             throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id));
-                         }
-                         try {
-                             emd.id = Long.parseLong(value);
-                         } catch(NumberFormatException e) {
-                             throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value));
-                         }
-                         value = atts.getValue("type");
-                         if (value == null) {
-                             throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id)));
-                         }
-                         if (! (value.equals("way") || value.equals("node") || value.equals("relation"))) {
-                             throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value));
-                         }
-                         emd.type= value;
-                         value = atts.getValue("role");
-                         emd.relationMember.role = value;
-
-                         if (emd.id == 0)
-                              throw new SAXException(tr("Incomplete <member> specification with ref=0"));
-
-                         list.add(emd);
+                } else if (qName.equals("relation")) {
+                    current = new OsmPrimitiveData();
+                    readCommon(atts, current);
+                    relations.put(current, new LinkedList<RelationMemberData>());
+                } else if (qName.equals("member")) {
+                    Collection<RelationMemberData> list = relations.get(current);
+                    if (list == null)
+                        throw new SAXException(tr("Found <member> element in non-relation."));
+                    RelationMemberData emd = new RelationMemberData();
+                    emd.relationMember = new RelationMember();
+                    String value = atts.getValue("ref");
+                    if (value == null)
+                        throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id));
+                    try {
+                        emd.id = Long.parseLong(value);
+                    } catch(NumberFormatException e) {
+                        throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value));
+                    }
+                    value = atts.getValue("type");
+                    if (value == null)
+                        throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id)));
+                    if (! (value.equals("way") || value.equals("node") || value.equals("relation")))
+                        throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value));
+                    emd.type= value;
+                    value = atts.getValue("role");
+                    emd.relationMember.role = value;
+
+                    if (emd.id == 0)
+                        throw new SAXException(tr("Incomplete <member> specification with ref=0"));
+
+                    list.add(emd);
 
                     // ---- PARSING TAGS (applicable to all objects) ----
 
-                    } else if (qName.equals("tag")) {
-//                         tagsN++;
-                        String key = atts.getValue("k");
-                        String internedKey = keys.get(key);
-                        if (internedKey == null) {
-                            internedKey = key;
-                            keys.put(key, key);
-                        }
-                         current.put(internedKey, atts.getValue("v"));
-                    }
-               } catch (NumberFormatException x) {
-                    x.printStackTrace(); // SAXException does not chain correctly
-                    throw new SAXException(x.getMessage(), x);
-               } catch (NullPointerException x) {
-                    x.printStackTrace(); // SAXException does not chain correctly
-                    throw new SAXException(tr("NullPointerException, possibly some missing tags."), x);
-               }
-          }
-
-          private double getDouble(Attributes atts, String value) {
-               return Double.parseDouble(atts.getValue(value));
-          }
-     }
-
-     /**
-      * Read out the common attributes from atts and put them into this.current.
-      */
-     void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
-          current.id = getLong(atts, "id");
-          if (current.id == 0)
-               throw new SAXException(tr("Illegal object with id=0"));
-
-          String time = atts.getValue("timestamp");
-          if (time != null && time.length() != 0) {
-               current.setTimestamp(DateUtils.fromString(time));
-          }
-
-          // user attribute added in 0.4 API
-          String user = atts.getValue("user");
-          if (user != null) {
-               // do not store literally; get object reference for string
-               current.user = User.get(user);
-          }
-
-          // uid attribute added in 0.6 API
-          String uid = atts.getValue("uid");
-          if (uid != null) {
-              if (current.user != null) {
-                  current.user.uid = uid;
-              }
-         }
-
-          // visible attribute added in 0.4 API
-          String visible = atts.getValue("visible");
-          if (visible != null) {
-               current.visible = Boolean.parseBoolean(visible);
-          }
-
-          String version = atts.getValue("version");
-          current.version = 0;
-          if (version != null) {
-              try {
-                  current.version = Integer.parseInt(version);
-              } catch(NumberFormatException e) {
-                  throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version));
-              }
-          } else {
-              // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
-              //
-              if (current.id > 0 && ds.version != null && ds.version.equals("0.6")) {
-                  throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id)));
-              }
-          }
-
-          String action = atts.getValue("action");
-          if (action == null)
-               return;
-          if (action.equals("delete"))
-               current.delete(true);
-          else if (action.startsWith("modify"))
-               current.modified = true;
-     }
-     private long getLong(Attributes atts, String value) throws SAXException {
-          String s = atts.getValue(value);
-          if (s == null)
-               throw new SAXException(tr("Missing required attribute \"{0}\".",value));
-          return Long.parseLong(s);
-     }
-
-     private Node findNode(long id) {
-         Node n = nodes.get(id);
-         if (n != null)
-              return n;
-         for (Node node : references.nodes)
-              if (node.id == id)
-                   return node;
-         // TODO: This has to be changed to support multiple layers.
-         for (Node node : Main.ds.nodes)
-              if (node.id == id)
-                   return new Node(node);
-         return null;
-    }
-
-     private void createWays() {
-          for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
-               Way w = new Way();
-               boolean failed = false;
-               for (long id : e.getValue()) {
-                    Node n = findNode(id);
-                    if (n == null) {
-                         /* don't report ALL of them, just a few */
-                         if (parseNotesCount++ < 6) {
-                             parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id);
-                         } else if (parseNotesCount == 6) {
-                             parseNotes += "...\n";
-                         }
-                         failed = true;
-                         break;
-                    }
-                    w.nodes.add(n);
-               }
-               if (failed) continue;
-               e.getKey().copyTo(w);
-               adder.visit(w);
-          }
-
-     }
-
-     /**
-      * Return the Way object with the given id, or null if it doesn't
-      * exist yet. This method only looks at ways stored in the data set.
-      *
-      * @param id
-      * @return way object or null
-      */
-     private Way findWay(long id) {
-          for (Way wy : Main.ds.ways)
-               if (wy.id == id)
-                    return wy;
-          return null;
-     }
-
-     /**
-      * Return the Relation object with the given id, or null if it doesn't
-      * exist yet. This method only looks at relations stored in the data set.
-      *
-      * @param id
-      * @return relation object or null
-      */
-     private Relation findRelation(long id) {
-          for (Relation e : ds.relations)
-               if (e.id == id)
-                    return e;
-          for (Relation e : Main.ds.relations)
-               if (e.id == id)
-                    return e;
-          return null;
-     }
-
-     /**
-      * Create relations. This is slightly different than n/s/w because
-      * unlike other objects, relations may reference other relations; it
-      * is not guaranteed that a referenced relation will have been created
-      * before it is referenced. So we have to create all relations first,
-      * and populate them later.
-      */
-     private void createRelations() {
-
-          // pass 1 - create all relations
-          for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
-               Relation en = new Relation();
-               e.getKey().copyTo(en);
-               adder.visit(en);
-          }
-
-          // Cache the ways here for much better search performance
-          HashMap<Long, Way> hm = new HashMap<Long, Way>(10000);
-          for (Way wy : ds.ways)
+                } else if (qName.equals("tag")) {
+                    String key = atts.getValue("k");
+                    String value = atts.getValue("v");
+                    current.keys.put(key,value);
+                }
+            } catch (NumberFormatException x) {
+                x.printStackTrace(); // SAXException does not chain correctly
+                throw new SAXException(x.getMessage(), x);
+            } catch (NullPointerException x) {
+                x.printStackTrace(); // SAXException does not chain correctly
+                throw new SAXException(tr("NullPointerException, possibly some missing tags."), x);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (qName.equals("node")) {
+                nodes.put(current.id, current.createNode());
+            }
+        }
+
+        private double getDouble(Attributes atts, String value) {
+            return Double.parseDouble(atts.getValue(value));
+        }
+    }
+
+    /**
+     * Read out the common attributes from atts and put them into this.current.
+     */
+    void readCommon(Attributes atts, OsmPrimitiveData current) throws SAXException {
+        current.id = getLong(atts, "id");
+        if (current.id == 0)
+            throw new SAXException(tr("Illegal object with id=0"));
+
+        String time = atts.getValue("timestamp");
+        if (time != null && time.length() != 0) {
+            current.timestamp =  DateUtils.fromString(time);
+        }
+
+        // user attribute added in 0.4 API
+        String user = atts.getValue("user");
+        if (user != null) {
+            // do not store literally; get object reference for string
+            current.user = User.get(user);
+        }
+
+        // uid attribute added in 0.6 API
+        String uid = atts.getValue("uid");
+        if (uid != null) {
+            if (current.user != null) {
+                current.user.uid = uid;
+            }
+        }
+
+        // visible attribute added in 0.4 API
+        String visible = atts.getValue("visible");
+        if (visible != null) {
+            current.visible = Boolean.parseBoolean(visible);
+        }
+
+        String version = atts.getValue("version");
+        current.version = 0;
+        if (version != null) {
+            try {
+                current.version = Integer.parseInt(version);
+            } catch(NumberFormatException e) {
+                throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version));
+            }
+        } else {
+            // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
+            //
+            if (current.id > 0 && ds.version != null && ds.version.equals("0.6"))
+                throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id)));
+        }
+
+        String action = atts.getValue("action");
+        if (action == null)
+            return;
+        if (action.equals("delete")) {
+            current.deleted = true;
+        } else if (action.startsWith("modify")) {
+            current.modified = true;
+        }
+    }
+    private long getLong(Attributes atts, String value) throws SAXException {
+        String s = atts.getValue(value);
+        if (s == null)
+            throw new SAXException(tr("Missing required attribute \"{0}\".",value));
+        return Long.parseLong(s);
+    }
+
+    private Node findNode(long id) {
+        Node n = nodes.get(id);
+        if (n != null)
+            return n;
+        for (Node node : references.nodes)
+            if (node.id == id)
+                return node;
+        // TODO: This has to be changed to support multiple layers.
+        for (Node node : Main.ds.nodes)
+            if (node.id == id)
+                return new Node(node);
+        return null;
+    }
+
+    protected void createWays() {
+        for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
+            Way w = new Way();
+            boolean failed = false;
+            for (long id : e.getValue()) {
+                Node n = findNode(id);
+                if (n == null) {
+                    /* don't report ALL of them, just a few */
+                    if (parseNotesCount++ < 6) {
+                        parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id);
+                    } else if (parseNotesCount == 6) {
+                        parseNotes += "...\n";
+                    }
+                    failed = true;
+                    break;
+                }
+                w.nodes.add(n);
+            }
+            if (failed) {
+                skippedWayIds.add(e.getKey().id);
+                continue;
+            }
+            e.getKey().copyTo(w);
+            adder.visit(w);
+        }
+
+    }
+
+    /**
+     * Return the Way object with the given id, or null if it doesn't
+     * exist yet. This method only looks at ways stored in the data set.
+     *
+     * @param id
+     * @return way object or null
+     */
+    private Way findWay(long id) {
+        for (Way wy : Main.ds.ways)
+            if (wy.id == id)
+                return wy;
+        return null;
+    }
+
+    /**
+     * Return the Relation object with the given id, or null if it doesn't
+     * exist yet. This method only looks at relations stored in the data set.
+     *
+     * @param id
+     * @return relation object or null
+     */
+    private Relation findRelation(long id) {
+        for (Relation e : ds.relations)
+            if (e.id == id)
+                return e;
+        for (Relation e : Main.ds.relations)
+            if (e.id == id)
+                return e;
+        return null;
+    }
+
+    /**
+     * Create relations. This is slightly different than n/s/w because
+     * unlike other objects, relations may reference other relations; it
+     * is not guaranteed that a referenced relation will have been created
+     * before it is referenced. So we have to create all relations first,
+     * and populate them later.
+     */
+    private void createRelations() {
+
+        // pass 1 - create all relations
+        for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
+            Relation en = new Relation();
+            e.getKey().copyTo(en);
+            adder.visit(en);
+        }
+
+        // Cache the ways here for much better search performance
+        HashMap<Long, Way> hm = new HashMap<Long, Way>(10000);
+        for (Way wy : ds.ways) {
             hm.put(wy.id, wy);
-
-          // pass 2 - sort out members
-          for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
-               Relation en = findRelation(e.getKey().id);
-               if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
-
-               for (RelationMemberData emd : e.getValue()) {
-                    RelationMember em = emd.relationMember;
-                    if (emd.type.equals("node")) {
-                         em.member = findNode(emd.id);
-                         if (em.member == null) {
-                              em.member = new Node(emd.id);
-                              adder.visit((Node)em.member);
-                         }
-                    } else if (emd.type.equals("way")) {
-                         em.member = hm.get(emd.id);
-                         if (em.member == null)
-                            em.member = findWay(emd.id);
-                         if (em.member == null) {
-                              em.member = new Way(emd.id);
-                              adder.visit((Way)em.member);
-                         }
-                    } else if (emd.type.equals("relation")) {
-                         em.member = findRelation(emd.id);
-                         if (em.member == null) {
-                              em.member = new Relation(emd.id);
-                              adder.visit((Relation)em.member);
-                         }
-                    } else {
-                         // this is an error.
-                    }
-                    en.members.add(em);
-               }
-          }
-          hm = null;
-     }
-
-     /**
-      * Parse the given input source and return the dataset.
-      * @param ref The dataset that is search in for references first. If
-      *      the Reference is not found here, Main.ds is searched and a copy of the
-      *  element found there is returned.
-      */
-     public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
-          return parseDataSetOsm(source, ref, pleaseWaitDlg).ds;
-     }
-
-     public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
-          OsmReader osm = new OsmReader();
-          osm.references = ref == null ? new DataSet() : ref;
-
-          currSource = source;
-
-          // phase 1: Parse nodes and read in raw ways
-          InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
-          try {
-             SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
-          } catch (ParserConfigurationException e1) {
-             e1.printStackTrace(); // broken SAXException chaining
-             throw new SAXException(e1);
-          }
-
-          Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data..."));
-          Main.pleaseWaitDlg.setIndeterminate(true);
-
-//          System.out.println("Parser finished: Tags " + tagsN + " Nodes " + nodesN + " Ways " + waysN +
-//            " Relations " + relationsN + " Members " + membersN);
-
-          for (Node n : osm.nodes.values())
-               osm.adder.visit(n);
-
-          try {
-               osm.createWays();
-               osm.createRelations();
-          } catch (NumberFormatException e) {
-               e.printStackTrace();
-               throw new SAXException(tr("Ill-formed node id"));
-          }
-
-          // clear all negative ids (new to this file)
-          for (OsmPrimitive o : osm.ds.allPrimitives())
-               if (o.id < 0)
-                    o.id = 0;
-
-//          System.out.println("Data loaded!");
-          Main.pleaseWaitDlg.setIndeterminate(false);
-          Main.pleaseWaitDlg.progress.setValue(0);
-
-          return osm;
-     }
+        }
+
+        // pass 2 - sort out members
+        for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
+            Relation en = findRelation(e.getKey().id);
+            if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
+
+            for (RelationMemberData emd : e.getValue()) {
+                RelationMember em = emd.relationMember;
+                if (emd.type.equals("node")) {
+                    em.member = findNode(emd.id);
+                    if (em.member == null) {
+                        em.member = new Node(emd.id);
+                        adder.visit((Node)em.member);
+                    }
+                } else if (emd.type.equals("way")) {
+                    em.member = hm.get(emd.id);
+                    if (em.member == null) {
+                        em.member = findWay(emd.id);
+                    }
+                    if (em.member == null) {
+                        em.member = new Way(emd.id);
+                        adder.visit((Way)em.member);
+                    }
+                } else if (emd.type.equals("relation")) {
+                    em.member = findRelation(emd.id);
+                    if (em.member == null) {
+                        em.member = new Relation(emd.id);
+                        adder.visit((Relation)em.member);
+                    }
+                } else {
+                    // this is an error.
+                }
+                en.members.add(em);
+            }
+        }
+        hm = null;
+    }
+
+    /**
+     * Parse the given input source and return the dataset.
+     * @param ref The dataset that is search in for references first. If
+     *      the Reference is not found here, Main.ds is searched and a copy of the
+     *  element found there is returned.
+     */
+    public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
+        return parseDataSetOsm(source, ref, pleaseWaitDlg).ds;
+    }
+
+    public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
+        OsmReader osm = new OsmReader();
+        osm.references = ref == null ? new DataSet() : ref;
+
+        // phase 1: Parse nodes and read in raw ways
+        InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
+        try {
+            SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
+        } catch (ParserConfigurationException e1) {
+            e1.printStackTrace(); // broken SAXException chaining
+            throw new SAXException(e1);
+        }
+
+        Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data..."));
+        Main.pleaseWaitDlg.setIndeterminate(true);
+
+        for (Node n : osm.nodes.values()) {
+            osm.adder.visit(n);
+        }
+
+        try {
+            osm.createWays();
+            osm.createRelations();
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+            throw new SAXException(tr("Ill-formed node id"));
+        }
+
+        // clear all negative ids (new to this file)
+        for (OsmPrimitive o : osm.ds.allPrimitives())
+            if (o.id < 0) {
+                o.id = 0;
+            }
+
+        Main.pleaseWaitDlg.setIndeterminate(false);
+        Main.pleaseWaitDlg.progress.setValue(0);
+
+        return osm;
+    }
+
+    /**
+     * replies a set of ids of skipped {@see Way}s, i.e. ways which were included in the downloaded
+     * data but which referred to nodes <strong>not</strong>  available in the downloaded data
+     * 
+     * @return the set of ids
+     */
+    public Set<Long> getSkippedWayIds() {
+        return skippedWayIds;
+    }
 }
