Index: /trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/Main.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/Main.java	(revision 2070)
@@ -58,4 +58,5 @@
 import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
+import org.openstreetmap.josm.io.IllegalDataException;
 import org.openstreetmap.josm.plugins.PluginHandler;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -503,4 +504,12 @@
                     OpenFileAction.openFile(f);
                 }
+            } catch(IllegalDataException e) {
+                e.printStackTrace();
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        tr("<html>Could not read file ''{0}\''.<br> Error is: <br>{1}</html>", f.getName(), e.getMessage()),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE
+                );
             }catch(IOException e) {
                 e.printStackTrace();
@@ -527,4 +536,12 @@
         try {
             OpenFileAction.openFile(f);
+        }catch(IllegalDataException e) {
+            e.printStackTrace();
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("<html>Could not read file ''{0}\''.<br> Error is: <br>{1}</html>", f.getName(), e.getMessage()),
+                    tr("Error"),
+                    JOptionPane.ERROR_MESSAGE
+            );
         }catch(IOException e) {
             e.printStackTrace();
Index: /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 2070)
@@ -4,5 +4,4 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
@@ -13,16 +12,10 @@
 import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.Map.Entry;
-
-import javax.swing.Box;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
+import java.util.Stack;
+
 import javax.swing.JOptionPane;
-import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
@@ -31,12 +24,14 @@
 import org.openstreetmap.josm.command.DeleteCommand;
 import org.openstreetmap.josm.command.SequenceCommand;
+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.TigerUtils;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.TagCollection;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.gui.conflict.tags.CombineWaysConflictResolverDialog;
 import org.openstreetmap.josm.tools.Pair;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -45,5 +40,4 @@
  * Combines multiple ways into one.
  *
- * @author Imi
  */
 public class CombineWayAction extends JosmAction {
@@ -54,16 +48,168 @@
     }
 
-    @SuppressWarnings("unchecked")
+    protected Set<OsmPrimitive> intersect(Set<? extends OsmPrimitive> s1, Set<? extends OsmPrimitive> s2) {
+        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>(s1);
+        ret.retainAll(s2);
+        return ret;
+    }
+
+    protected boolean confirmCombiningWithConflictsInRelationMemberships() {
+        ExtendedDialog ed = new ExtendedDialog(Main.parent,
+                tr("Combine ways with different memberships?"),
+                new String[] {tr("Combine Anyway"), tr("Cancel")});
+        ed.setButtonIcons(new String[] {"combineway.png", "cancel.png"});
+        ed.setContent(tr("The selected ways have differing relation memberships.  "
+                + "Do you still want to combine them?"));
+        ed.showDialog();
+
+        return ed.getValue() == 1;
+    }
+
+    protected boolean confirmChangeDirectionOfWays() {
+        ExtendedDialog ed = new ExtendedDialog(Main.parent,
+                tr("Change directions?"),
+                new String[] {tr("Reverse and Combine"), tr("Cancel")});
+        ed.setButtonIcons(new String[] {"wayflip.png", "cancel.png"});
+        ed.setContent(tr("The ways can not be combined in their current directions.  "
+                + "Do you want to reverse some of them?"));
+        ed.showDialog();
+        return ed.getValue() == 1;
+    }
+
+    protected void warnCombiningImpossible() {
+        String msg = tr("Could not combine ways "
+                + "(They could not be merged into a single string of nodes)");
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                msg,  //FIXME: not sure whether this fits in a dialog
+                tr("Information"),
+                JOptionPane.INFORMATION_MESSAGE
+        );
+        return;
+    }
+
+    protected Way getTargetWay(Collection<Way> combinedWays) {
+        // init with an arbitrary way
+        Way targetWay = combinedWays.iterator().next();
+
+        // look for the first way already existing on
+        // the server
+        for (Way w : combinedWays) {
+            targetWay = w;
+            if (w.getId() != 0) {
+                break;
+            }
+        }
+        return targetWay;
+    }
+
+    protected void completeTagCollectionWithMissingTags(TagCollection tc, Collection<Way> combinedWays) {
+        for (String key: tc.getKeys()) {
+            // make sure the empty value is in the tag set such that we can delete the tag
+            // in the conflict dialog if necessary
+            //
+            tc.add(new Tag(key,""));
+            for (Way w: combinedWays) {
+                if (w.get(key) == null) {
+                    tc.add(new Tag(key)); // add a tag with key and empty value
+                }
+            }
+        }
+        // remove irrelevant tags
+        //
+        tc.removeByKey("created_by");
+    }
+
+    public void combineWays(Collection<Way> ways) {
+
+        // prepare and clean the list of ways to combine
+        //
+        if (ways == null || ways.isEmpty())
+            return;
+        ways.remove(null); // just in case -  remove all null ways from the collection
+        ways = new HashSet<Way>(ways); // remove duplicates
+
+        // build the list of relations referring to the ways to combine
+        //
+        WayReferringRelations referringRelations = new WayReferringRelations(ways);
+        referringRelations.build(getCurrentDataSet());
+
+        // build the collection of tags used by the ways to combine
+        //
+        TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
+        completeTagCollectionWithMissingTags(wayTags, ways);
+
+        // try to build a new way out of the combination of ways
+        // which are combined
+        //
+        NodeGraph graph = NodeGraph.createDirectedGraphFromWays(ways);
+        List<Node> path = graph.buildSpanningPath();
+        if (path == null) {
+            graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
+            path = graph.buildSpanningPath();
+            if (path != null) {
+                if (!confirmChangeDirectionOfWays())
+                    return;
+            } else {
+                warnCombiningImpossible();
+                return;
+            }
+        }
+
+        // create the new way and apply the new node list
+        //
+        Way targetWay = getTargetWay(ways);
+        Way modifiedTargetWay = new Way(targetWay);
+        modifiedTargetWay.setNodes(path);
+
+        CombineWaysConflictResolverDialog dialog = CombineWaysConflictResolverDialog.getInstance();
+        dialog.getTagConflictResolverModel().populate(wayTags);
+        dialog.setTargetWay(targetWay);
+        dialog.getRelationMemberConflictResolverModel().populate(
+                referringRelations.getRelations(),
+                referringRelations.getWays()
+        );
+        dialog.prepareDefaultDecisions();
+
+        // resolve tag conflicts if necessary
+        //
+        if (!wayTags.isApplicableToPrimitive() || !referringRelations.getRelations().isEmpty()) {
+            dialog.setVisible(true);
+            if (dialog.isCancelled())
+                return;
+        }
+
+
+
+        LinkedList<Command> cmds = new LinkedList<Command>();
+        LinkedList<Way> deletedWays = new LinkedList<Way>(ways);
+        deletedWays.remove(targetWay);
+
+        cmds.add(new DeleteCommand(deletedWays));
+        cmds.add(new ChangeCommand(targetWay, modifiedTargetWay));
+        cmds.addAll(dialog.buildResolutionCommands(targetWay));
+        final SequenceCommand sequenceCommand = new SequenceCommand(tr("Combine {0} ways", ways.size()), cmds);
+
+        // update gui
+        final Way selectedWay = targetWay;
+        Runnable guiTask = new Runnable() {
+            public void run() {
+                Main.main.undoRedo.add(sequenceCommand);
+                getCurrentDataSet().setSelected(selectedWay);
+            }
+        };
+        if (SwingUtilities.isEventDispatchThread()) {
+            guiTask.run();
+        } else {
+            SwingUtilities.invokeLater(guiTask);
+        }
+    }
+
+
     public void actionPerformed(ActionEvent event) {
         if (getCurrentDataSet() == null)
             return;
         Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
-        LinkedList<Way> selectedWays = new LinkedList<Way>();
-
-        for (OsmPrimitive osm : selection)
-            if (osm instanceof Way) {
-                selectedWays.add((Way)osm);
-            }
-
+        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(selection, Way.class);
         if (selectedWays.size() < 2) {
             JOptionPane.showMessageDialog(
@@ -75,240 +221,5 @@
             return;
         }
-
-        // Check whether all ways have identical relationship membership. More
-        // specifically: If one of the selected ways is a member of relation X
-        // in role Y, then all selected ways must be members of X in role Y.
-
-        // FIXME: In a later revision, we should display some sort of conflict
-        // dialog like we do for tags, to let the user choose which relations
-        // should be kept.
-
-        // Step 1, iterate over all relations and figure out which of our
-        // selected ways are members of a relation.
-        HashMap<Pair<Relation,String>, HashSet<Way>> backlinks =
-            new HashMap<Pair<Relation,String>, HashSet<Way>>();
-        HashSet<Relation> relationsUsingWays = new HashSet<Relation>();
-        for (Relation r : getCurrentDataSet().relations) {
-            if (r.isDeleted() || r.incomplete) {
-                continue;
-            }
-            for (RelationMember rm : r.getMembers()) {
-                if (rm.isWay()) {
-                    for(Way w : selectedWays) {
-                        if (rm.getMember() == w) {
-                            Pair<Relation,String> pair = new Pair<Relation,String>(r, rm.getRole());
-                            HashSet<Way> waylinks = new HashSet<Way>();
-                            if (backlinks.containsKey(pair)) {
-                                waylinks = backlinks.get(pair);
-                            } else {
-                                waylinks = new HashSet<Way>();
-                                backlinks.put(pair, waylinks);
-                            }
-                            waylinks.add(w);
-
-                            // this is just a cache for later use
-                            relationsUsingWays.add(r);
-                        }
-                    }
-                }
-            }
-        }
-
-        // Complain to the user if the ways don't have equal memberships.
-        for (HashSet<Way> waylinks : backlinks.values()) {
-            if (!waylinks.containsAll(selectedWays)) {
-
-                ExtendedDialog ed = new ExtendedDialog(Main.parent,
-                        tr("Combine ways with different memberships?"),
-                        new String[] {tr("Combine Anyway"), tr("Cancel")});
-                ed.setButtonIcons(new String[] {"combineway.png", "cancel.png"});
-                ed.setContent(tr("The selected ways have differing relation memberships.  "
-                        + "Do you still want to combine them?"));
-                ed.showDialog();
-
-                if (ed.getValue() == 1) {
-                    break;
-                }
-
-                return;
-            }
-        }
-
-        // collect properties for later conflict resolving
-        Map<String, Set<String>> props = new TreeMap<String, Set<String>>();
-        for (Way w : selectedWays) {
-            for (Entry<String,String> e : w.entrySet()) {
-                if (!props.containsKey(e.getKey())) {
-                    props.put(e.getKey(), new TreeSet<String>());
-                }
-                props.get(e.getKey()).add(e.getValue());
-            }
-        }
-
-        List<Node> nodeList = null;
-        Object firstTry = actuallyCombineWays(selectedWays, false);
-        if (firstTry instanceof List<?>) {
-            nodeList = (List<Node>) firstTry;
-        } else {
-            Object secondTry = actuallyCombineWays(selectedWays, true);
-            if (secondTry instanceof List<?>) {
-                ExtendedDialog ed = new ExtendedDialog(Main.parent,
-                        tr("Change directions?"),
-                        new String[] {tr("Reverse and Combine"), tr("Cancel")});
-                ed.setButtonIcons(new String[] {"wayflip.png", "cancel.png"});
-                ed.setContent(tr("The ways can not be combined in their current directions.  "
-                        + "Do you want to reverse some of them?"));
-                ed.showDialog();
-                if (ed.getValue() != 1) return;
-
-                nodeList = (List<Node>) secondTry;
-            } else {
-                JOptionPane.showMessageDialog(
-                        Main.parent,
-                        secondTry, // FIXME: not sure whether this fits in a dialog
-                        tr("Information"),
-                        JOptionPane.INFORMATION_MESSAGE
-                );
-                return;
-            }
-        }
-
-        // Find the most appropriate way to modify.
-
-        // Eventually this might want to be the way with the longest
-        // history or the longest selected way but for now just attempt
-        // to reuse an existing id.
-        Way modifyWay = selectedWays.peek();
-        for (Way w : selectedWays) {
-            modifyWay = w;
-            if (w.getId() != 0) {
-                break;
-            }
-        }
-        Way newWay = new Way(modifyWay);
-
-        newWay.setNodes(nodeList);
-
-        // display conflict dialog
-        Map<String, JComboBox> components = new HashMap<String, JComboBox>();
-        JPanel p = new JPanel(new GridBagLayout());
-        for (Entry<String, Set<String>> e : props.entrySet()) {
-            if (TigerUtils.isTigerTag(e.getKey())) {
-                String combined = TigerUtils.combineTags(e.getKey(), e.getValue());
-                newWay.put(e.getKey(), combined);
-            } else if (e.getValue().size() > 1) {
-                JComboBox c = new JComboBox(e.getValue().toArray());
-                c.setEditable(true);
-                p.add(new JLabel(e.getKey()), GBC.std());
-                p.add(Box.createHorizontalStrut(10), GBC.std());
-                p.add(c, GBC.eol());
-                components.put(e.getKey(), c);
-            } else {
-                newWay.put(e.getKey(), e.getValue().iterator().next());
-            }
-        }
-
-        if (!components.isEmpty()) {
-
-            ExtendedDialog ed = new ExtendedDialog(Main.parent,
-                    tr("Enter values for all conflicts."),
-                    new String[] {tr("Solve Conflicts"), tr("Cancel")});
-            ed.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"});
-            ed.setContent(p);
-            ed.showDialog();
-
-            if (ed.getValue() != 1) return;
-
-            for (Entry<String, JComboBox> e : components.entrySet()) {
-                newWay.put(e.getKey(), e.getValue().getEditor().getItem().toString());
-            }
-        }
-
-        LinkedList<Command> cmds = new LinkedList<Command>();
-        LinkedList<Way> deletedWays = new LinkedList<Way>(selectedWays);
-        deletedWays.remove(modifyWay);
-        cmds.add(new DeleteCommand(deletedWays));
-        cmds.add(new ChangeCommand(modifyWay, newWay));
-
-        // modify all relations containing the now-deleted ways
-        for (Relation r : relationsUsingWays) {
-            List<RelationMember> newMembers = new ArrayList<RelationMember>();
-            HashSet<String> rolesToReAdd = new HashSet<String>();
-            for (RelationMember rm : r.getMembers()) {
-                // Don't copy the member if it to one of our ways, just keep a
-                // note to re-add it later on.
-                if (selectedWays.contains(rm.getMember())) {
-                    rolesToReAdd.add(rm.getRole());
-                } else {
-                    newMembers.add(rm);
-                }
-            }
-            for (String role : rolesToReAdd) {
-                newMembers.add(new RelationMember(role, modifyWay));
-            }
-            Relation newRel = new Relation(r);
-            newRel.setMembers(newMembers);
-            cmds.add(new ChangeCommand(r, newRel));
-        }
-        Main.main.undoRedo.add(new SequenceCommand(tr("Combine {0} ways", selectedWays.size()), cmds));
-        getCurrentDataSet().setSelected(modifyWay);
-    }
-
-    /**
-     * @return a message if combining failed, else a list of nodes.
-     */
-    private Object actuallyCombineWays(List<Way> ways, boolean ignoreDirection) {
-        // Battle plan:
-        //  1. Split the ways into small chunks of 2 nodes and weed out
-        //     duplicates.
-        //  2. Take a chunk and see if others could be appended or prepended,
-        //     if so, do it and remove it from the list of remaining chunks.
-        //     Rather, rinse, repeat.
-        //  3. If this algorithm does not produce a single way,
-        //     complain to the user.
-        //  4. Profit!
-
-        HashSet<Pair<Node,Node>> chunkSet = new HashSet<Pair<Node,Node>>();
-        for (Way w : ways) {
-            chunkSet.addAll(w.getNodePairs(ignoreDirection));
-        }
-
-        LinkedList<Pair<Node,Node>> chunks = new LinkedList<Pair<Node,Node>>(chunkSet);
-
-        if (chunks.isEmpty())
-            return tr("All the ways were empty");
-
-        List<Node> nodeList = Pair.toArrayList(chunks.poll());
-        while (!chunks.isEmpty()) {
-            ListIterator<Pair<Node,Node>> it = chunks.listIterator();
-            boolean foundChunk = false;
-            while (it.hasNext()) {
-                Pair<Node,Node> curChunk = it.next();
-                if (curChunk.a == nodeList.get(nodeList.size() - 1)) { // append
-                    nodeList.add(curChunk.b);
-                } else if (curChunk.b == nodeList.get(0)) { // prepend
-                    nodeList.add(0, curChunk.a);
-                } else if (ignoreDirection && curChunk.b == nodeList.get(nodeList.size() - 1)) { // append
-                    nodeList.add(curChunk.a);
-                } else if (ignoreDirection && curChunk.a == nodeList.get(0)) { // prepend
-                    nodeList.add(0, curChunk.b);
-                } else {
-                    continue;
-                }
-
-                foundChunk = true;
-                it.remove();
-                break;
-            }
-            if (!foundChunk) {
-                break;
-            }
-        }
-
-        if (!chunks.isEmpty())
-            return tr("Could not combine ways "
-                    + "(They could not be merged into a single string of nodes)");
-
-        return nodeList;
+        combineWays(selectedWays);
     }
 
@@ -328,3 +239,401 @@
         setEnabled(numWays >= 2);
     }
+
+    /**
+     * This is a collection of relations referring to at least one out of a set of
+     * ways.
+     * 
+     *
+     */
+    static private class WayReferringRelations {
+        /**
+         * the map references between relations and ways. The key is a ways, the value is a
+         * set of relations referring to that way.
+         */
+        private Map<Way, Set<Relation>> wayRelationMap;
+
+        /**
+         * 
+         * @param ways  a collection of ways
+         */
+        public WayReferringRelations(Collection<Way> ways) {
+            wayRelationMap = new HashMap<Way, Set<Relation>>();
+            if (ways == null) return;
+            ways.remove(null); // just in case - remove null values
+            for (Way way: ways) {
+                if (!wayRelationMap.containsKey(way)) {
+                    wayRelationMap.put(way, new HashSet<Relation>());
+                }
+            }
+        }
+
+        /**
+         * build the sets of referring relations from the relations in the dataset <code>ds</code>
+         * 
+         * @param ds the data set
+         */
+        public void build(DataSet ds) {
+            for (Relation r: ds.relations) {
+                if (r.isDeleted() || r.incomplete) {
+                    continue;
+                }
+                Set<Way> referringWays = OsmPrimitive.getFilteredSet(r.getMemberPrimitives(), Way.class);
+                for (Way w : wayRelationMap.keySet()) {
+                    if (referringWays.contains(w)) {
+                        wayRelationMap.get(w).add(r);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Replies the ways
+         * @return the ways
+         */
+        public Set<Way> getWays() {
+            return wayRelationMap.keySet();
+        }
+
+        /**
+         * Replies the set of referring relations
+         * 
+         * @return the set of referring relations
+         */
+        public Set<Relation> getRelations() {
+            HashSet<Relation> ret = new HashSet<Relation>();
+            for (Way w: wayRelationMap.keySet()) {
+                ret.addAll(wayRelationMap.get(w));
+            }
+            return ret;
+        }
+
+        /**
+         * Replies the set of referring relations for a specific way
+         * 
+         * @return the set of referring relations
+         */
+        public Set<Relation> getRelations(Way way) {
+            return wayRelationMap.get(way);
+        }
+
+        protected Command buildRelationUpdateCommand(Relation relation, Collection<Way> ways, Way targetWay) {
+            List<RelationMember> newMembers = new ArrayList<RelationMember>();
+            for (RelationMember rm : relation.getMembers()) {
+                if (ways.contains(rm.getMember())) {
+                    RelationMember newMember = new RelationMember(rm.getRole(),targetWay);
+                    newMembers.add(newMember);
+                } else {
+                    newMembers.add(rm);
+                }
+            }
+            Relation newRelation = new Relation(relation);
+            newRelation.setMembers(newMembers);
+            return new ChangeCommand(relation, newRelation);
+        }
+
+        public List<Command> buildRelationUpdateCommands(Way targetWay) {
+            Collection<Way> toRemove = getWays();
+            toRemove.remove(targetWay);
+            ArrayList<Command> cmds = new ArrayList<Command>();
+            for (Relation r : getRelations()) {
+                Command cmd = buildRelationUpdateCommand(r, toRemove, targetWay);
+                cmds.add(cmd);
+            }
+            return cmds;
+        }
+    }
+
+    static public class NodePair {
+        private Node a;
+        private Node b;
+        public NodePair(Node a, Node b) {
+            this.a =a;
+            this.b = b;
+        }
+
+        public NodePair(Pair<Node,Node> pair) {
+            this.a = pair.a;
+            this.b = pair.b;
+        }
+
+        public NodePair(NodePair other) {
+            this.a = other.a;
+            this.b = other.b;
+        }
+
+        public Node getA() {
+            return a;
+        }
+
+        public Node getB() {
+            return b;
+        }
+
+        public boolean isAdjacentToA(NodePair other) {
+            return other.getA() == a || other.getB() == a;
+        }
+
+        public boolean isAdjacentToB(NodePair other) {
+            return other.getA() == b || other.getB() == b;
+        }
+
+        public boolean isSuccessorOf(NodePair other) {
+            return other.getB() == a;
+        }
+
+        public boolean isPredecessorOf(NodePair other) {
+            return b == other.getA();
+        }
+
+        public NodePair swap() {
+            return new NodePair(b,a);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+            .append("[")
+            .append(a.getId())
+            .append(",")
+            .append(b.getId())
+            .append("]")
+            .toString();
+        }
+
+        public boolean contains(Node n) {
+            return a == n || b == n;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((a == null) ? 0 : a.hashCode());
+            result = prime * result + ((b == null) ? 0 : b.hashCode());
+            return result;
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            NodePair other = (NodePair) obj;
+            if (a == null) {
+                if (other.a != null)
+                    return false;
+            } else if (!a.equals(other.a))
+                return false;
+            if (b == null) {
+                if (other.b != null)
+                    return false;
+            } else if (!b.equals(other.b))
+                return false;
+            return true;
+        }
+    }
+
+
+    static public class NodeGraph {
+        static public List<NodePair> buildNodePairs(Way way, boolean directed) {
+            ArrayList<NodePair> pairs = new ArrayList<NodePair>();
+            for (Pair<Node,Node> pair: way.getNodePairs(false /* don't sort */)) {
+                pairs.add(new NodePair(pair));
+                if (!directed) {
+                    pairs.add(new NodePair(pair).swap());
+                }
+            }
+            return pairs;
+        }
+
+        static public List<NodePair> buildNodePairs(List<Way> ways, boolean directed) {
+            ArrayList<NodePair> pairs = new ArrayList<NodePair>();
+            for (Way w: ways) {
+                pairs.addAll(buildNodePairs(w, directed));
+            }
+            return pairs;
+        }
+
+        static public List<NodePair> eliminateDuplicateNodePairs(List<NodePair> pairs) {
+            ArrayList<NodePair> cleaned = new ArrayList<NodePair>();
+            for(NodePair p: pairs) {
+                if (!cleaned.contains(p) && !cleaned.contains(p.swap())) {
+                    cleaned.add(p);
+                }
+            }
+            return cleaned;
+        }
+
+        static public NodeGraph createDirectedGraphFromNodePairs(List<NodePair> pairs) {
+            NodeGraph graph = new NodeGraph();
+            for (NodePair pair: pairs) {
+                graph.add(pair);
+            }
+            return graph;
+        }
+
+        static public NodeGraph createDirectedGraphFromWays(Collection<Way> ways) {
+            NodeGraph graph = new NodeGraph();
+            for (Way w: ways) {
+                graph.add(buildNodePairs(w, true /* directed */));
+            }
+            return graph;
+        }
+
+        static public NodeGraph createUndirectedGraphFromNodeList(List<NodePair> pairs) {
+            NodeGraph graph = new NodeGraph();
+            for (NodePair pair: pairs) {
+                graph.add(pair);
+                graph.add(pair.swap());
+            }
+            return graph;
+        }
+
+        static public NodeGraph createUndirectedGraphFromNodeWays(Collection<Way> ways) {
+            NodeGraph graph = new NodeGraph();
+            for (Way w: ways) {
+                graph.add(buildNodePairs(w, false /* undirected */));
+            }
+            return graph;
+        }
+
+        private Set<NodePair> edges;
+        private int numUndirectedEges = 0;
+
+        protected void computeNumEdges() {
+            Set<NodePair> undirectedEdges = new HashSet<NodePair>();
+            for (NodePair pair: edges) {
+                if (!undirectedEdges.contains(pair) && ! undirectedEdges.contains(pair.swap())) {
+                    undirectedEdges.add(pair);
+                }
+            }
+            numUndirectedEges = undirectedEdges.size();
+        }
+
+        public NodeGraph() {
+            edges = new HashSet<NodePair>();
+        }
+
+        public void add(NodePair pair) {
+            if (!edges.contains(pair)) {
+                edges.add(pair);
+            }
+        }
+
+        public void add(List<NodePair> pairs) {
+            for (NodePair pair: pairs) {
+                add(pair);
+            }
+        }
+
+        protected Node getStartNode() {
+            return edges.iterator().next().getA();
+        }
+
+        protected Set<Node> getNodes(Stack<NodePair> pairs) {
+            HashSet<Node> nodes = new HashSet<Node>();
+            for (NodePair pair: pairs) {
+                nodes.add(pair.getA());
+                nodes.add(pair.getB());
+            }
+            return nodes;
+        }
+
+        protected List<NodePair> getOutboundPairs(NodePair pair) {
+            LinkedList<NodePair> outbound = new LinkedList<NodePair>();
+            for (NodePair candidate:edges) {
+                if (candidate.equals(pair)) {
+                    continue;
+                }
+                if (candidate.isSuccessorOf(pair)) {
+                    outbound.add(candidate);
+                }
+            }
+            return outbound;
+        }
+
+        protected List<NodePair> getOutboundPairs(Node node) {
+            LinkedList<NodePair> outbound = new LinkedList<NodePair>();
+            for (NodePair candidate:edges) {
+                if (candidate.getA() == node) {
+                    outbound.add(candidate);
+                }
+            }
+            return outbound;
+        }
+
+        protected Set<Node> getNodes() {
+            Set<Node> nodes = new HashSet<Node>();
+            for (NodePair pair: edges) {
+                nodes.add(pair.getA());
+                nodes.add(pair.getB());
+            }
+            return nodes;
+        }
+
+        protected boolean isSpanningWay(Stack<NodePair> way) {
+            return numUndirectedEges == way.size();
+        }
+
+
+        protected boolean advance(Stack<NodePair> path) {
+            // found a spanning path ?
+            //
+            if (isSpanningWay(path))
+                return true;
+
+            // advance with one of the possible follow up nodes
+            //
+            Stack<NodePair> nextPairs = new Stack<NodePair>();
+            nextPairs.addAll(getOutboundPairs(path.peek()));
+            while(!nextPairs.isEmpty()) {
+                NodePair next = nextPairs.pop();
+                if (path.contains(next) || path.contains(next.swap())) {
+                    continue;
+                }
+                path.push(next);
+                if (advance(path)) return true;
+                path.pop();
+            }
+            return false;
+        }
+
+        protected List<Node> buildPathFromNodePairs(Stack<NodePair> path) {
+            LinkedList<Node> ret = new LinkedList<Node>();
+            for (NodePair pair: path) {
+                ret.add(pair.getA());
+            }
+            ret.add(path.peek().getB());
+            return ret;
+        }
+
+        protected List<Node> buildSpanningPath(Node startNode) {
+            if (startNode == null)
+                return null;
+            Stack<NodePair> path = new Stack<NodePair>();
+            // advance with one of the possible follow up nodes
+            //
+            Stack<NodePair> nextPairs  = new Stack<NodePair>();
+            nextPairs.addAll(getOutboundPairs(startNode));
+            while(!nextPairs.isEmpty()) {
+                path.push(nextPairs.pop());
+                if (advance(path))
+                    return buildPathFromNodePairs(path);
+                path.pop();
+            }
+            return null;
+        }
+
+        public List<Node> buildSpanningPath() {
+            computeNumEdges();
+            for (Node n : getNodes()) {
+                List<Node> path = buildSpanningPath(n);
+                if (path != null)
+                    return path;
+            }
+            return null;
+        }
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 2070)
@@ -101,5 +101,5 @@
                     members.add(mnew);
                 }
-                enew.members.addAll(members);
+                enew.setMembers(members);
                 pasteBuffer.addPrimitive(enew);
             }
Index: /trunk/src/org/openstreetmap/josm/actions/DiskAccessAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/DiskAccessAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/DiskAccessAction.java	(revision 2070)
@@ -50,11 +50,15 @@
         if (!open) {
             File file = fc.getSelectedFile();
-            if (file == null || (file.exists() && 1 !=
-                new ExtendedDialog(Main.parent,
+            if (file != null && file.exists()) {
+                ExtendedDialog dialog = new ExtendedDialog(
+                        Main.parent,
                         tr("Overwrite"),
-                        tr("File exists. Overwrite?"),
-                        new String[] {tr("Overwrite"), tr("Cancel")},
-                        new String[] {"save_as.png", "cancel.png"}).getValue()))
-                return null;
+                        new String[] {tr("Overwrite"), tr("Cancel")}
+                );
+                dialog.setContent(tr("File exists. Overwrite?"));
+                dialog.setButtonIcons(new String[] {"save_as.png", "cancel.png"});
+                if (dialog.getValue() != 1)
+                    return null;
+            }
         }
 
Index: /trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java	(revision 2070)
@@ -201,9 +201,14 @@
 
         if (!components.isEmpty()) {
-            int answer = new ExtendedDialog(Main.parent,
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
                     tr("Enter values for all conflicts."),
-                    p,
-                    new String[] {tr("Solve Conflicts"), tr("Cancel")},
-                    new String[] {"dialogs/conflict.png", "cancel.png"}).getValue();
+                    new String[] {tr("Solve Conflicts"), tr("Cancel")}
+            );
+            dialog.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"});
+            dialog.setContent(p);
+            dialog.showDialog();
+            int answer = dialog.getValue();
+
             if (answer != 1)
                 return null;
Index: /trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java	(revision 2070)
@@ -17,4 +17,5 @@
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
 import org.openstreetmap.josm.io.FileImporter;
+import org.openstreetmap.josm.io.IllegalDataException;
 import org.openstreetmap.josm.io.OsmTransferException;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -51,5 +52,5 @@
     }
 
-    static public void openFile(File f) throws IOException {
+    static public void openFile(File f) throws IOException, IllegalDataException {
         for (FileImporter importer : ExtensionFileFilter.importers)
             if (importer.acceptFile(f)) {
Index: /trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java	(revision 2070)
@@ -31,5 +31,5 @@
     public OpenLocationAction() {
         super(tr("Open Location..."), "openlocation", tr("Open an URL."),
-        Shortcut.registerShortcut("system:open_location", tr("File: {0}", tr("Open Location...")), KeyEvent.VK_L, Shortcut.GROUP_MENU), true);
+                Shortcut.registerShortcut("system:open_location", tr("File: {0}", tr("Open Location...")), KeyEvent.VK_L, Shortcut.GROUP_MENU), true);
     }
 
@@ -43,10 +43,11 @@
         all.add(urltext, GBC.eol());
         all.add(layer, GBC.eol());
-        int answer = new ExtendedDialog(Main.parent,
-                        tr("Download Location"),
-                        all,
-                        new String[] {tr("Download URL"), tr("Cancel")},
-                        new String[] {"download.png", "cancel.png"}).getValue();
-        if (answer != 1) return;
+        ExtendedDialog dialog = new ExtendedDialog(Main.parent,
+                tr("Download Location"),
+                new String[] {tr("Download URL"), tr("Cancel")}
+        );
+        dialog.setContent(all);
+        dialog.setButtonIcons(new String[] {"download.png", "cancel.png"});
+        if (dialog.getValue() != 1) return;
         openUrl(layer.isSelected(), urltext.getText());
     }
Index: /trunk/src/org/openstreetmap/josm/actions/PasteAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 2070)
@@ -69,5 +69,5 @@
         for (Node n : pasteBuffer.nodes) {
             Node nnew = new Node(n);
-            nnew.id = 0;
+            nnew.clearOsmId();
             if (Main.map.mapView.getEditLayer() == source) {
                 nnew.setEastNorth(nnew.getEastNorth().add(offsetEast, offsetNorth));
@@ -78,5 +78,5 @@
             Way wnew = new Way();
             wnew.cloneFrom(w);
-            wnew.id = 0;
+            wnew.clearOsmId();
             /* make sure we reference the new nodes corresponding to the old ones */
             List<Node> nodes = new ArrayList<Node>();
@@ -89,12 +89,11 @@
         for (Relation r : pasteBuffer.relations) {
             Relation rnew = new Relation(r);
-            rnew.id = 0;
+            r.clearOsmId();
             List<RelationMember> members = new ArrayList<RelationMember>();
             for (RelationMember m : r.getMembers()) {
                 OsmPrimitive mo = map.get(m.getMember());
-                if(mo != null) /* TODO - This only prevents illegal data, but kills the relation */
+                if(mo != null) /* FIXME - This only prevents illegal data, but kills the relation */
                 {
-                    RelationMember mnew = new RelationMember(m);
-                    mnew.member = map.get(m.getMember());
+                    RelationMember mnew = new RelationMember(m.getRole(), map.get(m.getMember()));
                     members.add(mnew);
                 }
Index: /trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 2070)
@@ -37,4 +37,12 @@
     }
 
+    static private List<Class<? extends OsmPrimitive>> osmPrimitiveClasses;
+    {
+        osmPrimitiveClasses = new ArrayList<Class<? extends OsmPrimitive>>();
+        osmPrimitiveClasses.add(Node.class);
+        osmPrimitiveClasses.add(Way.class);
+        osmPrimitiveClasses.add(Relation.class);
+    }
+
     /**
      * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
@@ -126,5 +134,5 @@
     protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
         HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
-        for (Class<? extends OsmPrimitive> type: new Class[] {Node.class, Way.class, Relation.class}) {
+        for (Class<? extends OsmPrimitive> type: osmPrimitiveClasses) {
             if (!getSourceTagsByType(type).isEmpty()) {
                 ret.put(OsmPrimitiveType.from(type), getSourcePrimitivesByType(type).size());
@@ -136,5 +144,5 @@
     protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
         HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
-        for (Class<? extends OsmPrimitive> type: new Class[] {Node.class, Way.class, Relation.class}) {
+        for (Class<? extends OsmPrimitive> type: osmPrimitiveClasses) {
             int count = getSubcollectionByType(getEditLayer().data.getSelected(), type).size();
             if (count > 0) {
@@ -156,5 +164,5 @@
     protected void pasteFromHomogeneousSource(Collection<? extends OsmPrimitive> targets) {
         TagCollection tc = null;
-        for (Class<? extends OsmPrimitive> type : new Class[] {Node.class, Way.class, Relation.class}) {
+        for (Class<? extends OsmPrimitive> type : osmPrimitiveClasses) {
             TagCollection tc1 = getSourceTagsByType(type);
             if (!tc1.isEmpty()) {
Index: /trunk/src/org/openstreetmap/josm/actions/SaveAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SaveAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/SaveAction.java	(revision 2070)
@@ -34,10 +34,19 @@
             f=null;
         }
-        if(f != null && layer instanceof GpxLayer && 1 !=
-            new ExtendedDialog(Main.parent, tr("Overwrite"),
-                    tr("File {0} exists. Overwrite?", f.getName()),
-                    new String[] {tr("Overwrite"), tr("Cancel")},
-                    new String[] {"save_as.png", "cancel.png"}).getValue()) {
-            f = null;
+
+        // FIXME: why only for GpxLayer?
+        if(f != null && layer instanceof GpxLayer) {
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
+                    tr("Overwrite"),
+                    new String[] {tr("Overwrite"), tr("Cancel")}
+            );
+            dialog.setButtonIcons(new String[] {"save_as.png", "cancel.png"});
+            dialog.setContent(tr("File {0} exists. Overwrite?", f.getName()));
+            dialog.showDialog();
+            int ret = dialog.getValue();
+            if (ret != 1) {
+                f = null;
+            }
         }
         return f == null ? openFileDialog(layer) : f;
Index: /trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/SaveActionBase.java	(revision 2070)
@@ -91,6 +91,16 @@
      */
     public boolean checkSaveConditions(Layer layer) {
-        if (layer instanceof OsmDataLayer && isDataSetEmpty((OsmDataLayer)layer) && 1 != new ExtendedDialog(Main.parent, tr("Empty document"), tr("The document contains no data."), new String[] {tr("Save anyway"), tr("Cancel")}, new String[] {"save.png", "cancel.png"}).getValue())
-            return false;
+        if (layer instanceof OsmDataLayer && isDataSetEmpty((OsmDataLayer)layer)) {
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
+                    tr("Empty document"),
+                    new String[] {tr("Save anyway"), tr("Cancel")}
+            );
+            dialog.setContent(tr("The document contains no data."));
+            dialog.setButtonIcons(new String[] {"save.png", "cancel.png"});
+            dialog.showDialog();
+            if (dialog.getValue() != 1) return false;
+        }
+
         if (layer instanceof GpxLayer && ((GpxLayer)layer).data == null)
             return false;
@@ -98,11 +108,13 @@
             ConflictCollection conflicts = ((OsmDataLayer)layer).getConflicts();
             if (conflicts != null && !conflicts.isEmpty()) {
-                int answer = new ExtendedDialog(Main.parent,
+                ExtendedDialog dialog = new ExtendedDialog(
+                        Main.parent,
                         tr("Conflicts"),
-                        tr("There are unresolved conflicts. Conflicts will not be saved and handled as if you rejected all. Continue?"),
-                        new String[] {tr("Reject Conflicts and Save"), tr("Cancel")},
-                        new String[] {"save.png", "cancel.png"}).getValue();
-
-                if (answer != 1) return false;
+                        new String[] {tr("Reject Conflicts and Save"), tr("Cancel")}
+                );
+                dialog.setContent(tr("There are unresolved conflicts. Conflicts will not be saved and handled as if you rejected all. Continue?"));
+                dialog.setButtonIcons(new String[] {"save.png", "cancel.png"});
+                dialog.showDialog();
+                if (dialog.getValue() != 1) return false;
             }
         }
@@ -190,9 +202,15 @@
             }
         }
-        if(file == null || (file.exists() && 1 != new ExtendedDialog(Main.parent,
-                tr("Overwrite"), tr("File exists. Overwrite?"),
-                new String[] {tr("Overwrite"), tr("Cancel")},
-                new String[] {"save_as.png", "cancel.png"}).getValue()))
-            return null;
+        if(file == null || (file.exists())) {
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
+                    tr("Overwrite"),
+                    new String[] {tr("Overwrite"), tr("Cancel")}
+            );
+            dialog.setContent(tr("File exists. Overwrite?"));
+            dialog.setButtonIcons(new String[] {"save_as.png", "cancel.png"});
+            dialog.showDialog();
+            if (dialog.getValue() != 1) return null;
+        }
         return file;
     }
Index: /trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java	(revision 2070)
@@ -156,5 +156,5 @@
 
         Node n = new Node(selectedNode);
-        n.id = 0;
+        n.clearOsmId();
 
         // If this wasn't called from menu, place it where the cursor is/was
@@ -301,5 +301,5 @@
                 // clone the node for all other ways
                 pushNode = new Node(pushNode);
-                pushNode.id = 0;
+                pushNode.clearOsmId();
                 newNodes.add(pushNode);
                 cmds.add(new AddCommand(pushNode));
@@ -331,5 +331,5 @@
                         if (newRel == null) {
                             newRel = new Relation(r);
-                            newRel.members.clear();
+                            newRel.setMembers(null);
                             rolesToReAdd = new HashSet<String>();
                         }
@@ -340,11 +340,9 @@
             if (newRel != null) {
                 for (RelationMember rm : r.getMembers()) {
-                    //if (rm.member != selectedNode) {
-                    newRel.members.add(rm);
-                    //}
+                    newRel.addMember(rm);
                 }
                 for (Node n : newNodes) {
                     for (String role : rolesToReAdd) {
-                        newRel.members.add(new RelationMember(role, n));
+                        newRel.addMember(new RelationMember(role, n));
                     }
                 }
Index: /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 2070)
@@ -118,10 +118,14 @@
         p.add(left);
         p.add(right);
-
-        int result = new ExtendedDialog(Main.parent,
+        ExtendedDialog dialog = new ExtendedDialog(
+                Main.parent,
                 tr("Search"),
-                p,
-                new String[] {tr("Start Search"), tr("Cancel")},
-                new String[] {"dialogs/search.png", "cancel.png"}).getValue();
+                new String[] {tr("Start Search"), tr("Cancel")}
+        );
+        dialog.setButtonIcons(new String[] {"dialogs/search.png", "cancel.png"});
+        dialog.setContent(p);
+        dialog.showDialog();
+        int result = dialog.getValue();
+
         if(result != 1) return;
 
Index: /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 2070)
@@ -7,4 +7,5 @@
 import java.io.PushbackReader;
 import java.io.StringReader;
+import java.util.List;
 import java.util.Map.Entry;
 import java.util.regex.Matcher;
@@ -82,5 +83,5 @@
         public Id(long id) {this.id = id;}
         @Override public boolean match(OsmPrimitive osm) {
-            return osm.id == id;
+            return osm.getId() == id;
         }
         @Override public String toString() {return "id="+id;}
@@ -329,5 +330,5 @@
             }
             if (osm.user != null) {
-                String name = osm.user.name;
+                String name = osm.user.getName();
                 // is not Java 1.5
                 //String name = java.text.Normalizer.normalize(name, java.text.Normalizer.Form.NFC);
@@ -364,9 +365,21 @@
     private static class UserMatch extends Match {
         private User user;
-        public UserMatch(String user) { this.user = User.get(user); }
-        @Override public boolean match(OsmPrimitive osm) {
-            return osm.user == user;
-        }
-        @Override public String toString() { return "user=" + user.name; }
+        public UserMatch(String user) {
+            List<User> users = User.getByName(user);
+            if (!users.isEmpty()) {
+                // selecting an arbitrary user
+                this.user = users.get(0);
+            } else {
+                user = null;
+            }
+        }
+        @Override public boolean match(OsmPrimitive osm) {
+            if (osm.user == null && user == null) return true;
+            if (osm.user == null) return false;
+            return osm.user.equals(user);
+        }
+        @Override public String toString() {
+            return "user=" + user == null ? "" : user.getName();
+        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/command/ChangeRelationMemberRoleCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/ChangeRelationMemberRoleCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/ChangeRelationMemberRoleCommand.java	(revision 2070)
@@ -49,5 +49,5 @@
 
         oldRole = relation.getMember(position).getRole();
-        relation.getMember(position).role = newRole;
+        relation.getMember(position).getRole().equals(newRole);
 
         oldModified = relation.isModified();
@@ -57,5 +57,5 @@
 
     @Override public void undoCommand() {
-        relation.getMember(position).role = oldRole;
+        relation.getMember(position).getRole().equals(oldRole);
         relation.setModified(oldModified);
     }
Index: /trunk/src/org/openstreetmap/josm/command/DeleteCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/DeleteCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/DeleteCommand.java	(revision 2070)
@@ -92,5 +92,5 @@
         super.executeCommand();
         for (OsmPrimitive osm : toDelete) {
-            osm.delete(true);
+            osm.setDeleted(true);
         }
         return true;
Index: /trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java	(revision 2070)
@@ -71,7 +71,7 @@
             if (conflict.getTheir().isDeleted()) {
                 layer.data.unlinkReferencesToPrimitive(conflict.getMy());
-                conflict.getMy().delete(true);
+                conflict.getMy().setDeleted(true);
             } else {
-                conflict.getMy().delete(conflict.getTheir().isDeleted());
+                conflict.getMy().setDeleted(conflict.getTheir().isDeleted());
             }
         } else
Index: /trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java	(revision 2070)
@@ -185,5 +185,5 @@
             if (pair.getParent() instanceof Way) {
                 Way w = (Way)pair.getParent();
-                System.out.println(tr("removing reference from way {0}",w.id));
+                System.out.println(tr("removing reference from way {0}",w.getId()));
                 List<Node> wayNodes = w.getNodes();
                 wayNodes.remove(primitive);
@@ -194,5 +194,5 @@
                 if (w.getNodesCount() < 2) {
                     System.out.println(tr("Warning: Purging way {0} because number of nodes dropped below 2. Current is {1}",
-                            w.id,w.getNodesCount()));
+                            w.getId(),w.getNodesCount()));
                     if (!hive.contains(w)) {
                         hive.add(w);
@@ -201,5 +201,5 @@
             } else if (pair.getParent() instanceof Relation) {
                 Relation r = (Relation)pair.getParent();
-                System.out.println(tr("removing reference from relation {0}",r.id));
+                System.out.println(tr("removing reference from relation {0}",r.getId()));
                 r.removeMembersFor(primitive);
             }
Index: /trunk/src/org/openstreetmap/josm/command/RelationMemberConflictResolverCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/RelationMemberConflictResolverCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/RelationMemberConflictResolverCommand.java	(revision 2070)
@@ -56,5 +56,5 @@
         return new DefaultMutableTreeNode(
                 new JLabel(
-                        tr("Resolve conflicts in member list of relation {0}", my.id),
+                        tr("Resolve conflicts in member list of relation {0}", my.getId()),
                         ImageProvider.get("data", "object"),
                         JLabel.HORIZONTAL
Index: /trunk/src/org/openstreetmap/josm/command/RemoveRelationMemberCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/RemoveRelationMemberCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/RemoveRelationMemberCommand.java	(revision 2070)
@@ -51,5 +51,5 @@
         } else {
             relation.removeMember(removeIndex);
-            relation.modified = true;
+            relation.setModified(true);
             return true;
         }
@@ -59,5 +59,5 @@
         super.undoCommand();
         relation.addMember(member);
-        relation.modified = this.getOrig(relation).modified;
+        relation.setModified(this.getOrig(relation).isModified());
     }
 
@@ -66,13 +66,13 @@
     @Override public MutableTreeNode description() {
         String msg = "";
-        switch(OsmPrimitiveType.from(member.member)) {
-        case NODE: msg = marktr("Remove node ''{0}'' at position {1} from relation ''{2}''"); break;
-        case WAY: msg = marktr("Remove way ''{0}'' at position {1} from relation ''{2}''"); break;
-        case RELATION: msg = marktr("Remove relation ''{0}'' at position {1} from relation ''{2}''"); break;
+        switch(OsmPrimitiveType.from(member.getMember())) {
+            case NODE: msg = marktr("Remove node ''{0}'' at position {1} from relation ''{2}''"); break;
+            case WAY: msg = marktr("Remove way ''{0}'' at position {1} from relation ''{2}''"); break;
+            case RELATION: msg = marktr("Remove relation ''{0}'' at position {1} from relation ''{2}''"); break;
         }
         return new DefaultMutableTreeNode(
                 new JLabel(
                         tr(msg,
-                                member.member.getDisplayName(DefaultNameFormatter.getInstance()),
+                                member.getMember().getDisplayName(DefaultNameFormatter.getInstance()),
                                 relation.getMembers().indexOf(member),
                                 relation.getDisplayName(DefaultNameFormatter.getInstance())
Index: /trunk/src/org/openstreetmap/josm/command/RotateCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/RotateCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/RotateCommand.java	(revision 2070)
@@ -76,5 +76,5 @@
             os.latlon = new LatLon(n.getCoor());
             os.eastNorth = n.getEastNorth();
-            os.modified = n.modified;
+            os.modified = n.isModified();
             oldState.put(n, os);
             pivot = pivot.add(os.eastNorth.east(), os.eastNorth.north());
@@ -114,5 +114,5 @@
             n.setEastNorth(new EastNorth(nx, ny));
             if (setModified) {
-                n.modified = true;
+                n.setModified(true);
             }
         }
@@ -128,5 +128,5 @@
             OldState os = oldState.get(n);
             n.setCoor(os.latlon);
-            n.modified = os.modified;
+            n.setModified(os.modified);
         }
     }
Index: /trunk/src/org/openstreetmap/josm/command/TagConflictResolveCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/TagConflictResolveCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/TagConflictResolveCommand.java	(revision 2070)
@@ -67,11 +67,11 @@
         String msg = "";
         switch(OsmPrimitiveType.from(conflict.getMy())) {
-        case NODE: msg = marktr("Resolve {0} tag conflicts in node {1}"); break;
-        case WAY: msg = marktr("Resolve {0} tag conflicts in way {1}"); break;
-        case RELATION: msg = marktr("Resolve {0} tag conflicts in relation {1}"); break;
+            case NODE: msg = marktr("Resolve {0} tag conflicts in node {1}"); break;
+            case WAY: msg = marktr("Resolve {0} tag conflicts in way {1}"); break;
+            case RELATION: msg = marktr("Resolve {0} tag conflicts in relation {1}"); break;
         }
         return new DefaultMutableTreeNode(
                 new JLabel(
-                        tr(msg,getNumDecidedConflicts(), conflict.getMy().id),
+                        tr(msg,getNumDecidedConflicts(), conflict.getMy().getId()),
                         ImageProvider.get("data", "object"),
                         JLabel.HORIZONTAL
Index: /trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java	(revision 2070)
@@ -80,5 +80,5 @@
                 getLayer().getConflicts().remove(primitive);
             }
-            primitive.id = 0;
+            primitive.clearOsmId();
         }
         return true;
Index: /trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java	(revision 2070)
@@ -39,11 +39,11 @@
         String msg = "";
         switch(OsmPrimitiveType.from(conflict.getMy())) {
-        case NODE: msg = marktr("Resolve version conflicts for node {0}"); break;
-        case WAY: msg = marktr("Resolve version conflicts for way {0}"); break;
-        case RELATION: msg = marktr("Resolve version conflicts for relation {0}"); break;
+            case NODE: msg = marktr("Resolve version conflicts for node {0}"); break;
+            case WAY: msg = marktr("Resolve version conflicts for way {0}"); break;
+            case RELATION: msg = marktr("Resolve version conflicts for relation {0}"); break;
         }
         return new DefaultMutableTreeNode(
                 new JLabel(
-                        tr(msg,conflict.getMy().id),
+                        tr(msg,conflict.getMy().getId()),
                         ImageProvider.get("data", "object"),
                         JLabel.HORIZONTAL
@@ -55,5 +55,8 @@
     public boolean executeCommand() {
         super.executeCommand();
-        conflict.getMy().version = Math.max(conflict.getMy().version, conflict.getTheir().version);
+        conflict.getMy().setOsmId(
+                conflict.getMy().getId(),
+                (int)Math.max(conflict.getMy().getVersion(), conflict.getTheir().getVersion())
+        );
         getLayer().getConflicts().remove(conflict);
         rememberConflict(conflict);
Index: /trunk/src/org/openstreetmap/josm/command/WayNodesConflictResolverCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/WayNodesConflictResolverCommand.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/command/WayNodesConflictResolverCommand.java	(revision 2070)
@@ -52,5 +52,5 @@
         return new DefaultMutableTreeNode(
                 new JLabel(
-                        tr("Resolve conflicts in node list of of way {0}", conflict.getMy().id),
+                        tr("Resolve conflicts in node list of of way {0}", conflict.getMy().getId()),
                         ImageProvider.get("data", "object"),
                         JLabel.HORIZONTAL
Index: /trunk/src/org/openstreetmap/josm/data/osm/Changeset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 2070)
@@ -30,5 +30,5 @@
 
     public int compareTo(OsmPrimitive other) {
-        if (other instanceof Changeset) return Long.valueOf(id).compareTo(other.id);
+        if (other instanceof Changeset) return Long.valueOf(getId()).compareTo(other.getId());
         return 1;
     }
@@ -37,10 +37,10 @@
     public String getName() {
         // no translation
-        return "changeset " + id;
+        return "changeset " + getId();
     }
 
     @Override
     public String getLocalName(){
-        return tr("Changeset {0}",id);
+        return tr("Changeset {0}",getId());
     }
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 2070)
@@ -373,5 +373,5 @@
     protected void deleteWay(Way way) {
         way.setNodes(null);
-        way.delete(true);
+        way.setDeleted(true);
     }
 
@@ -471,3 +471,26 @@
         return false;
     }
+
+    public Set<Relation> getReferringRelations(Collection<? extends OsmPrimitive> primitives) {
+        HashSet<Relation> ret = new HashSet<Relation>();
+        if (primitives == null) return ret;
+        Set<? extends OsmPrimitive> referred;
+        if (primitives instanceof Set<?>) {
+            referred = (Set<? extends OsmPrimitive>)primitives;
+        } else {
+            referred = new HashSet<OsmPrimitive>(primitives);
+        }
+        referred.remove(null); // just in case - remove null element from primitives
+        for (Relation r: relations) {
+            if (r.isDeleted() || r.incomplete) {
+                continue;
+            }
+            Set<OsmPrimitive> memberPrimitives = r.getMemberPrimitives();
+            memberPrimitives.retainAll(referred);
+            if (!memberPrimitives.isEmpty()) {
+                ret.add(r);
+            }
+        }
+        return ret;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/Node.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Node.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Node.java	(revision 2070)
@@ -17,6 +17,5 @@
 
     public final void setCoor(LatLon coor) {
-        if(coor != null)
-        {
+        if(coor != null){
             if(this.coor == null) {
                 this.coor = new CachedLatLon(coor);
@@ -47,4 +46,12 @@
 
 
+    /**
+     * Create a new local node with id 0.
+     * 
+     */
+    public Node() {
+        this(0);
+    }
+
 
     /**
@@ -52,6 +59,5 @@
      */
     public Node(long id) {
-        this.id = id;
-        incomplete = true;
+        super(id);
     }
 
@@ -60,12 +66,15 @@
      */
     public Node(Node clone) {
+        super(clone.getId());
         cloneFrom(clone);
     }
 
     public Node(LatLon latlon) {
+        super(0);
         setCoor(latlon);
     }
 
     public Node(EastNorth eastNorth) {
+        super(0);
         setEastNorth(eastNorth);
     }
@@ -81,6 +90,6 @@
 
     @Override public String toString() {
-        if (coor == null) return "{Node id="+id+"}";
-        return "{Node id="+id+",version="+version+",lat="+coor.lat()+",lon="+coor.lon()+"}";
+        if (coor == null) return "{Node id="+getId()+"}";
+        return "{Node id="+getId()+",version="+getVersion()+",lat="+coor.lat()+",lon="+coor.lon()+"}";
     }
 
@@ -101,5 +110,5 @@
 
     public int compareTo(OsmPrimitive o) {
-        return o instanceof Node ? Long.valueOf(id).compareTo(o.id) : 1;
+        return o instanceof Node ? Long.valueOf(getId()).compareTo(o.getId()) : 1;
     }
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2070)
@@ -10,6 +10,10 @@
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.Map.Entry;
 
@@ -31,4 +35,27 @@
 abstract public class OsmPrimitive implements Comparable<OsmPrimitive> {
 
+    static public <T extends OsmPrimitive>  List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
+        if (list == null) return null;
+        List<T> ret = new LinkedList<T>();
+        for(OsmPrimitive p: list) {
+            if (type.isInstance(p)) {
+                ret.add(type.cast(p));
+            }
+        }
+        return ret;
+    }
+
+    static public <T extends OsmPrimitive>  Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
+        if (set == null) return null;
+        HashSet<T> ret = new HashSet<T>();
+        for(OsmPrimitive p: set) {
+            if (type.isInstance(p)) {
+                ret.add(type.cast(p));
+            }
+        }
+        return ret;
+    }
+
+
     /* mappaint data */
     public ElemStyle mappaintStyle = null;
@@ -70,5 +97,5 @@
      * the respective class.
      * 
-     * @deprecated use {@see #getId()}. Don't assign an id, create a primitive with
+     * @deprecated use {@see #getId()} and {@see #setId()}. Don't assign an id, create a primitive with
      * the respective constructors.
      */
@@ -120,4 +147,46 @@
 
     /**
+     * If set to true, this object is incomplete, which means only the id
+     * and type is known (type is the objects instance class)
+     */
+    public boolean incomplete = false;
+
+    /**
+     * Contains the version number as returned by the API. Needed to
+     * ensure update consistency
+     * @deprecated use {@see #getVersion()} and {@see #setVersion(long)}
+     */
+    @Deprecated
+    public int version = 0;
+
+
+    /**
+     * Creates a new primitive with id 0.
+     * 
+     */
+    public OsmPrimitive() {
+        this(0);
+    }
+
+    /**
+     * Creates a new primitive for the given id. If the id > 0, the primitive is marked
+     * as incomplete.
+     * 
+     * @param id the id. > 0 required
+     * @throws IllegalArgumentException thrown if id < 0
+     */
+    public OsmPrimitive(long id) throws IllegalArgumentException {
+        if (id < 0)
+            throw new IllegalArgumentException(tr("expected id >= 0. Got {0}", id));
+        this.id = id;
+        this.version = 0;
+        this.incomplete = id >0;
+    }
+
+    /* ------------------------------------------------------------------------------------ */
+    /* accessors                                                                            */
+    /* ------------------------------------------------------------------------------------ */
+
+    /**
      * Sets whether this primitive is selected or not.
      * 
@@ -162,5 +231,5 @@
      *
      * @return <code>true</code>, if the object has been deleted.
-     * @see #delete(boolean)
+     * @see #setDeleted(boolean)
      */
     public boolean isDeleted() {
@@ -205,4 +274,14 @@
 
     /**
+     * Replies the version number as returned by the API. The version is 0 if the id is 0 or
+     * if this primitive is incomplete.
+     * 
+     * @see #setVersion(int)
+     */
+    public long getVersion() {
+        return version;
+    }
+
+    /**
      * Replies the id of this primitive.
      * 
@@ -214,10 +293,47 @@
 
     /**
-     * If set to true, this object is highlighted. Currently this is only used to
-     * show which ways/nodes will connect
-     */
-    public volatile boolean highlighted = false;
-
-    private int timestamp;
+     * Sets the id and the version of this primitive if it is known to the OSM API.
+     * 
+     * Since we know the id and its version it can't be incomplete anymore. incomplete
+     * is set to false.
+     * 
+     * @param id the id. > 0 required
+     * @param version the version > 0 required
+     * @throws IllegalArgumentException thrown if id <= 0
+     * @throws IllegalArgumentException thrown if version <= 0
+     */
+    public void setOsmId(long id, int version) {
+        if (id <= 0)
+            throw new IllegalArgumentException(tr("id > 0 expected. Got {0}", id));
+        if (version <= 0)
+            throw new IllegalArgumentException(tr("version > 0 expected. Got {0}", version));
+        this.id = id;
+        this.version = version;
+        this.incomplete = false;
+    }
+
+    /**
+     * Clears the id and version known to the OSM API. The id and the version is set to 0.
+     * incomplete is set to false.
+     * 
+     * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
+     * Ways and relations might already refer to the primitive and clearing the OSM ID
+     * result in corrupt data.
+     * 
+     * Here's an example use case:
+     * <pre>
+     *     // create a clone of an already existing node
+     *     Node copy = new Node(otherExistingNode);
+     * 
+     *     // reset the clones OSM id
+     *     copy.clearOsmId();
+     * </pre>
+     * 
+     */
+    public void clearOsmId() {
+        this.id = 0;
+        this.version = 0;
+        this.incomplete = false;
+    }
 
     public void setTimestamp(Date timestamp) {
@@ -240,14 +356,10 @@
 
     /**
-     * If set to true, this object is incomplete, which means only the id
-     * and type is known (type is the objects instance class)
-     */
-    public boolean incomplete = false;
-
-    /**
-     * Contains the version number as returned by the API. Needed to
-     * ensure update consistency
-     */
-    public int version = -1;
+     * If set to true, this object is highlighted. Currently this is only used to
+     * show which ways/nodes will connect
+     */
+    public volatile boolean highlighted = false;
+
+    private int timestamp;
 
     private static Collection<String> uninteresting = null;
@@ -288,8 +400,15 @@
     abstract public void visit(Visitor visitor);
 
-    public final void delete(boolean deleted) {
+    /**
+     * Sets whether this primitive is deleted or not.
+     * 
+     * Also marks this primitive as modified if deleted is true and sets selected to false.
+     * 
+     * @param deleted  true, if this primitive is deleted; false, otherwise
+     */
+    public void setDeleted(boolean deleted) {
+        this.modified = deleted;
         this.deleted = deleted;
-        setSelected(false);
-        modified = true;
+        this.selected = false;
     }
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2070)
@@ -2,5 +2,7 @@
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
@@ -17,5 +19,8 @@
      * All members of this relation. Note that after changing this,
      * makeBackReferences and/or removeBackReferences should be called.
-     */
+     * 
+     * @deprecated use the improved API instead of accessing this list directly
+     */
+    @Deprecated
     public final List<RelationMember> members = new ArrayList<RelationMember>();
 
@@ -104,24 +109,29 @@
 
     /**
+     * Create a new relation with id 0
+     */
+    public Relation() {
+
+    }
+
+    /**
      * Create an identical clone of the argument (including the id)
      */
     public Relation(Relation clone) {
+        super(clone.getId());
         cloneFrom(clone);
     }
 
     /**
-     * Create an incomplete Relation.
-     */
-    public Relation(long id) {
-        this.id = id;
-        incomplete = true;
-    }
-
-    /**
-     * Create an empty Relation. Use this only if you set meaningful values
-     * afterwards.
-     */
-    public Relation() {
-    }
+     * Creates a new relation for the given id. If the id > 0, the way is marked
+     * as incomplete.
+     * 
+     * @param id the id. > 0 required
+     * @throws IllegalArgumentException thrown if id < 0
+     */
+    public Relation(long id) throws IllegalArgumentException {
+        super(id);
+    }
+
 
     @Override public void cloneFrom(OsmPrimitive osm) {
@@ -138,5 +148,5 @@
         // return "{Relation id="+id+" version="+version+" members="+Arrays.toString(members.toArray())+"}";
         // adding members in string increases memory usage a lot and overflows for looped relations
-        return "{Relation id="+id+" version="+version+"}";
+        return "{Relation id="+getId()+" version="+getVersion()+"}";
     }
 
@@ -152,5 +162,5 @@
 
     public int compareTo(OsmPrimitive o) {
-        return o instanceof Relation ? Long.valueOf(id).compareTo(o.id) : -1;
+        return o instanceof Relation ? Long.valueOf(getId()).compareTo(o.getId()) : -1;
     }
 
@@ -158,5 +168,5 @@
     public boolean isIncomplete() {
         for (RelationMember m : members)
-            if (m.member == null)
+            if (m.getMember() == null)
                 return true;
         return false;
@@ -183,5 +193,5 @@
         ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
         for (RelationMember member: members) {
-            if (member.member == primitive) {
+            if (member.getMember() == primitive) {
                 todelete.add(member);
             }
@@ -194,3 +204,20 @@
         return formatter.format(this);
     }
+
+    /**
+     * Replies the set of  {@see OsmPrimitive}s referred to by at least one
+     * member of this relation
+     * 
+     * @return the set of  {@see OsmPrimitive}s referred to by at least one
+     * member of this relation
+     */
+    public Set<OsmPrimitive> getMemberPrimitives() {
+        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
+        for (RelationMember m: members) {
+            if (m.getMember() != null) {
+                ret.add(m.getMember());
+            }
+        }
+        return ret;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java	(revision 2070)
@@ -6,9 +6,19 @@
  * list is not sufficient.
  *
- * @author Frederik Ramm <frederik@remote.org>
  */
 public class RelationMember {
 
+    /**
+     * 
+     * @deprecated use {@see #getRole()} or create a clone in order to assign a new role
+     */
+    @Deprecated
     public String role;
+
+    /**
+     * 
+     * @deprecated use {@see #getMember()} or create a clone in order to assign a new member
+     */
+    @Deprecated
     public OsmPrimitive member;
 
@@ -19,9 +29,7 @@
      */
     public String getRole() {
-        if (role == null) {
+        if (role == null)
             return "";
-        } else {
-            return role;
-        }
+        return role;
     }
 
@@ -45,8 +53,8 @@
 
     /**
-    *
-    * @return True if member is way
-    * @since 1937
-    */
+     *
+     * @return True if member is way
+     * @since 1937
+     */
     public boolean isWay() {
         return member instanceof Way;
@@ -54,8 +62,8 @@
 
     /**
-    *
-    * @return True if member is node
-    * @since 1937
-    */
+     *
+     * @return True if member is node
+     * @since 1937
+     */
     public boolean isNode() {
         return member instanceof Node;
@@ -72,8 +80,8 @@
 
     /**
-    *
-    * @return Member as way
-    * @since 1937
-    */
+     *
+     * @return Member as way
+     * @since 1937
+     */
     public Way getWay() {
         return (Way)member;
@@ -81,8 +89,8 @@
 
     /**
-    *
-    * @return Member as node
-    * @since 1937
-    */
+     *
+     * @return Member as node
+     * @since 1937
+     */
     public Node getNode() {
         return (Node)member;
@@ -90,8 +98,8 @@
 
     /**
-    *
-    * @return Member
-    * @since 1937
-    */
+     *
+     * @return Member
+     * @since 1937
+     */
     public OsmPrimitive getMember() {
         return member;
@@ -101,5 +109,5 @@
     /**
      * Default constructor. Does nothing.
-     * @deprecated Use other constructors because RelationMember class will became immutable
+     * @deprecated Use other constructors because RelationMember class will become immutable
      * in the future
      */
Index: /trunk/src/org/openstreetmap/josm/data/osm/Tag.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Tag.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Tag.java	(revision 2070)
@@ -1,4 +1,9 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.osm;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
 
 /**
@@ -27,4 +32,5 @@
      */
     public Tag(String key) {
+        this();
         this.key = key == null ? "" : key;
     }
Index: /trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java	(revision 2070)
@@ -6,4 +6,5 @@
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -571,5 +572,9 @@
             throw new IllegalStateException(tr("tag collection can't be applied to a primitive because there are keys with multiple values"));
         for (Tag tag: tags) {
-            primitive.put(tag.getKey(), tag.getValue());
+            if (tag.getValue() == null || tag.getValue().equals("")) {
+                primitive.remove(tag.getKey());
+            } else {
+                primitive.put(tag.getKey(), tag.getValue());
+            }
         }
     }
@@ -682,3 +687,23 @@
         return ret;
     }
+
+    /**
+     * Replies the concatenation of all tag values (concatenated by a semicolon)
+     * 
+     * @return the concatenation of all tag values
+     */
+    public String getJoinedValues(String key) {
+        StringBuffer buffer = new StringBuffer();
+        List<String> values = new ArrayList<String>(getValues(key));
+        values.remove("");
+        Collections.sort(values);
+        Iterator<String> iter = values.iterator();
+        while (iter.hasNext()) {
+            buffer.append(iter.next());
+            if (iter.hasNext()) {
+                buffer.append(";");
+            }
+        }
+        return buffer.toString();
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/User.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/User.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/User.java	(revision 2070)
@@ -2,5 +2,7 @@
 package org.openstreetmap.josm.data.osm;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
@@ -11,31 +13,113 @@
  * is only one user object.
  *
- * @author fred
  *
  */
 public class User {
+    static private long uidCounter = 0;
+    /**
+     * the map of known users
+     */
+    private static HashMap<Long,User> userMap = new HashMap<Long,User>();
 
-    /** storage for existing User objects. */
-    private static HashMap<String,User> userMap = new HashMap<String,User>();
 
-    /** the username. */
-    public String name;
+    private static long getNextLocalUid() {
+        synchronized(User.class) {
+            uidCounter--;
+            return uidCounter;
+        }
+    }
 
-    /** the user ID (since API 0.6) */
-    public String uid;
+    /**
+     * Creates a local user with the given name
+     * 
+     * @param name the name
+     */
+    public static User createLocalUser(String name) {
+        User user = new User(getNextLocalUid(), name);
+        userMap.put(user.getId(), user);
+        return user;
+    }
+
+    /**
+     * Creates a user known to the OSM server
+     * 
+     * @param uid  the user id
+     * @param name the name
+     */
+    public static User  createOsmUser(long uid, String name) {
+        User user = new User(uid, name);
+        userMap.put(user.getId(), user);
+        return user;
+    }
+
+
+    /**
+     * Returns the user with user id <code>uid</code> or null if this user doesn't exist
+     * 
+     * @param uid the user id
+     * @return the user; null, if there is no user with  this id
+     */
+    public static User getById(long uid) {
+        return userMap.get(uid);
+    }
+
+    /**
+     * Returns the list of users with name <code>name</code> or the empty list if
+     * no such users exist
+     * 
+     * @param name the user name
+     * @return the list of users with name <code>name</code> or the empty list if
+     * no such users exist
+     */
+    public static List<User> getByName(String name) {
+        name = name == null ? "" : name;
+        List<User> ret = new ArrayList<User>();
+        for (User user: userMap.values()) {
+            if (user.getName().equals(name)) {
+                ret.add(user);
+            }
+        }
+        return ret;
+    }
+
+    /** the user name */
+    private String name;
+    /** the user id */
+    private long uid;
+
+    /**
+     * Replies the user name
+     * 
+     * @return the user name. Never null, but may be the empty string
+     */
+    public String getName() {
+        return name == null ? "" : name;
+    }
+
+    /**
+     * Replies the user id. If this user is known to the OSM server the positive user id
+     * from the server is replied. Otherwise, a negative local value is replied.
+     * 
+     * A negative local is only unique during an editing session. It is lost when the
+     * application is closed and there is no guarantee that a negative local user id is
+     * always bound to a user with the same name.
+     * 
+     */
+    public long getId() {
+        return uid;
+    }
 
     /** private constructor, only called from get method. */
-    private User(String name) {
+    private User(long uid, String name) {
+        this.uid = uid;
         this.name = name;
     }
 
-    /** returns a new or existing User object that represents the given name. */
-    public static User get(String name) {
-        User user = userMap.get(name);
-        if (user == null) {
-            user = new User(name);
-            userMap.put(name, user);
-        }
-        return user;
+    public boolean isOsmUser() {
+        return uid > 0;
+    }
+
+    public boolean isLocalUser() {
+        return uid < 0;
     }
 
@@ -45,5 +129,5 @@
         int result = 1;
         result = prime * result + ((name == null) ? 0 : name.hashCode());
-        result = prime * result + ((uid == null) ? 0 : uid.hashCode());
+        result = prime * result + (int) (uid ^ (uid >>> 32));
         return result;
     }
@@ -63,8 +147,5 @@
         } else if (!name.equals(other.name))
             return false;
-        if (uid == null) {
-            if (other.uid != null)
-                return false;
-        } else if (!uid.equals(other.uid))
+        if (uid != other.uid)
             return false;
         return true;
Index: /trunk/src/org/openstreetmap/josm/data/osm/Way.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 2070)
@@ -119,4 +119,11 @@
 
     /**
+     * Creates a new way with id 0.
+     * 
+     */
+    public Way(){
+    }
+
+    /**
      * Create an identical clone of the argument (including the id).
      * 
@@ -124,24 +131,17 @@
      */
     public Way(Way original) {
+        super(original.getId());
         cloneFrom(original);
     }
 
     /**
-     * Create an empty way without id. Use this only if you set meaningful
-     * values yourself.
-     */
-    public Way() {
-    }
-
-    /**
-     * Create an incomplete Way with a given id.
-     * 
-     * @param id  the id. id > 0 required.
-     */
-    public Way(long id) {
-        // FIXME: shouldn't we check for id > 0?
-        //
-        this.id = id;
-        incomplete = true;
+     * Creates a new way for the given id. If the id > 0, the way is marked
+     * as incomplete.
+     * 
+     * @param id the id. > 0 required
+     * @throws IllegalArgumentException thrown if id < 0
+     */
+    public Way(long id) throws IllegalArgumentException {
+        super(id);
     }
 
@@ -153,6 +153,6 @@
 
     @Override public String toString() {
-        if (incomplete) return "{Way id="+id+" version="+version+" (incomplete)}";
-        return "{Way id="+id+" version="+version+" nodes="+Arrays.toString(nodes.toArray())+"}";
+        if (incomplete) return "{Way id="+getId()+" version="+getVersion()+" (incomplete)}";
+        return "{Way id="+getId()+" version="+getVersion()+" nodes="+Arrays.toString(nodes.toArray())+"}";
     }
 
@@ -170,5 +170,5 @@
         if (o instanceof Relation)
             return 1;
-        return o instanceof Way ? Long.valueOf(id).compareTo(o.id) : -1;
+        return o instanceof Way ? Long.valueOf(getId()).compareTo(o.getId()) : -1;
     }
 
Index: /trunk/src/org/openstreetmap/josm/data/osm/visitor/CollectBackReferencesVisitor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/visitor/CollectBackReferencesVisitor.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/visitor/CollectBackReferencesVisitor.java	(revision 2070)
@@ -46,5 +46,7 @@
     public void visit(Node n) {
         for (Way w : ds.ways) {
-            if (w.deleted || w.incomplete) continue;
+            if (w.isDeleted() || w.incomplete) {
+                continue;
+            }
             for (Node n2 : w.getNodes()) {
                 if (n == n2) {
@@ -72,5 +74,7 @@
         // references.
         for (Relation r : ds.relations) {
-            if (r.incomplete || r.deleted) continue;
+            if (r.incomplete || r.isDeleted()) {
+                continue;
+            }
             for (RelationMember m : r.getMembers()) {
                 if (m.getMember() == p) {
Index: /trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java	(revision 2070)
@@ -236,5 +236,5 @@
         if (myPrimitivesWithDefinedIds.containsKey(other.getId())) {
             P my = myPrimitivesWithDefinedIds.get(other.getId());
-            if (my.version <= other.version) {
+            if (my.getVersion() <= other.getVersion()) {
                 if (! my.isVisible() && other.isVisible()) {
                     // should not happen
@@ -243,5 +243,5 @@
                             + "their primitive with lower version {2} is not visible. "
                             + "Can't deal with this inconsistency. Keeping my primitive. ",
-                            Long.toString(my.getId()),Long.toString(my.version), Long.toString(other.version)
+                            Long.toString(my.getId()),Long.toString(my.getVersion()), Long.toString(other.getVersion())
                     ));
                     merged.put(other, my);
@@ -270,5 +270,5 @@
                     //
                     merged.put(other, my);
-                } else if (my.isDeleted() && ! other.isDeleted() && my.version == other.version) {
+                } else if (my.isDeleted() && ! other.isDeleted() && my.getVersion() == other.getVersion()) {
                     // same version, but my is deleted. Assume mine takes precedence
                     // otherwise too many conflicts when refreshing from the server
@@ -288,14 +288,14 @@
                     my.cloneFrom(other);
                     merged.put(other, my);
-                } else if (! my.isModified() && !other.isModified() && my.version == other.version) {
+                } else if (! my.isModified() && !other.isModified() && my.getVersion() == other.getVersion()) {
                     // both not modified. Keep mine
                     //
                     merged.put(other,my);
-                } else if (! my.isModified() && !other.isModified() && my.version < other.version) {
+                } else if (! my.isModified() && !other.isModified() && my.getVersion() < other.getVersion()) {
                     // my not modified but other is newer. clone other onto mine.
                     //
                     my.cloneFrom(other);
                     merged.put(other,my);
-                } else if (my.isModified() && ! other.isModified() && my.version == other.version) {
+                } else if (my.isModified() && ! other.isModified() && my.getVersion() == other.getVersion()) {
                     // my is same as other but mine is modified
                     // => keep mine
Index: /trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java	(revision 2070)
@@ -27,5 +27,5 @@
  * 
  */
-@Deprecated public class ConditionalOptionPaneUtil {
+public class ConditionalOptionPaneUtil {
     static public final int DIALOG_DISABLED_OPTION = Integer.MIN_VALUE;
 
Index: /trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java	(revision 2070)
@@ -74,5 +74,5 @@
     protected String decorateNameWithId(String name, OsmPrimitive primitive) {
         if (Main.pref.getBoolean("osm-primitives.showid"))
-            return name + tr(" [id: {0}]", primitive.id);
+            return name + tr(" [id: {0}]", primitive.getId());
         else
             return name;
@@ -96,5 +96,5 @@
             }
             if (name == null) {
-                name = node.id == 0 ? tr("node") : ""+ node.id;
+                name = node.getId() == 0 ? tr("node") : ""+ node.getId();
             }
             name += " (" + node.getCoor().latToString(CoordinateFormat.getDefaultFormat()) + ", " + node.getCoor().lonToString(CoordinateFormat.getDefaultFormat()) + ")";
@@ -175,5 +175,5 @@
             }
             if (nameTag == null) {
-                name += Long.toString(relation.id) + ", ";
+                name += Long.toString(relation.getId()) + ", ";
             } else {
                 name += "\"" + nameTag + "\", ";
@@ -197,5 +197,5 @@
      */
     public String format(Changeset changeset) {
-        return tr("Changeset {0}",changeset.id);
+        return tr("Changeset {0}",changeset.getId());
     }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/OsmPrimitivRenderer.java	(revision 2070)
@@ -3,4 +3,6 @@
 
 import java.awt.Component;
+import java.util.ArrayList;
+import java.util.Collections;
 
 import javax.swing.DefaultListCellRenderer;
@@ -62,7 +64,55 @@
             ((JLabel)def).setText(value.getDisplayName(DefaultNameFormatter.getInstance()));
             ((JLabel)def).setIcon(ImageProvider.get(OsmPrimitiveType.from(value)));
+            ((JLabel)def).setToolTipText(buildToolTipText(value));
         }
         return def;
     }
 
+    /**
+     * build the tool tip text for an {@see OsmPrimitive}. It consist of the formatted
+     * key/value pairs for this primitive.
+     * 
+     * @param primitive
+     * @return the tool tip text
+     */
+    public String buildToolTipText(OsmPrimitive primitive) {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<html>");
+        // show the id
+        //
+        sb.append("<strong>id</strong>=")
+        .append(primitive.getId())
+        .append("<br>");
+
+        // show the key/value-pairs, sorted by key
+        //
+        ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
+        Collections.sort(keyList);
+        for (int i = 0; i < keyList.size(); i++) {
+            if (i > 0) {
+                sb.append("<br>");
+            }
+            String key = keyList.get(i);
+            sb.append("<strong>")
+            .append(key)
+            .append("</strong>")
+            .append("=");
+            // make sure long values are split into several rows. Otherwise
+            // the tool tip window can become to wide
+            //
+            String value = primitive.get(key);
+            while(value.length() != 0) {
+                sb.append(value.substring(0,Math.min(50, value.length())));
+                if (value.length() > 50) {
+                    sb.append("<br>");
+                    value = value.substring(50);
+                } else {
+                    value = "";
+                }
+            }
+        }
+        sb.append("</html>");
+        return sb.toString();
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListMergeModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListMergeModel.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListMergeModel.java	(revision 2070)
@@ -76,6 +76,6 @@
     @Override
     public boolean isEqualEntry(Node e1, Node e2) {
-        if (e1.id > 0)
-            return e1.id == e2.id;
+        if (e1.getId() > 0)
+            return e1.getId() == e2.getId();
         else
             return e1 == e2;
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java	(revision 2070)
@@ -166,9 +166,9 @@
         }
 
-        myDeletedState = my.deleted;
-        theirDeletedState = their.deleted;
-
-        myVisibleState = my.visible;
-        theirVisibleState = their.visible;
+        myDeletedState = my.isDeleted();
+        theirDeletedState = their.isDeleted();
+
+        myVisibleState = my.isVisible();
+        theirVisibleState = their.isVisible();
 
         coordMergeDecision = UNDECIDED;
@@ -474,5 +474,5 @@
                         + "<br>"
                         + "Do you want to undelete these nodes too?</html>",
-                        Long.toString(dependent.size()), Long.toString(way.id)),
+                        Long.toString(dependent.size()), Long.toString(way.getId())),
                         tr("Undelete additional nodes?"),
                         JOptionPane.YES_NO_OPTION,
@@ -503,5 +503,5 @@
                         + "<br>"
                         + "Do you want to undelete them too?</html>",
-                        Long.toString(dependent.size()), Long.toString(r.id)),
+                        Long.toString(dependent.size()), Long.toString(r.getId())),
                         tr("Undelete dependent primitives?"),
                         JOptionPane.YES_NO_OPTION,
@@ -535,6 +535,6 @@
         HashMap<Long,OsmPrimitive> candidates = new HashMap<Long,OsmPrimitive>();
         for (Node n : way.getNodes()) {
-            if (n.id > 0 && ! candidates.values().contains(n)) {
-                candidates.put(n.id, n);
+            if (n.getId() > 0 && ! candidates.values().contains(n)) {
+                candidates.put(n.getId(), n);
             }
         }
@@ -545,6 +545,6 @@
         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 (candidates.keySet().contains(their.getId()) && ! their.isVisible()) {
+                toDelete.add(candidates.get(their.getId()));
             }
         }
@@ -572,6 +572,6 @@
         HashMap<Long,OsmPrimitive> candidates = new HashMap<Long, OsmPrimitive>();
         for (RelationMember m : r.getMembers()) {
-            if (m.getMember().id > 0 && !candidates.values().contains(m.getMember())) {
-                candidates.put(m.getMember().id, m.getMember());
+            if (m.getMember().getId() > 0 && !candidates.values().contains(m.getMember())) {
+                candidates.put(m.getMember().getId(), m.getMember());
             }
         }
@@ -583,6 +583,6 @@
         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 (candidates.keySet().contains(their.getId()) && ! their.isVisible()) {
+                toDelete.add(candidates.get(their.getId()));
             }
         }
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java	(revision 2070)
@@ -586,5 +586,5 @@
                             + "it a new id.<br>"
                             + "Do yo agree?</html>",
-                            model.getMyPrimitive().id
+                            model.getMyPrimitive().getId()
                     ),
                     tr("Reset id to 0"),
@@ -625,5 +625,5 @@
                             + "from the dataset.<br>"
                             + "Do you agree?</html>",
-                            model.getMyPrimitive().id
+                            model.getMyPrimitive().getId()
                     ),
                     tr("Remove from dataset"),
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberListMergeModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberListMergeModel.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberListMergeModel.java	(revision 2070)
@@ -25,6 +25,6 @@
     public boolean isEqualEntry(RelationMember e1, RelationMember e2) {
         boolean ret = e1.getRole().equals(e2.getRole());
-        if (e1.getMember().id > 0 ) {
-            ret = ret && (e1.getMember().id == e2.getMember().id);
+        if (e1.getMember().getId() > 0 ) {
+            ret = ret && (e1.getMember().getId() == e2.getMember().getId());
         } else {
             ret = ret && (e1 == e2);
@@ -42,6 +42,6 @@
             public boolean isCellEditable(int row, int column) {
                 switch(column) {
-                case 1: return true;
-                default: return false;
+                    case 1: return true;
+                    default: return false;
                 }
             }
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java	(revision 2070)
@@ -71,5 +71,5 @@
         sb.append("<html>");
         sb.append("<strong>id</strong>=")
-        .append(primitive.id)
+        .append(primitive.getId())
         .append("<br>");
         ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
@@ -197,23 +197,23 @@
         renderForeground(getModel(table), member, row, column, isSelected);
         switch(column) {
-        case 0:
-            renderRowId(row);
-            break;
-        case 1:
-            if (member == null) {
-                renderEmptyRow();
-            } else {
-                renderRole(member);
-            }
-            break;
-        case 2:
-            if (member == null) {
-                renderEmptyRow();
-            } else {
-                renderPrimitive(member);
-            }
-            break;
-        default:
-            // should not happen
+            case 0:
+                renderRowId(row);
+                break;
+            case 1:
+                if (member == null) {
+                    renderEmptyRow();
+                } else {
+                    renderRole(member);
+                }
+                break;
+            case 2:
+                if (member == null) {
+                    renderEmptyRow();
+                } else {
+                    renderPrimitive(member);
+                }
+                break;
+            default:
+                // should not happen
         }
         return this;
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombineWaysConflictResolverDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombineWaysConflictResolverDialog.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombineWaysConflictResolverDialog.java	(revision 2070)
@@ -0,0 +1,306 @@
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+
+public class CombineWaysConflictResolverDialog extends JDialog {
+
+    static private CombineWaysConflictResolverDialog instance;
+
+    public static CombineWaysConflictResolverDialog getInstance() {
+        if (instance == null) {
+            instance = new CombineWaysConflictResolverDialog(Main.parent);
+        }
+        return instance;
+    }
+
+    private JSplitPane spTagConflictTypes;
+    private TagConflictResolver pnlTagConflictResolver;
+    private RelationMemberConflictResolver pnlRelationMemberConflictResolver;
+    private boolean cancelled;
+    private JPanel pnlButtons;
+    private Way targetWay;
+
+
+
+    public Way getTargetWay() {
+        return targetWay;
+    }
+
+    public void setTargetWay(Way targetWay) {
+        this.targetWay = targetWay;
+        updateTitle();
+    }
+
+    protected void updateTitle() {
+        if (targetWay == null) {
+            setTitle(tr("Conflicts when combining ways"));
+            return;
+        }
+        setTitle(
+                tr(
+                        "Conflicts when combining ways - combined way is ''{0}''",
+                        targetWay.getDisplayName(DefaultNameFormatter.getInstance())
+                )
+        );
+    }
+
+    protected void build() {
+        getContentPane().setLayout(new BorderLayout());
+        updateTitle();
+        spTagConflictTypes = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+        spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
+        spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
+        getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
+        addWindowListener(new AdjustDividerLocationAction());
+    }
+
+    protected JPanel buildTagConflictResolverPanel() {
+        pnlTagConflictResolver = new TagConflictResolver();
+        return pnlTagConflictResolver;
+    }
+
+    protected JPanel buildRelationMemberConflictResolverPanel() {
+        pnlRelationMemberConflictResolver = new RelationMemberConflictResolver();
+        return pnlRelationMemberConflictResolver;
+    }
+
+    protected JPanel buildButtonPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
+
+        // -- apply button
+        ApplyAction applyAction = new ApplyAction();
+        pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
+        pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
+        pnl.add(new SideButton(applyAction));
+
+        // -- cancel button
+        CancelAction cancelAction = new CancelAction();
+        pnl.add(new SideButton(cancelAction));
+
+        return pnl;
+    }
+
+    public CombineWaysConflictResolverDialog(Component owner) {
+        super(JOptionPane.getFrameForComponent(owner),true /* modal */);
+        build();
+    }
+
+    public TagConflictResolverModel getTagConflictResolverModel() {
+        return pnlTagConflictResolver.getModel();
+    }
+
+    public RelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
+        return pnlRelationMemberConflictResolver.getModel();
+    }
+
+    protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
+        LinkedList<Command> cmds = new LinkedList<Command>();
+        for (String key : tc.getKeys()) {
+            if (tc.hasUniqueEmptyValue(key)) {
+                if (primitive.get(key) != null) {
+                    cmds.add(new ChangePropertyCommand(primitive, key, null));
+                }
+            } else {
+                String value = tc.getJoinedValues(key);
+                if (!value.equals(primitive.get(key))) {
+                    cmds.add(new ChangePropertyCommand(primitive, key, value));
+                }
+            }
+        }
+        return cmds;
+    }
+
+    public List<Command> buildResolutionCommands(Way targetWay) {
+        List<Command> cmds = new LinkedList<Command>();
+
+        if (getTagConflictResolverModel().getNumDecisions() >0) {
+            TagCollection tc = getTagConflictResolverModel().getResolution();
+            cmds.addAll(buildTagChangeCommand(targetWay, tc));
+        }
+
+        if (getRelationMemberConflictResolverModel().getNumDecisions() >0) {
+            cmds.addAll(getRelationMemberConflictResolverModel().buildResolutionCommands(targetWay));
+        }
+
+        Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(
+                getRelationMemberConflictResolverModel().getModifiedRelations(targetWay)
+        );
+        if (cmd != null) {
+            cmds.add(cmd);
+        }
+        return cmds;
+    }
+
+    protected void prepareDefaultTagDecisions() {
+        TagConflictResolverModel model = getTagConflictResolverModel();
+        for (int i =0; i< model.getRowCount(); i++) {
+            MultiValueResolutionDecision decision = model.getDecision(i);
+            List<String> values = decision.getValues();
+            values.remove("");
+            if (values.size() == 1) {
+                decision.keepOne(values.get(0));
+            } else {
+                decision.keepAll();
+            }
+        }
+        model.refresh();
+    }
+
+    protected void prepareDefaultRelationDecisions() {
+        RelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
+        for (int i=0; i < model.getNumDecisions(); i++) {
+            model.getDecision(i).decide(RelationMemberConflictDecisionType.REPLACE);
+        }
+        model.refresh();
+    }
+
+    public void prepareDefaultDecisions() {
+        prepareDefaultTagDecisions();
+        prepareDefaultRelationDecisions();
+    }
+
+    protected JPanel buildEmptyConflictsPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BorderLayout());
+        pnl.add(new JLabel(tr("No conflicts to resolve")));
+        return pnl;
+    }
+
+    protected void prepareGUIBeforeConflictResolutionStarts() {
+        RelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
+        TagConflictResolverModel tagModel = getTagConflictResolverModel();
+        getContentPane().removeAll();
+        if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
+            spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
+            spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
+            getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
+        } else if (relModel.getNumDecisions() > 0) {
+            getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
+        } else if (tagModel.getNumDecisions() >0) {
+            getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
+        } else {
+            getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
+        }
+        getContentPane().add(pnlButtons, BorderLayout.SOUTH);
+        validate();
+        int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
+        int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
+        if (numTagDecisions > 0 &&  numRelationDecisions > 0) {
+            spTagConflictTypes.setDividerLocation(0.5);
+        }
+    }
+
+    protected void setCancelled(boolean cancelled) {
+        this.cancelled = cancelled;
+    }
+
+    public boolean isCancelled() {
+        return cancelled;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            prepareGUIBeforeConflictResolutionStarts();
+            new WindowGeometry(
+                    getClass().getName()  + ".geometry",
+                    WindowGeometry.centerInWindow(
+                            Main.parent,
+                            new Dimension(600,400)
+                    )
+            ).applySafe(this);
+        } else {
+            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+        }
+        super.setVisible(visible);
+    }
+
+
+    class CancelAction extends AbstractAction {
+
+        public CancelAction() {
+            putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
+            putValue(Action.NAME, tr("Cancel"));
+            putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
+            setEnabled(true);
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            setCancelled(true);
+            setVisible(false);
+        }
+    }
+
+    class ApplyAction extends AbstractAction implements PropertyChangeListener {
+
+        public ApplyAction() {
+            putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
+            putValue(Action.NAME, tr("Apply"));
+            putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
+            updateEnabledState();
+        }
+
+        public void actionPerformed(ActionEvent arg0) {
+            setVisible(false);
+        }
+
+        protected void updateEnabledState() {
+            setEnabled(
+                    pnlTagConflictResolver.getModel().getNumConflicts() == 0
+                    && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0
+            );
+        }
+
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
+                updateEnabledState();
+            }
+            if (evt.getPropertyName().equals(RelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
+                updateEnabledState();
+            }
+        }
+    }
+
+    class AdjustDividerLocationAction extends WindowAdapter {
+        @Override
+        public void windowOpened(WindowEvent e) {
+            int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
+            int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
+            if (numTagDecisions > 0 &&  numRelationDecisions > 0) {
+                spTagConflictTypes.setDividerLocation(0.5);
+            }
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java	(revision 2070)
@@ -127,24 +127,4 @@
 
     /**
-     * Replies the concatenation of all tag values (concatenated by a semicolon)
-     * 
-     * @return the concatenation of all tag values
-     */
-    protected String joinValues() {
-        StringBuffer buffer = new StringBuffer();
-        List<String> values = new ArrayList<String>(tags.getValues());
-        values.remove("");
-        Collections.sort(values);
-        Iterator<String> iter = values.iterator();
-        while (iter.hasNext()) {
-            buffer.append(iter.next());
-            if (iter.hasNext()) {
-                buffer.append(";");
-            }
-        }
-        return buffer.toString();
-    }
-
-    /**
      * Replies the chosen value
      * 
@@ -157,5 +137,5 @@
             case KEEP_ONE: return value;
             case KEEP_NONE: return null;
-            case KEEP_ALL: return joinValues();
+            case KEEP_ALL: return tags.getJoinedValues(getKey());
         }
         // should not happen
@@ -171,4 +151,5 @@
         ArrayList<String> ret = new ArrayList<String>(tags.getValues());
         ret.remove("");
+        ret.remove(null);
         Collections.sort(ret);
         return ret;
@@ -303,5 +284,5 @@
     public Tag getResolution() {
         switch(type) {
-            case KEEP_ALL: return new Tag(getKey(), joinValues());
+            case KEEP_ALL: return new Tag(getKey(), tags.getJoinedValues(getKey()));
             case KEEP_ONE: return new Tag(getKey(),value);
             case KEEP_NONE: return new Tag(getKey(), "");
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java	(revision 2070)
@@ -148,7 +148,7 @@
         String msg = "";
         switch(type) {
-            case NODE: msg= trn("{0} node", "{0} nodes", count, count); break;
-            case WAY: msg= trn("{0} way", "{0} ways", count, count); break;
-            case RELATION: msg= trn("{0} relation", "{0} relations", count, count); break;
+        case NODE: msg= trn("{0} node", "{0} nodes", count, count); break;
+        case WAY: msg= trn("{0} way", "{0} ways", count, count); break;
+        case RELATION: msg= trn("{0} relation", "{0} relations", count, count); break;
         }
         return msg;
@@ -295,5 +295,5 @@
 
         public void propertyChange(PropertyChangeEvent evt) {
-            if (evt.getPropertyName().equals(TagConflictResolverModel.RESOLVED_COMPLETELY_PROP)) {
+            if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
                 updateEnabledState();
             }
@@ -324,5 +324,5 @@
 
     public void propertyChange(PropertyChangeEvent evt) {
-        if (evt.getPropertyName().equals(TagConflictResolverModel.RESOLVED_COMPLETELY_PROP)) {
+        if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
             TagConflictResolverModel model = (TagConflictResolverModel)evt.getSource();
             for (int i=0; i < tpResolvers.getTabCount();i++) {
@@ -446,7 +446,7 @@
                 String msg = "";
                 switch(type) {
-                    case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives,numPrimitives); break;
-                    case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break;
-                    case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break;
+                case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives,numPrimitives); break;
+                case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break;
+                case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break;
                 }
                 text = text.equals("") ? msg : text + ", " + msg;
@@ -472,7 +472,7 @@
                 String msg = "";
                 switch(type) {
-                    case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives,numPrimitives); break;
-                    case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break;
-                    case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break;
+                case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives,numPrimitives); break;
+                case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break;
+                case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break;
                 }
                 text = text.equals("") ? msg : text + ", " + msg;
@@ -491,7 +491,7 @@
 
                 switch(column) {
-                    case 0: renderNumTags(info); break;
-                    case 1: renderFrom(info); break;
-                    case 2: renderTo(info); break;
+                case 0: renderNumTags(info); break;
+                case 1: renderFrom(info); break;
+                case 2: renderTo(info); break;
                 }
             }
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecision.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecision.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecision.java	(revision 2070)
@@ -0,0 +1,116 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType.*;
+
+public class RelationMemberConflictDecision {
+
+    private Relation relation;
+    private int pos;
+    private OsmPrimitive originalPrimitive;
+    private String role;
+    private RelationMemberConflictDecisionType decision;
+
+    public RelationMemberConflictDecision(Relation relation, int pos) throws IllegalArgumentException {
+        if (relation == null)
+            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "relation"));
+        RelationMember member = relation.getMember(pos);
+        if (member == null)
+            throw new IndexOutOfBoundsException(tr("pos {0} is out of range. current number of members: {1}", pos, relation.getMembersCount()));
+        this.relation = relation;
+        this.pos  = pos;
+        this.originalPrimitive = member.getMember();
+        this.role = member.hasRole()? member.getRole() : "";
+        this.decision = UNDECIDED;
+    }
+
+    public Relation getRelation() {
+        return relation;
+    }
+
+    public int getPos() {
+        return pos;
+    }
+
+    public OsmPrimitive getOriginalPrimitive() {
+        return originalPrimitive;
+    }
+
+    public String getRole() {
+        return role;
+    }
+
+    public RelationMemberConflictDecisionType getDecision() {
+        return decision;
+    }
+
+    public void setRole(String role) {
+        this.role = role == null ? "" : role;
+    }
+
+    public void decide(RelationMemberConflictDecisionType decision) {
+        if (decision == null) {
+            decision = UNDECIDED;
+        }
+        this.decision = decision;
+    }
+
+    public boolean isDecided() {
+        return ! UNDECIDED.equals(decision);
+    }
+
+    public boolean matches(Relation relation, int pos) {
+        return this.relation == relation && this.pos == pos;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((decision == null) ? 0 : decision.hashCode());
+        result = prime * result + ((originalPrimitive == null) ? 0 : originalPrimitive.hashCode());
+        result = prime * result + pos;
+        result = prime * result + ((relation == null) ? 0 : relation.hashCode());
+        result = prime * result + ((role == null) ? 0 : role.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        RelationMemberConflictDecision other = (RelationMemberConflictDecision) obj;
+        if (decision == null) {
+            if (other.decision != null)
+                return false;
+        } else if (!decision.equals(other.decision))
+            return false;
+        if (originalPrimitive == null) {
+            if (other.originalPrimitive != null)
+                return false;
+        } else if (!originalPrimitive.equals(other.originalPrimitive))
+            return false;
+        if (pos != other.pos)
+            return false;
+        if (relation == null) {
+            if (other.relation != null)
+                return false;
+        } else if (!relation.equals(other.relation))
+            return false;
+        if (role == null) {
+            if (other.role != null)
+                return false;
+        } else if (!role.equals(other.role))
+            return false;
+        return true;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionEditor.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionEditor.java	(revision 2070)
@@ -0,0 +1,71 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import java.awt.Component;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.EventObject;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JTable;
+import javax.swing.event.CellEditorListener;
+import javax.swing.table.TableCellEditor;
+
+import org.openstreetmap.josm.gui.util.TableCellEditorSupport;
+
+public class RelationMemberConflictDecisionEditor extends JComboBox implements TableCellEditor {
+
+    public RelationMemberConflictDecisionEditor() {
+        setOpaque(true);
+        DefaultComboBoxModel model = new DefaultComboBoxModel();
+        model.addElement(RelationMemberConflictDecisionType.REPLACE);
+        model.addElement(RelationMemberConflictDecisionType.REMOVE);
+        model.addElement(RelationMemberConflictDecisionType.UNDECIDED);
+        setModel(model);
+        setRenderer(new RelationMemberConflictDecisionRenderer());
+        tableCellEditorSupport = new TableCellEditorSupport(this);
+    }
+    /* --------------------------------------------------------------------------------- */
+    /* TableCellEditor                                                                   */
+    /* --------------------------------------------------------------------------------- */
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+        setSelectedItem(value);
+        this.originalValue = (RelationMemberConflictDecisionType)value;
+        return this;
+    }
+
+    private TableCellEditorSupport tableCellEditorSupport;
+    private RelationMemberConflictDecisionType originalValue;
+
+
+    public void addCellEditorListener(CellEditorListener l) {
+        tableCellEditorSupport.addCellEditorListener(l);
+    }
+
+    public void cancelCellEditing() {
+        setSelectedItem(originalValue);
+        tableCellEditorSupport.fireEditingCanceled();
+    }
+
+    public Object getCellEditorValue() {
+        return getSelectedItem();
+    }
+
+    public boolean isCellEditable(EventObject anEvent) {
+        return true;
+    }
+
+    public void removeCellEditorListener(CellEditorListener l) {
+        tableCellEditorSupport.removeCellEditorListener(l);
+    }
+
+    public boolean shouldSelectCell(EventObject anEvent) {
+        return true;
+    }
+
+    public boolean stopCellEditing() {
+        tableCellEditorSupport.fireEditingStopped();
+        return true;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionRenderer.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionRenderer.java	(revision 2070)
@@ -0,0 +1,60 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import java.awt.Component;
+import java.awt.Font;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.ListCellRenderer;
+import javax.swing.UIManager;
+import javax.swing.table.TableCellRenderer;
+
+public class RelationMemberConflictDecisionRenderer extends JLabel implements TableCellRenderer, ListCellRenderer{
+
+    protected void resetTableRenderer() {
+        setOpaque(true);
+        setFont(UIManager.getFont("Table.font"));
+        setBackground(UIManager.getColor("Table.background"));
+        setForeground(UIManager.getColor("Table.foreground"));
+    }
+
+    protected void resetListRenderer() {
+        setOpaque(true);
+        setFont(UIManager.getFont("ComboBox.font"));
+        setBackground(UIManager.getColor("ComboBox.background"));
+        setForeground(UIManager.getColor("ComboBox.foreground"));
+    }
+
+
+    /* --------------------------------------------------------------------------------- */
+    /* TableCellRenderer                                                                 */
+    /* --------------------------------------------------------------------------------- */
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
+            int row, int column) {
+        resetTableRenderer();
+        if (isSelected) {
+            setBackground(UIManager.getColor("Table.selectionBackground"));
+            setForeground(UIManager.getColor("Table.selectionForeground"));
+        }
+        RelationMemberConflictDecisionType decision = (RelationMemberConflictDecisionType)value;
+        RelationMemberConflictDecisionType.prepareLabel(decision, this);
+        return this;
+    }
+
+    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
+            boolean cellHasFocus) {
+        resetListRenderer();
+        if (isSelected) {
+            setBackground(UIManager.getColor("ComboBox.selectionBackground"));
+            setForeground(UIManager.getColor("ComboBox.selectionForeground"));
+        }
+        RelationMemberConflictDecisionType decision = (RelationMemberConflictDecisionType)value;
+        RelationMemberConflictDecisionType.prepareLabel(decision, this);
+        if (RelationMemberConflictDecisionType.UNDECIDED.equals(decision)) {
+            setFont(getFont().deriveFont(Font.ITALIC));
+        }
+        return this;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionType.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionType.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictDecisionType.java	(revision 2070)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Font;
+
+import javax.swing.JLabel;
+
+public enum RelationMemberConflictDecisionType {
+    /**
+     * replace the respective relation member with a member referring
+     * to the new primitive
+     */
+    REPLACE,
+
+    /**
+     * remove the respective relation member
+     */
+    REMOVE,
+
+    /**
+     * not yet decided
+     */
+    UNDECIDED;
+
+
+    static public void prepareLabel(RelationMemberConflictDecisionType decision, JLabel label) {
+        switch(decision) {
+            case REMOVE:
+                label.setText(tr("Remove"));
+                label.setToolTipText(tr("Remove this relation member from the relation"));
+                break;
+            case REPLACE:
+                label.setText(tr("Replace"));
+                label.setToolTipText(tr("Replace the way this member refers with the combined way"));
+                break;
+            case UNDECIDED:
+                label.setText(tr("Undecided"));
+                label.setToolTipText(tr("Not decided yet"));
+                break;
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolver.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolver.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolver.java	(revision 2070)
@@ -0,0 +1,113 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.UIManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.tagging.AutoCompletingTextField;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class RelationMemberConflictResolver extends JPanel {
+
+    private AutoCompletingTextField tfRole;
+    private AutoCompletingTextField tfKey;
+    private AutoCompletingTextField tfValue;
+    private JCheckBox cbTagRelations;
+    private RelationMemberConflictResolverModel model;
+
+    protected void build() {
+        setLayout(new BorderLayout());
+        model=new RelationMemberConflictResolverModel();
+        add (new JScrollPane(new RelationMemberConflictResolverTable(model)), BorderLayout.CENTER);
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
+        pnl.add(buildRoleEditingPanel());
+        pnl.add(buildTagRelationsPanel());
+        add(pnl, BorderLayout.SOUTH);
+    }
+
+    protected JPanel buildRoleEditingPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
+        pnl.add(new JLabel(tr("Role:")));
+        pnl.add(tfRole = new AutoCompletingTextField(10));
+        pnl.add(new JButton(new ApplyRoleAction()));
+        return pnl;
+    }
+
+    protected JPanel buildTagRelationsPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
+        cbTagRelations = new JCheckBox(tr("Tag modified relations with   "));
+        cbTagRelations.addChangeListener(new ToggleTagRelationsAction());
+        pnl.add(cbTagRelations);
+        pnl.add(new JLabel(tr("Key:")));
+        pnl.add(tfKey = new AutoCompletingTextField(10));
+        pnl.add(new JLabel(tr("Value:")));
+        pnl.add(tfValue = new AutoCompletingTextField(10));
+        cbTagRelations.setSelected(false);
+        tfKey.setEnabled(false);
+        tfValue.setEnabled(false);
+        return pnl;
+    }
+
+    public RelationMemberConflictResolver() {
+        build();
+    }
+
+    class ApplyRoleAction extends AbstractAction {
+        public ApplyRoleAction() {
+            putValue(NAME, tr("Apply"));
+            putValue(SMALL_ICON, ImageProvider.get("ok"));
+            putValue(SHORT_DESCRIPTION, tr("Apply this role to all members"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            model.applyRole(tfRole.getText());
+        }
+    }
+
+    class ToggleTagRelationsAction implements ChangeListener {
+        public void stateChanged(ChangeEvent e) {
+            ButtonModel buttonModel = ((AbstractButton)e.getSource()).getModel();
+            tfKey.setEnabled(buttonModel.isSelected());
+            tfValue.setEnabled(buttonModel.isSelected());
+            tfKey.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager.getColor("Panel.background"));
+            tfValue.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager.getColor("Panel.background"));
+        }
+    }
+
+    public RelationMemberConflictResolverModel getModel() {
+        return model;
+    }
+
+    public Command buildTagApplyCommands(Collection<? extends OsmPrimitive> primitives) {
+        if (! cbTagRelations.isSelected()) return null;
+        if (tfKey.getText().trim().equals("")) return null;
+        if (tfValue.getText().trim().equals("")) return null;
+        if (primitives == null || primitives.isEmpty()) return null;
+        return new ChangePropertyCommand(primitives, tfKey.getText(), tfValue.getText());
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverColumnModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverColumnModel.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverColumnModel.java	(revision 2070)
@@ -0,0 +1,72 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+import org.openstreetmap.josm.gui.tagging.AutoCompletingTextField;
+
+public class RelationMemberConflictResolverColumnModel extends DefaultTableColumnModel{
+
+    protected void createColumns() {
+        OsmPrimitivRenderer primitiveRenderer = new OsmPrimitivRenderer();
+        AutoCompletingTextField roleEditor = new AutoCompletingTextField();
+        RelationMemberConflictDecisionRenderer decisionRenderer = new RelationMemberConflictDecisionRenderer();
+        RelationMemberConflictDecisionEditor decisionEditor = new RelationMemberConflictDecisionEditor();
+
+        TableColumn col = null;
+
+        // column 0 - Relation
+        col = new TableColumn(0);
+        col.setHeaderValue("Relation");
+        col.setResizable(true);
+        col.setWidth(100);
+        col.setPreferredWidth(100);
+        col.setCellRenderer(primitiveRenderer);
+        addColumn(col);
+
+        // column 1 - Position
+        col = new TableColumn(1);
+        col.setHeaderValue(tr("Pos."));
+        col.setResizable(true);
+        col.setWidth(40);
+        col.setPreferredWidth(40);
+        col.setMaxWidth(50);
+        addColumn(col);
+
+        // column 2 - Role
+        col = new TableColumn(2);
+        col.setHeaderValue(tr("Role"));
+        col.setResizable(true);
+        col.setCellEditor(roleEditor);
+        col.setWidth(50);
+        col.setPreferredWidth(50);
+        addColumn(col);
+
+        // column 3 - Original Way
+        col = new TableColumn(3);
+        col.setHeaderValue(tr("Orig. Way"));
+        col.setResizable(true);
+        col.setCellRenderer(primitiveRenderer);
+        col.setWidth(100);
+        col.setPreferredWidth(100);
+        addColumn(col);
+        // column 4 - New Way
+        col = new TableColumn(4);
+        col.setHeaderValue(tr("Decision"));
+        col.setResizable(true);
+        col.setCellRenderer(decisionRenderer);
+        col.setCellEditor(decisionEditor);
+        col.setWidth(100);
+        col.setPreferredWidth(100);
+        col.setMaxWidth(100);
+        addColumn(col);
+    }
+
+    public RelationMemberConflictResolverColumnModel() {
+        createColumns();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java	(revision 2070)
@@ -0,0 +1,216 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+
+public class RelationMemberConflictResolverModel extends DefaultTableModel {
+    static public final String NUM_CONFLICTS_PROP = RelationMemberConflictResolverModel.class.getName() + ".numConflicts";
+
+    private List<RelationMemberConflictDecision> decisions;
+    private Collection<Relation> relations;
+    private int numConflicts;
+    private PropertyChangeSupport support;
+
+
+    public int getNumConflicts() {
+        return numConflicts;
+    }
+
+    protected void updateNumConflicts() {
+        int count = 0;
+        for (RelationMemberConflictDecision decision: decisions) {
+            if (!decision.isDecided()) {
+                count++;
+            }
+        }
+        int oldValue = numConflicts;
+        numConflicts = count;
+        if (numConflicts != oldValue) {
+            support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, numConflicts);
+        }
+    }
+
+    public void addPropertyChangeListener(PropertyChangeListener l) {
+        support.addPropertyChangeListener(l);
+    }
+
+    public void removePropertyChangeListener(PropertyChangeListener l) {
+        support.removePropertyChangeListener(l);
+    }
+
+    public RelationMemberConflictResolverModel() {
+        decisions = new ArrayList<RelationMemberConflictDecision>();
+        support = new PropertyChangeSupport(this);
+    }
+
+    @Override
+    public int getRowCount() {
+        if (decisions == null) return 0;
+        return decisions.size();
+    }
+
+    @Override
+    public Object getValueAt(int row, int column) {
+        if (decisions == null) return null;
+
+        RelationMemberConflictDecision d = decisions.get(row);
+        switch(column) {
+            case 0: /* relation */ return d.getRelation();
+            case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
+            case 2: /* role */ return d.getRole();
+            case 3: /* original */ return d.getOriginalPrimitive();
+            case 4: /* decision */ return d.getDecision();
+        }
+        return null;
+    }
+
+    @Override
+    public void setValueAt(Object value, int row, int column) {
+        RelationMemberConflictDecision d = decisions.get(row);
+        switch(column) {
+            case 2: /* role */
+                d.setRole((String)value);
+                break;
+            case 4: /* decision */
+                d.decide((RelationMemberConflictDecisionType)value);
+                refresh();
+                break;
+        }
+        fireTableDataChanged();
+    }
+
+    protected void populate(Relation relation, OsmPrimitive primitive) {
+        for (int i =0; i<relation.getMembersCount();i++) {
+            if (relation.getMember(i).refersTo(primitive)) {
+                decisions.add(new RelationMemberConflictDecision(relation, i));
+            }
+        }
+    }
+
+    public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives) {
+        decisions.clear();
+        for (Relation r : relations) {
+            for (OsmPrimitive p: memberPrimitives) {
+                populate(r,p);
+            }
+        }
+        this.relations = relations;
+        refresh();
+    }
+
+    public RelationMemberConflictDecision getDecision(int row) {
+        return decisions.get(row);
+    }
+
+    public int getNumDecisions() {
+        return  getRowCount();
+    }
+
+    public void refresh() {
+        updateNumConflicts();
+        fireTableDataChanged();
+    }
+
+    public void applyRole(String role) {
+        role = role == null ? "" : role;
+        for (RelationMemberConflictDecision decision : decisions) {
+            decision.setRole(role);
+        }
+        refresh();
+    }
+
+    protected RelationMemberConflictDecision getDecision(Relation relation, int pos) {
+        for(RelationMemberConflictDecision decision: decisions) {
+            if (decision.matches(relation, pos)) return decision;
+        }
+        return null;
+    }
+
+    protected Command buildResolveCommand(Relation relation, OsmPrimitive newPrimitive) {
+        Relation modifiedRelation = new Relation(relation);
+        modifiedRelation.setMembers(null);
+        boolean isChanged = false;
+        for (int i=0; i < relation.getMembersCount(); i++) {
+            RelationMember rm = relation.getMember(i);
+            RelationMember rmNew;
+            RelationMemberConflictDecision decision = getDecision(relation, i);
+            if (decision == null) {
+                modifiedRelation.addMember(rm);
+            } else {
+                switch(decision.getDecision()) {
+                    case REPLACE:
+                        rmNew = new RelationMember(decision.getRole(),newPrimitive);
+                        modifiedRelation.addMember(rmNew);
+                        isChanged |= ! rm.equals(rmNew);
+                        break;
+                    case REMOVE:
+                        isChanged = true;
+                        // do nothing
+                        break;
+                    case UNDECIDED:
+                        // FIXME: this is an error
+                        break;
+                }
+            }
+        }
+        if (isChanged)
+            return new ChangeCommand(relation, modifiedRelation);
+        return null;
+    }
+
+    public List<Command> buildResolutionCommands(OsmPrimitive newPrimitive) {
+        List<Command> command = new LinkedList<Command>();
+        for (Relation relation : relations) {
+            Command cmd = buildResolveCommand(relation, newPrimitive);
+            if (cmd != null) {
+                command.add(cmd);
+            }
+        }
+        return command;
+    }
+
+    protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) {
+        for (int i=0; i < relation.getMembersCount(); i++) {
+            RelationMemberConflictDecision decision = getDecision(relation, i);
+            if (decision == null) {
+                continue;
+            }
+            switch(decision.getDecision()) {
+                case REMOVE: return true;
+                case REPLACE:
+                    if (!relation.getMember(i).getRole().equals(decision.getRole()))
+                        return true;
+                    if (relation.getMember(i).getMember() != newPrimitive)
+                        return true;
+                case UNDECIDED:
+                    // FIXME: handle error
+            }
+        }
+        return false;
+    }
+
+    public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
+        HashSet<Relation> ret = new HashSet<Relation>();
+        for (Relation relation: relations) {
+            if (isChanged(relation, newPrimitive)) {
+                ret.add(relation);
+            }
+        }
+        return ret;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverTable.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverTable.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverTable.java	(revision 2070)
@@ -0,0 +1,110 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+
+public class RelationMemberConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
+
+    private SelectNextColumnCellAction selectNextColumnCellAction;
+    private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
+
+    public RelationMemberConflictResolverTable(RelationMemberConflictResolverModel model) {
+        super(model, new RelationMemberConflictResolverColumnModel());
+        build();
+    }
+
+    protected void build() {
+        setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+
+        // make ENTER behave like TAB
+        //
+        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
+                KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
+
+        // install custom navigation actions
+        //
+        selectNextColumnCellAction = new SelectNextColumnCellAction();
+        selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
+        getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
+        getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
+    }
+
+    /**
+     * Action to be run when the user navigates to the next cell in the table, for instance by
+     * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
+     * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
+     * when the user leaves the last cell in the table</li> <ul>
+     *
+     *
+     */
+    class SelectNextColumnCellAction extends AbstractAction {
+        public void actionPerformed(ActionEvent e) {
+            run();
+        }
+
+        public void run() {
+            int col = getSelectedColumn();
+            int row = getSelectedRow();
+            if (getCellEditor() != null) {
+                getCellEditor().stopCellEditing();
+            }
+
+            if (col == 2 && row < getRowCount() - 1) {
+                row++;
+            } else if (row < getRowCount() - 1) {
+                col = 2;
+                row++;
+            }
+            changeSelection(row, col, false, false);
+            editCellAt(getSelectedRow(), getSelectedColumn());
+            getEditorComponent().requestFocusInWindow();
+        }
+    }
+
+    /**
+     * Action to be run when the user navigates to the previous cell in the table, for instance by
+     * pressing Shift-TAB
+     *
+     */
+    class SelectPreviousColumnCellAction extends AbstractAction {
+
+        public void actionPerformed(ActionEvent e) {
+            run();
+        }
+
+        public void run() {
+            int col = getSelectedColumn();
+            int row = getSelectedRow();
+            if (getCellEditor() != null) {
+                getCellEditor().stopCellEditing();
+            }
+
+            if (col <= 0 && row <= 0) {
+                // change nothing
+            } else if (row > 0) {
+                col = 2;
+                row--;
+            }
+            changeSelection(row, col, false, false);
+            editCellAt(getSelectedRow(), getSelectedColumn());
+            getEditorComponent().requestFocusInWindow();
+        }
+    }
+
+    public void gotoNextDecision() {
+        selectNextColumnCellAction.run();
+    }
+
+    public void gotoPreviousDecision() {
+        selectPreviousColumnCellAction.run();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java	(revision 2070)
@@ -17,14 +17,14 @@
 
 public class TagConflictResolverModel extends DefaultTableModel {
-    static public final String RESOLVED_COMPLETELY_PROP = TagConflictResolverModel.class.getName() + ".resolvedCompletely";
+    static public final String NUM_CONFLICTS_PROP = TagConflictResolverModel.class.getName() + ".numConflicts";
 
     private TagCollection tags;
     private List<String> keys;
     private HashMap<String, MultiValueResolutionDecision> decisions;
-    private boolean resolvedCompletely;
+    private int numConflicts;
     private PropertyChangeSupport support;
 
     public TagConflictResolverModel() {
-        resolvedCompletely= false;
+        numConflicts = 0;
         support = new PropertyChangeSupport(this);
     }
@@ -38,20 +38,21 @@
     }
 
-    protected void setResolvedCompletely(boolean resolvedCompletey) {
-        boolean oldValue = this.resolvedCompletely;
-        this.resolvedCompletely = resolvedCompletey;
-        if (oldValue != this.resolvedCompletely) {
-            support.firePropertyChange(RESOLVED_COMPLETELY_PROP, oldValue, this.resolvedCompletely);
+
+    protected void setNumConflicts(int numConflicts) {
+        int oldValue = this.numConflicts;
+        this.numConflicts = numConflicts;
+        if (oldValue != this.numConflicts) {
+            support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
         }
     }
 
-    protected void refreshResolvedCompletely() {
+    protected void refreshNumConflicts() {
+        int count = 0;
         for (MultiValueResolutionDecision d : decisions.values()) {
             if (!d.isDecided()) {
-                setResolvedCompletely(false);
-                return;
+                count++;
             }
         }
-        setResolvedCompletely(true);
+        setNumConflicts(count);
     }
 
@@ -78,5 +79,5 @@
             decisions.put(key,decision);
         }
-        refreshResolvedCompletely();
+        refreshNumConflicts();
     }
 
@@ -126,5 +127,5 @@
         }
         fireTableDataChanged();
-        refreshResolvedCompletely();
+        refreshNumConflicts();
     }
 
@@ -136,5 +137,13 @@
      */
     public boolean isResolvedCompletely() {
-        return resolvedCompletely;
+        return numConflicts == 0;
+    }
+
+    public int getNumConflicts() {
+        return numConflicts;
+    }
+
+    public int getNumDecisions() {
+        return getRowCount();
     }
 
@@ -146,3 +155,12 @@
         return tc;
     }
+
+    public MultiValueResolutionDecision getDecision(int row) {
+        return decisions.get(keys.get(row));
+    }
+
+    public void refresh() {
+        fireTableDataChanged();
+        refreshNumConflicts();
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java	(revision 2070)
@@ -220,8 +220,8 @@
                 return;
             for (OsmPrimitive primitive: Main.main.getCurrentDataSet().getSelected()) {
-                if (primitive.id == 0) {
+                if (primitive.getId() == 0) {
                     continue;
                 }
-                History h = HistoryDataSet.getInstance().getHistory(primitive.id);
+                History h = HistoryDataSet.getInstance().getHistory(primitive.getId());
                 if (h !=null) {
                     data.add(h);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 2070)
@@ -379,9 +379,7 @@
 
         public void launchEditorForDuplicate(Relation original) {
-            Relation copy = new Relation();
+            Relation copy = new Relation(original.getId());
             copy.cloneFrom(original);
-            // FIXME: id is going to be hidden. How to reset a primitive?
-            //
-            copy.id = 0;
+            copy.clearOsmId();
             copy.setModified(true);
             RelationEditor editor = RelationEditor.getEditor(
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java	(revision 2070)
@@ -201,5 +201,5 @@
             User user = (User)infoObject;
             try {
-                return getBaseUserUrl() + "/" + URLEncoder.encode(user.name, "UTF-8");
+                return getBaseUserUrl() + "/" + URLEncoder.encode(user.getName(), "UTF-8");
             } catch(UnsupportedEncodingException e) {
                 e.printStackTrace();
@@ -251,12 +251,12 @@
             if (count < o.count) return 1;
             if (count > o.count) return -1;
-            if (user== null || user.name == null) return 1;
-            if (o.user == null || o.user.name == null) return -1;
-            return user.name.compareTo(o.user.name);
+            if (user== null || user.getName() == null) return 1;
+            if (o.user == null || o.user.getName() == null) return -1;
+            return user.getName().compareTo(o.user.getName());
         }
 
         public String getName() {
             if (user == null) return null;
-            return user.name;
+            return user.getName();
         }
     }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java	(revision 2070)
@@ -361,5 +361,5 @@
          */
         protected void rememberChildRelationsToDownload(Relation parent) {
-            downloadedRelationIds.add(parent.id);
+            downloadedRelationIds.add(parent.getId());
             for (RelationMember member: parent.getMembers()) {
                 if (member.isRelation()) {
@@ -403,10 +403,10 @@
                 while(! relationsToDownload.isEmpty() && !cancelled) {
                     Relation r = relationsToDownload.pop();
-                    if (r.id == 0) {
+                    if (r.getId() == 0) {
                         continue;
                     }
                     rememberChildRelationsToDownload(r);
                     progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
-                    OsmServerObjectReader reader = new OsmServerObjectReader(r.id, OsmPrimitiveType.RELATION,
+                    OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
                             true);
                     DataSet dataSet = null;
@@ -514,9 +514,9 @@
                 while(it.hasNext() && !cancelled) {
                     Relation r = it.next();
-                    if (r.id == 0) {
+                    if (r.getId() == 0) {
                         continue;
                     }
                     progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance())));
-                    OsmServerObjectReader reader = new OsmServerObjectReader(r.id, OsmPrimitiveType.RELATION,
+                    OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION,
                             true);
                     DataSet dataSet = reader.parseOsm(progressMonitor
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableCellRenderer.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableCellRenderer.java	(revision 2070)
@@ -35,5 +35,5 @@
         StringBuilder sb = new StringBuilder();
         sb.append("<html>");
-        sb.append("<strong>id</strong>=").append(primitive.id).append("<br>");
+        sb.append("<strong>id</strong>=").append(primitive.getId()).append("<br>");
         ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
         Collections.sort(keyList);
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 2070)
@@ -198,8 +198,8 @@
     public void updateMemberReferences(DataSet ds) {
         for (RelationMember member : members) {
-            if (member.getMember().id == 0) {
+            if (member.getMember().getId() == 0) {
                 continue;
             }
-            OsmPrimitive primitive = ds.getPrimitiveById(member.getMember().id);
+            OsmPrimitive primitive = ds.getPrimitiveById(member.getMember().getId());
             if (primitive != null) {
                 member.member = primitive;
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ParentRelationLoadingTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ParentRelationLoadingTask.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ParentRelationLoadingTask.java	(revision 2070)
@@ -70,5 +70,5 @@
      * @exception IllegalArgumentException thrown if child is null
      * @exception IllegalArgumentException thrown if layer is null
-     * @exception IllegalArgumentException thrown if child.id == 0
+     * @exception IllegalArgumentException thrown if child.getId() == 0
      */
     public ParentRelationLoadingTask(Relation child, OsmDataLayer layer, boolean full, PleaseWaitProgressMonitor monitor ) {
@@ -78,6 +78,6 @@
         if (layer == null)
             throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "layer"));
-        if (child.id == 0)
-            throw new IllegalArgumentException(tr("child.id >0 expected. Got {1}", child.id));
+        if (child.getId() == 0)
+            throw new IllegalArgumentException(tr("child.getId() >0 expected. Got {1}", child.getId()));
         referrers = null;
         this.layer = layer;
@@ -150,5 +150,5 @@
         parents.clear();
         for (Relation parent : referrers.relations) {
-            parents.add((Relation)getLayer().data.getPrimitiveById(parent.id));
+            parents.add((Relation)getLayer().data.getPrimitiveById(parent.getId()));
         }
         if (continuation != null) {
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ReferringRelationsBrowserModel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ReferringRelationsBrowserModel.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ReferringRelationsBrowserModel.java	(revision 2070)
@@ -89,5 +89,5 @@
 
     public boolean canReload() {
-        return relation != null && relation.id > 0;
+        return relation != null && relation.getId() > 0;
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationDialogManager.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationDialogManager.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationDialogManager.java	(revision 2070)
@@ -90,5 +90,5 @@
         @Override
         public String toString() {
-            return "[Context: layer=" + layer.getName() + ",relation=" + relation.id + "]";
+            return "[Context: layer=" + layer.getName() + ",relation=" + relation.getId() + "]";
         }
     }
@@ -103,5 +103,4 @@
         openDialogs = new HashMap<DialogContext, RelationEditor>();
     }
-
     /**
      * Register the relation editor for a relation managed by a
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationEditor.java	(revision 2070)
@@ -117,8 +117,8 @@
         if (getRelation() == null) {
             setTitle(tr("Create new relation in layer ''{0}''", layer.getName()));
-        } else if (getRelation().id == 0) {
+        } else if (getRelation().getId() == 0) {
             setTitle(tr("Edit new relation in layer ''{0}''", layer.getName()));
         } else {
-            setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.id, layer.getName()));
+            setTitle(tr("Edit relation #{0} in layer ''{1}''", relation.getId(), layer.getName()));
         }
     }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationTree.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationTree.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/RelationTree.java	(revision 2070)
@@ -91,5 +91,5 @@
             TreePath path  = event.getPath();
             Relation parent = (Relation)event.getPath().getLastPathComponent();
-            if (! parent.incomplete || parent.id == 0)
+            if (! parent.incomplete || parent.getId() == 0)
                 // we don't load complete  or new relations
                 return;
@@ -154,5 +154,5 @@
         protected void realRun() throws SAXException, IOException, OsmTransferException {
             try {
-                OsmServerObjectReader reader = new OsmServerObjectReader(relation.id, OsmPrimitiveType.from(relation), true /* full load */);
+                OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true /* full load */);
                 ds = reader.parseOsm(progressMonitor
                         .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/SelectionTableCellRenderer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/SelectionTableCellRenderer.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/relation/SelectionTableCellRenderer.java	(revision 2070)
@@ -60,5 +60,5 @@
         sb.append("<html>");
         sb.append("<strong>id</strong>=")
-        .append(primitive.id)
+        .append(primitive.getId())
         .append("<br>");
         ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
Index: /trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java	(revision 2070)
@@ -68,5 +68,5 @@
         if (primitive == null)
             throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "primitive"));
-        return add(primitive.id, OsmPrimitiveType.from(primitive));
+        return add(primitive.getId(), OsmPrimitiveType.from(primitive));
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/AutoCompletingTextField.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/AutoCompletingTextField.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/AutoCompletingTextField.java	(revision 2070)
@@ -7,8 +7,16 @@
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.EventObject;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.logging.Logger;
 
 import javax.swing.ComboBoxEditor;
+import javax.swing.JTable;
 import javax.swing.JTextField;
+import javax.swing.event.CellEditorListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.table.TableCellEditor;
 import javax.swing.text.AttributeSet;
 import javax.swing.text.BadLocationException;
@@ -17,4 +25,5 @@
 
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.util.TableCellEditorSupport;
 
 /**
@@ -27,5 +36,5 @@
  *
  */
-public class AutoCompletingTextField extends JTextField implements ComboBoxEditor {
+public class AutoCompletingTextField extends JTextField implements ComboBoxEditor, TableCellEditor {
 
     static private Logger logger = Logger.getLogger(AutoCompletingTextField.class.getName());
@@ -133,4 +142,5 @@
                 }
         );
+        tableCellEditorSupport = new TableCellEditorSupport(this);
     }
 
@@ -187,4 +197,54 @@
     }
 
-
+    /* ------------------------------------------------------------------------------------ */
+    /* TableCellEditor interface                                                            */
+    /* ------------------------------------------------------------------------------------ */
+
+    private TableCellEditorSupport tableCellEditorSupport;
+    private String originalValue;
+
+    public void addCellEditorListener(CellEditorListener l) {
+        tableCellEditorSupport.addCellEditorListener(l);
+    }
+
+    protected void rememberOriginalValue(String value) {
+        this.originalValue = value;
+    }
+
+    protected void restoreOriginalValue() {
+        setText(originalValue);
+    }
+
+    public void removeCellEditorListener(CellEditorListener l) {
+        tableCellEditorSupport.removeCellEditorListener(l);
+    }
+    public void cancelCellEditing() {
+        restoreOriginalValue();
+        tableCellEditorSupport.fireEditingCanceled();
+
+    }
+
+    public Object getCellEditorValue() {
+        return getText();
+    }
+
+    public boolean isCellEditable(EventObject anEvent) {
+        return true;
+    }
+
+
+    public boolean shouldSelectCell(EventObject anEvent) {
+        return true;
+    }
+
+    public boolean stopCellEditing() {
+        tableCellEditorSupport.fireEditingStopped();
+        return true;
+    }
+
+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+        setText( value == null ? "" : value.toString());
+        rememberOriginalValue(getText());
+        return this;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/util/TableCellEditorSupport.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/util/TableCellEditorSupport.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/gui/util/TableCellEditorSupport.java	(revision 2070)
@@ -0,0 +1,52 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.event.CellEditorListener;
+import javax.swing.event.ChangeEvent;
+
+public class TableCellEditorSupport {
+    private Object editor;
+    private LinkedList<CellEditorListener> listeners;
+
+    public TableCellEditorSupport(Object editor) {
+        this.editor = editor;
+        listeners = new LinkedList<CellEditorListener>();
+    }
+
+    protected List<CellEditorListener> getListeners() {
+        synchronized (this) {
+            return new ArrayList<CellEditorListener>(listeners);
+        }
+    }
+
+    public void addCellEditorListener(CellEditorListener l) {
+        synchronized (this) {
+            if (l != null && ! listeners.contains(l)) {
+                listeners.add(l);
+            }
+        }
+    }
+    public void removeCellEditorListener(CellEditorListener l) {
+        synchronized (this) {
+            if (l != null &&listeners.contains(l)) {
+                listeners.remove(l);
+            }
+        }
+    }
+
+    public void fireEditingCanceled() {
+        for (CellEditorListener listener: getListeners()) {
+            listener.editingCanceled(new ChangeEvent(editor));
+        }
+    }
+
+    public void fireEditingStopped() {
+        for (CellEditorListener listener: getListeners()) {
+            listener.editingStopped(new ChangeEvent(editor));
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/DiffResultReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/DiffResultReader.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/DiffResultReader.java	(revision 2070)
@@ -94,10 +94,9 @@
     public void visit(Node n) {
         String key = "node:" + (newIdMap.containsKey(n) ? newIdMap.get(n) : n.getId());
-        System.out.println("key: "+key);
         Long[] nv = versions.get(key);
         if (nv != null) {
             processed.add(n);
             if (!n.isDeleted()) {
-                n.id = nv[0]; n.version = nv[1].intValue();
+                n.setOsmId(nv[0], nv[1].intValue());
             }
         }
@@ -109,5 +108,5 @@
             processed.add(w);
             if (!w.isDeleted()) {
-                w.id = nv[0]; w.version = nv[1].intValue();
+                w.setOsmId(nv[0], nv[1].intValue());
             }
         }
@@ -119,5 +118,5 @@
             processed.add(r);
             if (!r.isDeleted()) {
-                r.id = nv[0]; r.version = nv[1].intValue();
+                r.setOsmId(nv[0], nv[1].intValue());
             }
         }
Index: /trunk/src/org/openstreetmap/josm/io/FileImporter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/FileImporter.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/FileImporter.java	(revision 2070)
@@ -21,5 +21,5 @@
     }
 
-    public void importData(File file) throws IOException {
+    public void importData(File file) throws IOException, IllegalDataException {
         throw new IOException(tr("Could not read \"{0}\"", file.getName()));
     }
Index: /trunk/src/org/openstreetmap/josm/io/IllegalDataException.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/IllegalDataException.java	(revision 2070)
+++ /trunk/src/org/openstreetmap/josm/io/IllegalDataException.java	(revision 2070)
@@ -0,0 +1,26 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+public class IllegalDataException extends Exception{
+
+    public IllegalDataException() {
+        super();
+        // TODO Auto-generated constructor stub
+    }
+
+    public IllegalDataException(String message, Throwable cause) {
+        super(message, cause);
+        // TODO Auto-generated constructor stub
+    }
+
+    public IllegalDataException(String message) {
+        super(message);
+        // TODO Auto-generated constructor stub
+    }
+
+    public IllegalDataException(Throwable cause) {
+        super(cause);
+        // TODO Auto-generated constructor stub
+    }
+
+}
Index: /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java	(revision 2070)
@@ -204,8 +204,8 @@
         remember(relation.getId(), OsmPrimitiveType.RELATION);
         for (RelationMember member : relation.getMembers()) {
-            if (OsmPrimitiveType.from(member.member).equals(OsmPrimitiveType.RELATION)) {
+            if (OsmPrimitiveType.from(member.getMember()).equals(OsmPrimitiveType.RELATION)) {
                 // avoid infinite recursion in case of cyclic dependencies in relations
                 //
-                if (relations.contains(member.member.getId())) {
+                if (relations.contains(member.getMember().getId())) {
                     continue;
                 }
@@ -325,6 +325,8 @@
         progressMonitor.subTask(tr("Downloading OSM data..."));
         try {
-            final OsmReader osm = OsmReader.parseDataSetOsm(in, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
-            merge(osm.getDs());
+
+            merge(
+                    OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false))
+            );
         } catch(Exception e) {
             throw new OsmTransferException(e);
@@ -348,6 +350,7 @@
         progressMonitor.subTask(tr("Downloading OSM data..."));
         try {
-            final OsmReader osm = OsmReader.parseDataSetOsm(in, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
-            merge(osm.getDs());
+            merge(
+                    OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false))
+            );
         } catch(Exception e) {
             throw new OsmTransferException(e);
Index: /trunk/src/org/openstreetmap/josm/io/OsmApi.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmApi.java	(revision 2070)
@@ -233,6 +233,5 @@
         try {
             ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/create", toXml(osm, true),monitor);
-            osm.id = Long.parseLong(ret.trim());
-            osm.version = 1;
+            osm.setOsmId(Long.parseLong(ret.trim()), 1);
         } catch(NumberFormatException e){
             throw new OsmTransferException(tr("unexpected format of id replied by the server, got ''{0}''", ret));
@@ -258,5 +257,5 @@
             try {
                 ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.getId(), toXml(osm, true), monitor);
-                osm.version = Integer.parseInt(ret.trim());
+                osm.setOsmId(osm.getId(), Integer.parseInt(ret.trim()));
             } catch(NumberFormatException e) {
                 throw new OsmTransferException(tr("unexpected format of new version of modified primitive ''{0}'', got ''{1}''", osm.getId(), ret));
@@ -341,6 +340,5 @@
                     return;
                 }
-                changeset.id = this.changeset.getId();
-                this.changeset.cloneFrom(changeset);
+                this.changeset.setKeys(changeset.getKeys());
                 progressMonitor.setCustomText(tr("Updating changeset {0}...", changeset.getId()));
                 sendRequest(
@@ -473,5 +471,4 @@
      */
     private String sendRequest(String requestMethod, String urlSuffix,String requestBody, ProgressMonitor monitor) throws OsmTransferException {
-
         StringBuffer responseBody = new StringBuffer();
 
Index: /trunk/src/org/openstreetmap/josm/io/OsmBzip2Importer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmBzip2Importer.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmBzip2Importer.java	(revision 2070)
@@ -11,5 +11,4 @@
 import org.apache.tools.bzip2.CBZip2InputStream;
 import org.openstreetmap.josm.actions.ExtensionFileFilter;
-import org.xml.sax.SAXException;
 
 public class OsmBzip2Importer extends OsmImporter {
@@ -21,22 +20,14 @@
 
     @Override
-    public void importData(File file) throws IOException {
+    public void importData(File file) throws IOException, IllegalDataException {
         BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
         int b = bis.read();
-        if (b != 'B') {
+        if (b != 'B')
             throw new IOException(tr("Invalid bz2 file."));
-        }
         b = bis.read();
-        if (b != 'Z') {
+        if (b != 'Z')
             throw new IOException(tr("Invalid bz2 file."));
-        }
         CBZip2InputStream in = new CBZip2InputStream(bis);
-
-        try {
-            importData(in, file);
-        } catch (SAXException e) {
-            e.printStackTrace();
-            throw new IOException(tr("Could not read \"{0}\"", file.getName()));
-        }
+        importData(in, file);
     }
 }
Index: /trunk/src/org/openstreetmap/josm/io/OsmGzipImporter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmGzipImporter.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmGzipImporter.java	(revision 2070)
@@ -10,5 +10,4 @@
 
 import org.openstreetmap.josm.actions.ExtensionFileFilter;
-import org.xml.sax.SAXException;
 
 public class OsmGzipImporter extends OsmImporter {
@@ -19,12 +18,7 @@
 
     @Override
-    public void importData(File file) throws IOException {
+    public void importData(File file) throws IOException, IllegalDataException {
         GZIPInputStream in = new GZIPInputStream(new FileInputStream(file));
-        try {
-            importData(in, file);
-        } catch (SAXException e) {
-            e.printStackTrace();
-            throw new IOException(tr("Could not read \"{0}\"", file.getName()));
-        }
+        importData(in, file);
     }
 }
Index: /trunk/src/org/openstreetmap/josm/io/OsmImporter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmImporter.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmImporter.java	(revision 2070)
@@ -4,5 +4,4 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.HeadlessException;
 import java.io.File;
 import java.io.FileInputStream;
@@ -18,5 +17,4 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.xml.sax.SAXException;
 
 public class OsmImporter extends FileImporter {
@@ -30,23 +28,16 @@
     }
 
-    @Override public void importData(File file) throws IOException {
+    @Override public void importData(File file) throws IOException, IllegalDataException {
         try {
             FileInputStream in = new FileInputStream(file);
             importData(in, file);
-        } catch (HeadlessException e) {
-            e.printStackTrace();
-            throw new IOException(tr("Could not read \"{0}\"", file.getName()));
         } catch (FileNotFoundException e) {
             e.printStackTrace();
             throw new IOException(tr("File \"{0}\" does not exist", file.getName()));
-        } catch (SAXException e) {
-            e.printStackTrace();
-            throw new IOException(tr("Parsing file \"{0}\" failed", file.getName()));
         }
     }
 
-    protected void importData(InputStream in, File associatedFile) throws SAXException, IOException {
-        OsmReader osm = OsmReader.parseDataSetOsm(in, NullProgressMonitor.INSTANCE);
-        DataSet dataSet = osm.getDs();
+    protected void importData(InputStream in, File associatedFile) throws IllegalDataException {
+        DataSet dataSet = OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE);
         final OsmDataLayer layer = new OsmDataLayer(dataSet, associatedFile.getName(), associatedFile);
         // FIXME: remove UI stuff from IO subsystem
Index: /trunk/src/org/openstreetmap/josm/io/OsmReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmReader.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmReader.java	(revision 2070)
@@ -1,8 +1,6 @@
-// License: GPL. Copyright 2007 by Immanuel Scholz and others
 package org.openstreetmap.josm.io;
 
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -14,5 +12,4 @@
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.logging.Logger;
 
@@ -34,4 +31,5 @@
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
@@ -40,10 +38,4 @@
  * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
  *
- * Reading process takes place in three phases. During the first phase (including xml parse),
- * all nodes are read and stored. Other information than nodes are stored in a raw list
- *
- * The second phase read all ways out of the remaining objects in the raw list.
- *
- * @author Imi
  */
 public class OsmReader {
@@ -54,10 +46,26 @@
      */
     private DataSet ds = new DataSet();
-    public DataSet getDs() { return ds; }
-
-    /**
-     * All read nodes after phase 1.
-     */
-    private Map<Long, Node> nodes = new HashMap<Long, Node>();
+
+    /**
+     * Replies the parsed data set
+     * 
+     * @return the parsed data set
+     */
+    public DataSet getDataSet() {
+        return ds;
+    }
+
+    /** the map from external ids to read OsmPrimitives. External ids are
+     * longs too, but in contrast to internal ids negative values are used
+     * to identify primitives unknown to the OSM server
+     * 
+     * The keys are strings composed as follows
+     * <ul>
+     *   <li>"n" + id  for nodes</li>
+     *   <li>"w" + id  for nodes</li>
+     *   <li>"r" + id  for nodes</li>
+     * </ul>
+     */
+    private Map<String, OsmPrimitive> externalIdMap = new HashMap<String, OsmPrimitive>();
 
 
@@ -69,47 +77,56 @@
      */
     private OsmReader() {
+        externalIdMap = new HashMap<String, OsmPrimitive>();
     }
 
     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 int version = 0;
         public LatLon latlon = new LatLon(0,0);
+        private OsmPrimitive primitive;
 
         public void copyTo(OsmPrimitive osm) {
-            osm.id = id;
-            osm.setKeys(keys);
-            osm.modified = modified;
-            osm.setSelected(selected);
-            osm.deleted = deleted;
+            osm.setModified(modified);
+            osm.setDeleted(deleted);
+            //  id < 0 possible if read from a file
+            if (id <= 0) {
+                osm.clearOsmId();
+            } else {
+                osm.setOsmId(id, version);
+            }
             osm.setTimestamp(timestamp);
             osm.user = user;
-            osm.visible = visible;
-            osm.version = version;
+            osm.setVisible(visible);
             osm.mappaintStyle = null;
         }
 
         public Node createNode() {
-            Node node = new Node(latlon);
+            Node node = new Node();
+            node.setCoor(latlon);
             copyTo(node);
+            primitive = node;
             return node;
         }
 
         public Way createWay() {
-            Way way = new Way(id);
+            Way way = new Way();
             copyTo(way);
+            primitive = way;
             return way;
         }
-
         public Relation createRelation() {
-            Relation rel = new Relation(id);
-            copyTo(rel);
-            return rel;
+            Relation relation= new Relation();
+            copyTo(relation);
+            primitive = relation;
+            return relation;
+        }
+
+        public void rememberTag(String key, String value) {
+            primitive.put(key, value);
         }
     }
@@ -128,12 +145,70 @@
      * Data structure for the remaining way objects
      */
-    private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
+    private Map<Long, Collection<Long>> ways = new HashMap<Long, Collection<Long>>();
 
     /**
      * Data structure for relation objects
      */
-    private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
+    private Map<Long, Collection<RelationMemberData>> relations = new HashMap<Long, Collection<RelationMemberData>>();
+
+    static public class OsmDataParsingException extends SAXException {
+        private int columnNumber;
+        private int lineNumber;
+
+        public OsmDataParsingException() {
+            super();
+        }
+
+        public OsmDataParsingException(Exception e) {
+            super(e);
+        }
+
+        public OsmDataParsingException(String message, Exception e) {
+            super(message, e);
+        }
+
+        public OsmDataParsingException(String message) {
+            super(message);
+        }
+
+        public OsmDataParsingException rememberLocation(Locator locator) {
+            if (locator == null) return this;
+            this.columnNumber = locator.getColumnNumber();
+            this.lineNumber = locator.getLineNumber();
+            return this;
+        }
+
+        @Override
+        public String getMessage() {
+            String msg = super.getMessage();
+            if (lineNumber == 0 && columnNumber == 0)
+                return msg;
+            if (msg == null) {
+                msg = getClass().getName();
+            }
+            msg = msg + " " + tr("(at line {0}, column {1})", lineNumber, columnNumber);
+            return msg;
+        }
+
+        public int getColumnNumber() {
+            return columnNumber;
+        }
+
+        public int getLineNumber() {
+            return lineNumber;
+        }
+    }
 
     private class Parser extends DefaultHandler {
+        private Locator locator;
+
+        @Override
+        public void setDocumentLocator(Locator locator) {
+            this.locator = locator;
+        }
+
+        protected void throwException(String msg) throws OsmDataParsingException{
+            throw new OsmDataParsingException(msg).rememberLocation(locator);
+        }
         /**
          * The current osm primitive to be read.
@@ -143,107 +218,124 @@
 
         @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);
+            if (qName.equals("osm")) {
+                if (atts == null) {
+                    throwException(tr("Missing mandatory attribute ''{0}'' of XML element {1}", "version", "osm"));
+                }
+                String v = atts.getValue("version");
+                if (v == null) {
+                    throwException(tr("Missing mandatory attribute ''{0}''", "version"));
+                }
+                if (!(v.equals("0.5") || v.equals("0.6"))) {
+                    throwException(tr("Unsupported 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;
                     }
-
-                    // ---- PARSING NODES AND WAYS ----
-
-                } 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(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();
-                    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.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")) {
-                    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());
+                    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);
+                } else {
+                    throwException(tr(
+                            "Missing manadatory attributes on element ''bounds''. Got minlon=''{0}'',minlat=''{1}00,maxlon=''{3}'',maxlat=''{4}'', origin=''{5}''",
+                            minlon, minlat, maxlon, maxlat, origin
+                    ));
+                }
+
+                // ---- PARSING NODES AND WAYS ----
+
+            } else if (qName.equals("node")) {
+                current = new OsmPrimitiveData();
+                current.latlon = new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon"));
+                readCommon(atts, current);
+                Node n = current.createNode();
+                externalIdMap.put("n"+current.id, n);
+            } else if (qName.equals("way")) {
+                current = new OsmPrimitiveData();
+                readCommon(atts, current);
+                Way w = current.createWay();
+                externalIdMap.put("w"+current.id, w);
+                ways.put(current.id, new ArrayList<Long>());
+            } else if (qName.equals("nd")) {
+                Collection<Long> list = ways.get(current.id);
+                if (list == null) {
+                    throwException(
+                            tr("found XML element <nd> element not as direct child of element <way>")
+                    );
+                }
+                if (atts.getValue("ref") == null) {
+                    throwException(
+                            tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}", "ref", current.id)
+                    );
+                }
+                long id = getLong(atts, "ref");
+                if (id == 0) {
+                    throwException(
+                            tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}", id)
+                    );
+                }
+                list.add(id);
+
+                // ---- PARSING RELATIONS ----
+
+            } else if (qName.equals("relation")) {
+                current = new OsmPrimitiveData();
+                readCommon(atts, current);
+                Relation r = current.createRelation();
+                externalIdMap.put("r"+current.id, r );
+                relations.put(current.id, new LinkedList<RelationMemberData>());
+            } else if (qName.equals("member")) {
+                Collection<RelationMemberData> list = relations.get(current.id);
+                if (list == null) {
+                    throwException(
+                            tr("Found XML element <member> not as direct child of element <relation>")
+                    );
+                }
+                RelationMemberData emd = new RelationMemberData();
+                String value = atts.getValue("ref");
+                if (value == null) {
+                    throwException(tr("Missing attribute ''ref'' on member in relation {0}",current.id));
+                }
+                try {
+                    emd.id = Long.parseLong(value);
+                } catch(NumberFormatException e) {
+                    throwException(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) {
+                    throwException(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"))) {
+                    throwException(tr("Illegal value for attribute ''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.role = value;
+
+                if (emd.id == 0) {
+                    throwException(tr("Incomplete <member> specification with ref=0"));
+                }
+
+                list.add(emd);
+
+                // ---- PARSING TAGS (applicable to all objects) ----
+
+            } else if (qName.equals("tag")) {
+                String key = atts.getValue("k");
+                String value = atts.getValue("v");
+                current.rememberTag(key, value);
             }
         }
@@ -252,86 +344,111 @@
             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) {
+
+        private User createUser(String uid, String name) throws SAXException {
+            if (uid == null) {
+                if (name == null)
+                    return null;
+                return User.createLocalUser(name);
+            }
             try {
-                current.version = Integer.parseInt(version);
+                long id = Long.parseLong(uid);
+                return User.createOsmUser(id, name);
             } 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;
-        return null;
-    }
-
-    protected void createWays() {
-        for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
-            Way w = new Way(e.getKey().id);
+                throwException(tr("Illegal value for attribute ''uid''. Got ''{0}''", uid));
+            }
+            return null;
+        }
+        /**
+         * 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) {
+                throwException(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");
+            // uid attribute added in 0.6 API
+            String uid = atts.getValue("uid");
+            current.user = createUser(uid, user);
+
+            // 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) {
+                    throwException(tr("Illegal value for attribute ''version'' on OSM primitive with id {0}. Got {1}", Long.toString(current.id), version));
+                }
+                if (current.version <= 0) {
+                    throwException(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")) {
+                    throwException(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 name) throws SAXException {
+            String value = atts.getValue(name);
+            if (value == null) {
+                throwException(tr("Missing required attribute ''{0}''.",name));
+            }
+            try {
+                return Long.parseLong(value);
+            } catch(NumberFormatException e) {
+                throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''",name, value));
+            }
+            return 0; // should not happen
+        }
+    }
+
+
+    /**
+     * Processes the ways after parsing. Rebuilds the list of nodes of each way and
+     * adds the way to the dataset
+     * 
+     * @throws IllegalDataException thrown if a data integrity problem is detected
+     */
+    protected void processWaysAfterParsing() throws IllegalDataException{
+        for (Long externalWayId: ways.keySet()) {
+            Way w = (Way)externalIdMap.get("w" + externalWayId);
             boolean incomplete = false;
             List<Node> wayNodes = new ArrayList<Node>();
-            for (long id : e.getValue()) {
-                Node n = findNode(id);
+            for (long id : ways.get(externalWayId)) {
+                Node n = (Node)externalIdMap.get("n" +id);
                 if (n == null) {
+                    if (id <= 0)
+                        throw new IllegalDataException (
+                                tr(
+                                        "way with external id ''{0}'' includes missing node with external id ''{1}''",
+                                        externalWayId,
+                                        id
+                                )
+                        );
                     n = new Node(id);
                     n.incomplete = true;
@@ -343,10 +460,8 @@
             if (incomplete) {
                 logger.warning(tr("marked way {0} with {1} nodes incomplete because at least one node was missing in the " +
-                		"loaded data and is therefore incomplete too", e.getKey().id, w.getNodesCount()));
-                e.getKey().copyTo(w);
+                        "loaded data and is therefore incomplete too", externalWayId, w.getNodesCount()));
                 w.incomplete = true;
                 ds.addPrimitive(w);
             } else {
-                e.getKey().copyTo(w);
                 w.incomplete = false;
                 ds.addPrimitive(w);
@@ -356,140 +471,112 @@
 
     /**
-     * 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 already parsed
-     * ways.
-     *
-     * @param id
-     * @return way object or null
-     */
-    private Way findWay(long id) {
-        for (Way way : ds.ways)
-            if (way.id == id)
-                return way;
-        return null;
-    }
-
-    /**
-     * Return the Relation object with the given id, or null if it doesn't
-     * exist yet. This method only looks at relations in the already parsed
-     * relations.
-     *
-     * @param id
-     * @return relation object or null
-     */
-    private Relation findRelation(long id) {
-        for (Relation e : 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() throws SAXException {
-
-        // pass 1 - create all relations
-        for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
-            Relation en = new Relation();
-            e.getKey().copyTo(en);
-            ds.addPrimitive(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);
-
+     * Processes the parsed nodes after parsing. Just adds them to
+     * the dataset
+     * 
+     */
+    protected void processNodesAfterParsing() {
+        for (OsmPrimitive primitive: externalIdMap.values()) {
+            if (primitive instanceof Node) {
+                this.ds.addPrimitive(primitive);
+            }
+        }
+    }
+
+    /**
+     * Completes the parsed relations with its members.
+     * 
+     * @throws IllegalDataException thrown if a data integrity problem is detected, i.e. if a
+     * relation member refers to a local primitive which wasn't available in the data
+     * 
+     */
+    private void processRelationsAfterParsing() throws IllegalDataException {
+        for (Long externalRelationId : relations.keySet()) {
+            Relation relation = (Relation) externalIdMap.get("r" +externalRelationId);
             List<RelationMember> relationMembers = new ArrayList<RelationMember>();
-
-            for (RelationMemberData emd : e.getValue()) {
-                OsmPrimitive member;
-                if (emd.type.equals("node")) {
-                    member = findNode(emd.id);
-                    if (member == null) {
-                        member = new Node(emd.id);
-                        ds.addPrimitive(member);
+            for (RelationMemberData rm : relations.get(externalRelationId)) {
+                OsmPrimitive primitive = null;
+
+                // lookup the member from the map of already created primitives
+                //
+                if (rm.type.equals("node")) {
+                    primitive = externalIdMap.get("n" + rm.id);
+                } else if (rm.type.equals("way")) {
+                    primitive = externalIdMap.get("w" + rm.id);
+                } else if (rm.type.equals("relation")) {
+                    primitive = externalIdMap.get("r" + rm.id);
+                } else
+                    throw new IllegalDataException(
+                            tr("Unknown relation member type ''{0}'' in relation with external id ''{1}''", rm.type,externalRelationId)
+                    );
+
+                if (primitive == null) {
+                    if (rm.id <= 0)
+                        // relation member refers to a primitive with a negative id which was not
+                        // found in the data. This is always a data integrity problem and we abort
+                        // with an exception
+                        //
+                        throw new IllegalDataException(
+                                tr(
+                                        "Relation with external id ''{0}'' refers to missing primitive with external id ''{1}''",
+                                        externalRelationId,
+                                        rm.id
+                                )
+                        );
+
+                    // member refers to OSM primitive which was not present in the parsed data
+                    // -> create a new incomplete primitive and add it to the dataset
+                    //
+                    if (rm.type.equals("node")) {
+                        primitive = new Node(rm.id);
+                    } else if (rm.type.equals("way")) {
+                        primitive = new Way(rm.id);
+                    } else if (rm.type.equals("relation")) {
+                        primitive = new Relation(rm.id);
+                    } else {
+                        // can't happen, we've been testing for valid member types
+                        // at the beginning of this method
+                        //
                     }
-                } else if (emd.type.equals("way")) {
-                    member = hm.get(emd.id);
-                    if (member == null) {
-                        member = findWay(emd.id);
-                    }
-                    if (member == null) {
-                        member = new Way(emd.id);
-                        ds.addPrimitive(member);
-                    }
-                } else if (emd.type.equals("relation")) {
-                    member = findRelation(emd.id);
-                    if (member == null) {
-                        member = new Relation(emd.id);
-                        ds.addPrimitive(member);
-                    }
-                } else {
-                    throw new SAXException(tr("Unknown relation member type {0}", emd.type));
-                }
-                relationMembers.add(new RelationMember(emd.role, member));
-            }
-            en.setMembers(relationMembers);
-        }
-        hm = null;
+                    ds.addPrimitive(primitive);
+                }
+                relationMembers.add(new RelationMember(rm.role, primitive));
+            }
+            relation.setMembers(relationMembers);
+            ds.addPrimitive(relation);
+        }
     }
 
     /**
      * 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, ProgressMonitor progressMonitor) throws SAXException, IOException {
-        return parseDataSetOsm(source, progressMonitor).ds;
-    }
-
-    public static OsmReader parseDataSetOsm(InputStream source, ProgressMonitor progressMonitor) throws SAXException, IOException {
+     * 
+     * @param source the source input stream
+     * @param progressMonitor  the progress monitor
+     * 
+     * @return the dataset with the parsed data
+     * @throws IllegalDataException thrown if the an error was found while parsing the data from the source
+     */
+    public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
         OsmReader reader = new OsmReader();
-
-        // phase 1: Parse nodes and read in raw ways
-        InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
         try {
+            progressMonitor.beginTask(tr("Prepare OSM data...", 2));
+            progressMonitor.subTask(tr("Parsing OSM data..."));
+            InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
             SAXParserFactory.newInstance().newSAXParser().parse(inputSource, reader.new Parser());
-        } catch (ParserConfigurationException e1) {
-            e1.printStackTrace(); // broken SAXException chaining
-            throw new SAXException(e1);
-        }
-
-        progressMonitor.beginTask(tr("Prepare OSM data...", 2));
-        try {
-            for (Node n : reader.nodes.values()) {
-                reader.ds.addPrimitive(n);
-            }
-
             progressMonitor.worked(1);
 
-            try {
-                reader.createWays();
-                reader.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 : reader.ds.allPrimitives())
-                if (o.id < 0) {
-                    o.id = 0;
-                }
-
-            return reader;
+            progressMonitor.subTask(tr("Preparing data set..."));
+            reader.processNodesAfterParsing();
+            reader.processWaysAfterParsing();
+            reader.processRelationsAfterParsing();
+            progressMonitor.worked(1);
+            return reader.getDataSet();
+        } catch(IllegalDataException e) {
+            throw e;
+        } catch(ParserConfigurationException e) {
+            throw new IllegalDataException(e.getMessage(), e);
+        } catch(SAXException e) {
+            throw new IllegalDataException(e.getMessage(), e);
+        } catch(Exception e) {
+            throw new IllegalDataException(e);
         } finally {
             progressMonitor.finishTask();
Index: /trunk/src/org/openstreetmap/josm/io/OsmServerBackreferenceReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmServerBackreferenceReader.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmServerBackreferenceReader.java	(revision 2070)
@@ -51,7 +51,7 @@
         if (primitive == null)
             throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "primitive"));
-        if (primitive.id == 0)
-            throw new IllegalArgumentException(tr("id parameter ''{0}'' > 0 required. Got {1}", "primitive", primitive.id));
-        this.id = primitive.id;
+        if (primitive.getId() == 0)
+            throw new IllegalArgumentException(tr("id parameter ''{0}'' > 0 required. Got {1}", "primitive", primitive.getId()));
+        this.id = primitive.getId();
         this.primitiveType = OsmPrimitiveType.from(primitive);
         this.readFull = false;
@@ -222,6 +222,6 @@
             if (isReadFull() ||primitiveType.equals(OsmPrimitiveType.NODE)) {
                 for (Way way: waysToCheck) {
-                    if (way.id > 0 && way.incomplete) {
-                        OsmServerObjectReader reader = new OsmServerObjectReader(way.id, OsmPrimitiveType.from(way), true /* read full */);
+                    if (way.getId() > 0 && way.incomplete) {
+                        OsmServerObjectReader reader = new OsmServerObjectReader(way.getId(), OsmPrimitiveType.from(way), true /* read full */);
                         DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
                         MergeVisitor visitor = new MergeVisitor(ds, wayDs);
@@ -233,6 +233,6 @@
                 Collection<Relation> relationsToCheck  = new ArrayList<Relation>(ds.relations);
                 for (Relation relation: relationsToCheck) {
-                    if (relation.id > 0 && relation.incomplete) {
-                        OsmServerObjectReader reader = new OsmServerObjectReader(relation.id, OsmPrimitiveType.from(relation), true /* read full */);
+                    if (relation.getId() > 0 && relation.incomplete) {
+                        OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true /* read full */);
                         DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
                         MergeVisitor visitor = new MergeVisitor(ds, wayDs);
Index: /trunk/src/org/openstreetmap/josm/io/OsmServerObjectReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmServerObjectReader.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmServerObjectReader.java	(revision 2070)
@@ -46,6 +46,5 @@
             if (in == null)
                 return null;
-            final OsmReader osm = OsmReader.parseDataSetOsm(in, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
-            final DataSet data = osm.getDs();
+            final DataSet data = OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
             return data;
         } catch(OsmTransferException e) {
Index: /trunk/src/org/openstreetmap/josm/io/OsmWriter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/io/OsmWriter.java	(revision 2070)
@@ -14,4 +14,5 @@
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.User;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
@@ -208,9 +209,15 @@
         // user and visible added with 0.4 API
         if (osm.user != null) {
-            out.print(" user='"+XmlWriter.encode(osm.user.name)+"'");
+            if(osm.user.isLocalUser()) {
+                out.print(" user='"+XmlWriter.encode(osm.user.getName())+"'");
+            } else if (osm.user.isOsmUser()) {
+                // uid added with 0.6
+                out.print(" uid='"+ osm.user.getId()+"'");
+                out.print(" user='"+XmlWriter.encode(osm.user.getName())+"'");
+            }
         }
         out.print(" visible='"+osm.isVisible()+"'");
-        if (osm.version != -1) {
-            out.print(" version='"+osm.version+"'");
+        if (osm.getVersion() != 0) {
+            out.print(" version='"+osm.getVersion()+"'");
         }
         if (this.changeset != null && this.changeset.getId() != 0) {
Index: /trunk/src/org/openstreetmap/josm/plugins/PluginDownloader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/plugins/PluginDownloader.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/plugins/PluginDownloader.java	(revision 2070)
@@ -122,9 +122,13 @@
         if(pd.mainversion > AboutAction.getVersionNumber())
         {
-            int answer = new ExtendedDialog(Main.parent,
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
                     tr("Skip download"),
-                    tr("JOSM version {0} required for plugin {1}.", pd.mainversion, pd.name),
-                    new String[] {tr("Download Plugin"), tr("Skip Download")},
-                    new String[] {"download.png", "cancel.png"}).getValue();
+                    new String[] {tr("Download Plugin"), tr("Skip Download")}
+            );
+            dialog.setContent(tr("JOSM version {0} required for plugin {1}.", pd.mainversion, pd.name));
+            dialog.setButtonIcons(new String[] {"download.png", "cancel.png"});
+            dialog.showDialog();
+            int answer = dialog.getValue();
             if (answer != 1)
                 return false;
Index: /trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java	(revision 2070)
@@ -171,10 +171,13 @@
                 } catch (Throwable e) {
                     e.printStackTrace();
-
-                    int result = new ExtendedDialog(Main.parent,
+                    ExtendedDialog dialog = new ExtendedDialog(
+                            Main.parent,
                             tr("Disable plugin"),
-                            tr("Could not load plugin {0}. Delete from preferences?", info.name),
-                            new String[] {tr("Disable plugin"), tr("Keep plugin")},
-                            new String[] {"dialogs/delete.png", "cancel.png"}).getValue();
+                            new String[] {tr("Disable plugin"), tr("Keep plugin")}
+                    );
+                    dialog.setContent(tr("Could not load plugin {0}. Delete from preferences?", info.name));
+                    dialog.setButtonIcons( new String[] {"dialogs/delete.png", "cancel.png"});
+                    dialog.showDialog();
+                    int result = dialog.getValue();
 
                     if(result == 1)
@@ -278,17 +281,26 @@
 
         if (plugin != null) {
-            int answer = new ExtendedDialog(Main.parent,
+            ExtendedDialog dialog = new ExtendedDialog(
+                    Main.parent,
                     tr("Disable plugin"),
+                    new String[] {tr("Disable plugin"), tr("Cancel")}
+            );
+            dialog.setButtonIcons(new String[] {"dialogs/delete.png", "cancel.png"});
+            dialog.setContent(
+                    tr("<html>") +
                     tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", plugin.info.name)
-                    + "\n"
+                    + "<br>"
                     + (plugin.info.author != null
                             ? tr("According to the information within the plugin, the author is {0}.", plugin.info.author)
                                     : "")
-                                    + "\n"
+                                    + "<br>"
                                     + tr("Try updating to the newest version of this plugin before reporting a bug.")
-                                    + "\n"
-                                    + tr("Should the plugin be disabled?"),
-                                    new String[] {tr("Disable plugin"), tr("Cancel")},
-                                    new String[] {"dialogs/delete.png", "cancel.png"}).getValue();
+                                    + "<br>"
+                                    + tr("Should the plugin be disabled?")
+                                    + "</html>"
+            );
+            dialog.showDialog();
+            int answer = dialog.getValue();
+
             if (answer == 1) {
                 List<String> plugins = new ArrayList<String>(Main.pref.getCollection("plugins", Collections.<String>emptyList()));
Index: /trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java	(revision 2069)
+++ /trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java	(revision 2070)
@@ -4,5 +4,7 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.Frame;
 import java.awt.Point;
 import java.awt.Toolkit;
@@ -10,4 +12,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
@@ -43,11 +47,12 @@
      * @return the geometry object
      */
-    static public WindowGeometry centerInWindow(Window parent, Dimension extent) {
+    static public WindowGeometry centerInWindow(Component parent, Dimension extent) {
+        Frame parentWindow = JOptionPane.getFrameForComponent(parent);
         Point topLeft = new Point(
-                Math.max(0, (parent.getSize().width - extent.width) /2),
-                Math.max(0, (parent.getSize().height - extent.height) /2)
+                Math.max(0, (parentWindow.getSize().width - extent.width) /2),
+                Math.max(0, (parentWindow.getSize().height - extent.height) /2)
         );
-        topLeft.x += parent.getLocation().x;
-        topLeft.y += parent.getLocation().y;
+        topLeft.x += parentWindow.getLocation().x;
+        topLeft.y += parentWindow.getLocation().y;
         return new WindowGeometry(topLeft, extent);
     }
