Index: trunk/src/org/openstreetmap/josm/command/Command.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/Command.java	(revision 2283)
+++ trunk/src/org/openstreetmap/josm/command/Command.java	(revision 2284)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.Way;
@@ -25,5 +26,5 @@
  *
  * The command remembers the {@see OsmDataLayer} it is operating on.
- * 
+ *
  * @author imi
  */
@@ -31,19 +32,19 @@
 
     private static final class CloneVisitor extends AbstractVisitor {
-        public Map<OsmPrimitive, OsmPrimitive> orig = new HashMap<OsmPrimitive, OsmPrimitive>();
+        public Map<OsmPrimitive, PrimitiveData> orig = new HashMap<OsmPrimitive, PrimitiveData>();
 
         public void visit(Node n) {
-            orig.put(n, new Node(n));
+            orig.put(n, n.save());
         }
         public void visit(Way w) {
-            orig.put(w, new Way(w));
+            orig.put(w, w.save());
         }
         public void visit(Relation e) {
-            orig.put(e, new Relation(e));
+            orig.put(e, e.save());
         }
     }
 
     /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
-    private Map<OsmPrimitive, OsmPrimitive> cloneMap = new HashMap<OsmPrimitive, OsmPrimitive>();
+    private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<OsmPrimitive, PrimitiveData>();
 
     /** the layer which this command is applied to */
@@ -56,5 +57,5 @@
     /**
      * Creates a new command in the context of a specific data layer
-     * 
+     *
      * @param layer the data layer
      */
@@ -86,6 +87,6 @@
      */
     public void undoCommand() {
-        for (Entry<OsmPrimitive, OsmPrimitive> e : cloneMap.entrySet()) {
-            e.getKey().cloneFrom(e.getValue());
+        for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
+            e.getKey().load(e.getValue(), layer.data);
         }
     }
@@ -95,5 +96,5 @@
      * any buffer if it is not longer applicable to the dataset (e.g. it was part of
      * the removed layer)
-     * 
+     *
      * @param oldLayer the old layer
      * @return true if this command
@@ -109,11 +110,11 @@
      * of the object. Usually for undoing.
      */
-    public OsmPrimitive getOrig(OsmPrimitive osm) {
-        OsmPrimitive o = cloneMap.get(osm);
+    public PrimitiveData getOrig(OsmPrimitive osm) {
+        PrimitiveData o = cloneMap.get(osm);
         if (o != null)
             return o;
         Main.debug("unable to find osm with id: " + osm.getId() + " hashCode: " + osm.hashCode());
         for (OsmPrimitive t : cloneMap.keySet()) {
-            OsmPrimitive to = cloneMap.get(t);
+            PrimitiveData to = cloneMap.get(t);
             Main.debug("now: " + t.getId() + " hashCode: " + t.hashCode());
             Main.debug("orig: " + to.getId() + " hashCode: " + to.hashCode());
@@ -124,5 +125,5 @@
     /**
      * Replies the layer this command is (or was) applied to.
-     * 
+     *
      * @return
      */
Index: trunk/src/org/openstreetmap/josm/data/osm/Node.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Node.java	(revision 2283)
+++ trunk/src/org/openstreetmap/josm/data/osm/Node.java	(revision 2284)
@@ -46,10 +46,15 @@
 
 
+    protected Node(long id, boolean allowNegative) {
+        super(id, allowNegative);
+    }
+
+
     /**
-     * Create a new local node with id 0.
-     * 
+     * Create a new local node.
+     *
      */
     public Node() {
-        this(0);
+        this(0, false);
     }
 
@@ -59,5 +64,5 @@
      */
     public Node(long id) {
-        super(id);
+        super(id, false);
     }
 
@@ -66,16 +71,21 @@
      */
     public Node(Node clone) {
-        super(clone.getId());
+        super(clone.getUniqueId(), true);
         cloneFrom(clone);
     }
 
     public Node(LatLon latlon) {
-        super(0);
+        super(0, false);
         setCoor(latlon);
     }
 
     public Node(EastNorth eastNorth) {
-        super(0);
+        super(0, false);
         setEastNorth(eastNorth);
+    }
+
+    public Node(NodeData data, DataSet dataSet) {
+        super(data);
+        load(data, dataSet);
     }
 
@@ -87,4 +97,16 @@
         super.cloneFrom(osm);
         setCoor(((Node)osm).coor);
+    }
+
+    @Override public void load(PrimitiveData data, DataSet dataSet) {
+        super.load(data, dataSet);
+        setCoor(((NodeData)data).getCoor());
+    }
+
+    @Override public NodeData save() {
+        NodeData data = new NodeData();
+        saveCommonAttributes(data);
+        data.setCoor(getCoor());
+        return data;
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/NodeData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/NodeData.java	(revision 2284)
+++ trunk/src/org/openstreetmap/josm/data/osm/NodeData.java	(revision 2284)
@@ -0,0 +1,18 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+
+public class NodeData extends PrimitiveData {
+
+    private LatLon coor;
+
+    public LatLon getCoor() {
+        return coor;
+    }
+
+    public void setCoor(LatLon coor) {
+        this.coor = coor;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2283)
+++ trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2284)
@@ -17,4 +17,5 @@
 import java.util.Set;
 import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.openstreetmap.josm.Main;
@@ -35,4 +36,6 @@
 abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, Tagged {
 
+    private static AtomicLong idCounter = new AtomicLong(0);
+
     private static final int FLAG_MODIFIED = 1 << 0;
     private static final int FLAG_VISIBLE  = 1 << 1;
@@ -147,24 +150,31 @@
 
     /**
-     * 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;
+     * @param allowNegativeId Allows to set negative id. For internal use
+     * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
+     */
+    protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
+        if (allowNegativeId) {
+            this.id = id;
+        } else {
+            if (id < 0) {
+                throw new IllegalArgumentException(tr("Expected ID >= 0. Got {0}.", id));
+            } else if (id == 0) {
+                this.id = idCounter.decrementAndGet();
+            } else {
+                this.id = id;
+            }
+
+        }
         this.version = 0;
-        this.incomplete = id >0;
+        this.incomplete = id > 0;
+    }
+
+    protected OsmPrimitive(PrimitiveData data) {
+        version = data.getVersion();
+        id = data.getId();
     }
 
@@ -306,5 +316,5 @@
      */
     public void setVisible(boolean visible) throws IllegalStateException{
-        if (id == 0 && visible == false)
+        if (isNew() && visible == false)
             throw new IllegalStateException(tr("A primitive with ID = 0 can't be invisible."));
         if (visible) {
@@ -331,9 +341,17 @@
      */
     public long getId() {
+        return id >= 0?id:0;
+    }
+
+    /**
+     *
+     * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
+     */
+    public long getUniqueId() {
         return id;
     }
 
     /**
-     * 
+     *
      * @return True if primitive is new (not yet uploaded the server, id <= 0)
      */
@@ -473,5 +491,5 @@
      */
     @Override public boolean equals(Object obj) {
-        if (id == 0) return obj == this;
+        if (isNew()) return obj == this;
         if (obj instanceof OsmPrimitive)
             return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
@@ -660,5 +678,5 @@
      */
     public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
-        if (id != other.id)
+        if (!isNew() &&  id != other.id)
             return false;
         if (incomplete && ! other.incomplete || !incomplete  && other.incomplete)
@@ -782,3 +800,36 @@
      */
     public abstract String getDisplayName(NameFormatter formatter);
+
+    /**
+     * Loads (clone) this primitive from provided PrimitiveData
+     * @param data
+     * @param dataSet Dataset this primitive is part of. This parameter is used only
+     * temporarily. OsmPrimitive will have final field dataset in future
+     */
+    public void load(PrimitiveData data, DataSet dataSet) {
+        setKeys(data.getKeys());
+        timestamp = data.getTimestamp();
+        user = data.getUser();
+        setDeleted(data.isDeleted());
+        setModified(data.isModified());
+        setVisible(data.isVisible());
+    }
+
+    /**
+     * Save parameters of this primitive to the transport object
+     * @return
+     */
+    public abstract PrimitiveData save();
+
+    protected void saveCommonAttributes(PrimitiveData data) {
+        data.getKeys().clear();
+        data.getKeys().putAll(getKeys());
+        data.setTimestamp(timestamp);
+        data.setUser(user);
+        data.setDeleted(isDeleted());
+        data.setModified(isModified());
+        data.setVisible(isVisible());
+    }
+
 }
+
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 2284)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 2284)
@@ -0,0 +1,78 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * This class can be used to save properties of OsmPrimitive. The main difference between PrimitiveData
+ * and OsmPrimitive is that PrimitiveData is not part of the dataset and changes in PrimitiveData are not
+ * reported by events
+ *
+ */
+public class PrimitiveData {
+
+    // Useful?
+    //private boolean disabled;
+    //private boolean filtered;
+    //private boolean selected;
+    //private boolean highlighted;
+
+    private final Map<String, String> keys = new HashMap<String, String>();
+    private boolean modified;
+    private boolean visible;
+    private boolean deleted;
+    private long id;
+    private User user;
+    private int version;
+    private int timestamp;
+
+    public boolean isModified() {
+        return modified;
+    }
+    public void setModified(boolean modified) {
+        this.modified = modified;
+    }
+    public boolean isVisible() {
+        return visible;
+    }
+    public void setVisible(boolean visible) {
+        this.visible = visible;
+    }
+    public boolean isDeleted() {
+        return deleted;
+    }
+    public void setDeleted(boolean deleted) {
+        this.deleted = deleted;
+    }
+    public long getId() {
+        return id;
+    }
+    public void setId(long id) {
+        this.id = id;
+    }
+    public User getUser() {
+        return user;
+    }
+    public void setUser(User user) {
+        this.user = user;
+    }
+    public int getVersion() {
+        return version;
+    }
+    public void setVersion(int version) {
+        this.version = version;
+    }
+    public int getTimestamp() {
+        return timestamp;
+    }
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+    public Map<String, String> getKeys() {
+        return keys;
+    }
+
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2283)
+++ trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2284)
@@ -2,6 +2,8 @@
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
@@ -19,5 +21,5 @@
      * All members of this relation. Note that after changing this,
      * makeBackReferences and/or removeBackReferences should be called.
-     * 
+     *
      */
     private final List<RelationMember> members = new ArrayList<RelationMember>();
@@ -106,9 +108,13 @@
     }
 
+    protected Relation(long id, boolean allowNegative) {
+        super(id, allowNegative);
+    }
+
     /**
      * Create a new relation with id 0
      */
     public Relation() {
-        super(0);
+        super(0, false);
     }
 
@@ -117,5 +123,5 @@
      */
     public Relation(Relation clone) {
-        super(clone.getId());
+        super(clone.getUniqueId(), true);
         cloneFrom(clone);
     }
@@ -124,10 +130,15 @@
      * 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);
+        super(id, false);
+    }
+
+    public Relation(RelationData data, DataSet dataSet) {
+        super(data);
+        load(data, dataSet);
     }
 
@@ -141,4 +152,85 @@
             members.add(new RelationMember(em));
         }
+    }
+
+    @Override public void load(PrimitiveData data, DataSet dataSet) {
+        super.load(data, dataSet);
+
+        RelationData relationData = (RelationData)data;
+
+        // TODO Make this faster
+
+        Node nodeMarker = new Node();
+        Way wayMarker = new Way();
+        Relation relationMarker = new Relation();
+        Map<Long, Node> nodes = new HashMap<Long, Node>();
+        Map<Long, Way> ways = new HashMap<Long, Way>();
+        Map<Long, Relation> relations = new HashMap<Long, Relation>();
+
+        for (RelationMemberData member:relationData.getMembers()) {
+            switch (member.getMemberType()) {
+            case NODE:
+                nodes.put(member.getMemberId(), nodeMarker);
+                break;
+            case WAY:
+                ways.put(member.getMemberId(), wayMarker);
+                break;
+            case RELATION:
+                relations.put(member.getMemberId(), relationMarker);
+                break;
+            }
+        }
+
+        for (Node node:dataSet.nodes) {
+            if (nodes.get(node.getUniqueId()) == nodeMarker) {
+                nodes.put(node.getUniqueId(), node);
+            }
+        }
+        for (Way way:dataSet.ways) {
+            if (ways.get(way.getUniqueId()) == wayMarker) {
+                ways.put(way.getUniqueId(), way);
+            }
+        }
+        for (Relation relation:dataSet.relations) {
+            if (relations.get(relation.getUniqueId()) == relationMarker) {
+                relations.put(relation.getUniqueId(), relation);
+            }
+        }
+
+        List<RelationMember> newMembers = new ArrayList<RelationMember>();
+        for (RelationMemberData member:relationData.getMembers()) {
+            OsmPrimitive foundMember = null;
+            switch (member.getMemberType()) {
+            case NODE:
+                foundMember = nodes.get(member.getMemberId());
+                if (foundMember == nodeMarker) {
+                    foundMember = new Node(member.getMemberId(), true);
+                }
+                break;
+            case WAY:
+                foundMember = ways.get(member.getMemberId());
+                if (foundMember == wayMarker) {
+                    foundMember = new Way(member.getMemberId(), true);
+                }
+                break;
+            case RELATION:
+                foundMember = relations.get(member.getMemberId());
+                if (foundMember == relationMarker) {
+                    foundMember = new Relation(member.getMemberId(), true);
+                }
+                break;
+            }
+            newMembers.add(new RelationMember(member.getRole(), foundMember));
+        }
+        setMembers(newMembers);
+    }
+
+    @Override public RelationData save() {
+        RelationData data = new RelationData();
+        saveCommonAttributes(data);
+        for (RelationMember member:getMembers()) {
+            data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
+        }
+        return data;
     }
 
@@ -206,5 +298,5 @@
      * 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
Index: trunk/src/org/openstreetmap/josm/data/osm/RelationData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/RelationData.java	(revision 2284)
+++ trunk/src/org/openstreetmap/josm/data/osm/RelationData.java	(revision 2284)
@@ -0,0 +1,15 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RelationData extends PrimitiveData {
+
+    private final List<RelationMemberData> members = new ArrayList<RelationMemberData>();
+
+    public List<RelationMemberData> getMembers() {
+        return members;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/RelationMemberData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/RelationMemberData.java	(revision 2284)
+++ trunk/src/org/openstreetmap/josm/data/osm/RelationMemberData.java	(revision 2284)
@@ -0,0 +1,39 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+public class RelationMemberData {
+
+    private String role;
+    private long memberId;
+    private OsmPrimitiveType memberType;
+
+    public RelationMemberData() {
+
+    }
+
+    public RelationMemberData(String role, OsmPrimitive primitive) {
+        this.role = role;
+        this.memberId = primitive.getUniqueId();
+        this.memberType = OsmPrimitiveType.from(primitive);
+    }
+
+    public long getMemberId() {
+        return memberId;
+    }
+    public void setMemberId(long memberId) {
+        this.memberId = memberId;
+    }
+    public String getRole() {
+        return role;
+    }
+    public void setRole(String role) {
+        this.role = role;
+    }
+    public OsmPrimitiveType getMemberType() {
+        return memberType;
+    }
+    public void setMemberType(OsmPrimitiveType memberType) {
+        this.memberType = memberType;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/User.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/User.java	(revision 2283)
+++ trunk/src/org/openstreetmap/josm/data/osm/User.java	(revision 2284)
@@ -73,5 +73,7 @@
      */
     public static List<User> getByName(String name) {
-        name = name == null ? "" : name;
+        if (name == null) {
+            name = "";
+        }
         List<User> ret = new ArrayList<User>();
         for (User user: userMap.values()) {
@@ -84,7 +86,7 @@
 
     /** the user name */
-    private String name;
+    private final String name;
     /** the user id */
-    private long uid;
+    private final long uid;
 
     /**
@@ -94,5 +96,5 @@
      */
     public String getName() {
-        return name == null ? "" : name;
+        return name;
     }
 
@@ -113,5 +115,9 @@
     private User(long uid, String name) {
         this.uid = uid;
-        this.name = name;
+        if (name == null) {
+            this.name = "";
+        } else {
+            this.name = name;
+        }
     }
 
@@ -128,5 +134,5 @@
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + name.hashCode();
         result = prime * result + (int) (uid ^ (uid >>> 32));
         return result;
@@ -135,19 +141,13 @@
     @Override
     public boolean equals(Object obj) {
-        if (this == obj)
+        if (obj instanceof User) {
+            User other = (User) obj;
+            if (!name.equals(other.name))
+                return false;
+            if (uid != other.uid)
+                return false;
             return true;
-        if (obj == null)
+        } else
             return false;
-        if (getClass() != obj.getClass())
-            return false;
-        User other = (User) obj;
-        if (name == null) {
-            if (other.name != null)
-                return false;
-        } else if (!name.equals(other.name))
-            return false;
-        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 2283)
+++ trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 2284)
@@ -7,5 +7,7 @@
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
@@ -128,4 +130,8 @@
     }
 
+    protected Way(long id, boolean allowNegative) {
+        super(id, allowNegative);
+    }
+
     /**
      * Creates a new way with id 0.
@@ -133,5 +139,5 @@
      */
     public Way(){
-        super(0);
+        super(0, false);
     }
 
@@ -142,5 +148,5 @@
      */
     public Way(Way original) {
-        super(original.getId());
+        super(original.getUniqueId(), true);
         cloneFrom(original);
     }
@@ -154,5 +160,53 @@
      */
     public Way(long id) throws IllegalArgumentException {
-        super(id);
+        super(id, false);
+    }
+
+    public Way(WayData data, DataSet dataSet) {
+        super(data);
+        load(data, dataSet);
+    }
+
+    /**
+     *
+     * @param data
+     * @param dataSet Dataset this way is part of. This parameter will be removed in future
+     */
+    public void load(PrimitiveData data, DataSet dataSet) {
+        super.load(data, dataSet);
+
+        WayData wayData = (WayData)data;
+
+        // TODO We should have some lookup by id mechanism in future to speed this up
+        Node marker = new Node(0);
+        Map<Long, Node> foundNodes = new HashMap<Long, Node>();
+        for (Long nodeId:wayData.getNodes()) {
+            foundNodes.put(nodeId, marker);
+        }
+        for (Node node:dataSet.nodes) {
+            if (foundNodes.get(node.getUniqueId()) == marker) {
+                foundNodes.put(node.getUniqueId(), node);
+            }
+        }
+
+        List<Node> newNodes = new ArrayList<Node>(wayData.getNodes().size());
+        for (Long nodeId:wayData.getNodes()) {
+            Node node = foundNodes.get(nodeId);
+            if (node != marker) {
+                newNodes.add(foundNodes.get(nodeId));
+            } else {
+                newNodes.add(new Node(nodeId, true));
+            }
+        }
+        setNodes(newNodes);
+    }
+
+    @Override public WayData save() {
+        WayData data = new WayData();
+        saveCommonAttributes(data);
+        for (Node node:getNodes()) {
+            data.getNodes().add(node.getUniqueId());
+        }
+        return data;
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/WayData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/WayData.java	(revision 2284)
+++ trunk/src/org/openstreetmap/josm/data/osm/WayData.java	(revision 2284)
@@ -0,0 +1,15 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WayData extends PrimitiveData {
+
+    private final List<Long> nodes = new ArrayList<Long>();
+
+    public List<Long> getNodes() {
+        return nodes;
+    }
+
+}
