Index: trunk/src/org/openstreetmap/josm/Main.java
===================================================================
--- trunk/src/org/openstreetmap/josm/Main.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/Main.java	(revision 2305)
@@ -39,5 +39,5 @@
 import org.openstreetmap.josm.data.coor.CoordinateFormat;
 import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.projection.Mercator;
+import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.GettingStarted;
@@ -89,5 +89,5 @@
      * The global paste buffer.
      */
-    public static DataSet pasteBuffer = new DataSet();
+    public static PrimitiveDeepCopy pasteBuffer = new PrimitiveDeepCopy();
     public static Layer pasteSource;
     /**
@@ -377,5 +377,5 @@
     /**
      * Run any cleanup operation before exit
-     * 
+     *
      */
     public static  void cleanupBeforeExit() {
Index: trunk/src/org/openstreetmap/josm/actions/CopyAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/actions/CopyAction.java	(revision 2305)
@@ -7,20 +7,11 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.List;
 
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -43,5 +34,5 @@
         if(isEmptySelection()) return;
 
-        Main.pasteBuffer = copyData();
+        Main.pasteBuffer.makeCopy(getCurrentDataSet().getSelected());
         Main.pasteSource = getEditLayer();
         Main.main.menu.paste.setEnabled(true); /* now we have a paste buffer we can make paste available */
@@ -50,68 +41,4 @@
             a.pasteBufferChanged(Main.pasteBuffer);
         }
-    }
-
-    public DataSet copyData() {
-        /* New pasteBuffer - will be assigned to the global one at the end */
-        final DataSet pasteBuffer = new DataSet();
-        final HashMap<OsmPrimitive,OsmPrimitive> map = new HashMap<OsmPrimitive,OsmPrimitive>();
-        /* temporarily maps old nodes to new so we can do a true deep copy */
-
-        if(isEmptySelection()) return pasteBuffer;
-
-        /* scan the selected objects, mapping them to copies; when copying a way or relation,
-         * the copy references the copies of their child objects */
-        new AbstractVisitor() {
-            public void visit(Node n) {
-                /* check if already in pasteBuffer - e.g. two ways are selected which share a node;
-                 * or a way and a node in that way is selected, we'll see it twice, once via the
-                 * way and once directly; and so on. */
-                if (map.containsKey(n))
-                    return;
-                Node nnew = new Node(n);
-                map.put(n, nnew);
-                pasteBuffer.addPrimitive(nnew);
-            }
-            public void visit(Way w) {
-                /* check if already in pasteBuffer - could have come from a relation, and directly etc. */
-                if (map.containsKey(w))
-                    return;
-                Way wnew = new Way();
-                wnew.cloneFrom(w);
-                map.put(w, wnew);
-                List<Node> nodes = new ArrayList<Node>();
-                for (Node n : w.getNodes()) {
-                    if (! map.containsKey(n)) {
-                        n.visit(this);
-                    }
-                    nodes.add((Node)map.get(n));
-                }
-                wnew.setNodes(nodes);
-                pasteBuffer.addPrimitive(wnew);
-            }
-            public void visit(Relation e) {
-                if (map.containsKey(e))
-                    return;
-                Relation enew = new Relation(e);
-                map.put(e, enew);
-                List<RelationMember> members = new ArrayList<RelationMember>();
-                for (RelationMember m : e.getMembers()) {
-                    if (! map.containsKey(m.getMember())) {
-                        m.getMember().visit(this);
-                    }
-                    RelationMember mnew = new RelationMember(m.getRole(), map.get(m.getMember()));
-                    members.add(mnew);
-                }
-                enew.setMembers(members);
-                pasteBuffer.addPrimitive(enew);
-            }
-            public void visitAll() {
-                for (OsmPrimitive osm : getCurrentDataSet().getSelected()) {
-                    osm.visit(this);
-                }
-            }
-        }.visitAll();
-
-        return pasteBuffer;
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/actions/DuplicateAction.java	(revision 2305)
@@ -10,4 +10,5 @@
 
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -21,5 +22,5 @@
 
     public void actionPerformed(ActionEvent e) {
-        new PasteAction().pasteData(new CopyAction().copyData(), getEditLayer(), e);
+        new PasteAction().pasteData(new PrimitiveDeepCopy(getCurrentDataSet().getSelected()), getEditLayer(), e);
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/JosmAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/JosmAction.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/actions/JosmAction.java	(revision 2305)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
@@ -22,5 +23,5 @@
 /**
  * Base class helper for all Actions in JOSM. Just to make the life easier.
- * 
+ *
  * A JosmAction is a {@see LayerChangeListener} and a {@see SelectionChangedListener}. Upon
  * a layer change event or a selection change event it invokes {@see #updateEnabled()}.
@@ -28,5 +29,5 @@
  * of a JosmAction depending on the {@see #getCurrentDataSet()} and the current layers
  * (see also {@see #getEditLayer()}).
- * 
+ *
  * destroy() from interface Destroyable is called e.g. for MapModes, when the last layer has
  * been removed and so the mapframe will be destroyed. For other JosmActions, destroy() may never
@@ -105,5 +106,5 @@
      * needs to be overridden to be useful
      */
-    public void pasteBufferChanged(DataSet newPasteBuffer) {
+    public void pasteBufferChanged(PrimitiveDeepCopy newPasteBuffer) {
         return;
     }
@@ -126,5 +127,5 @@
     /**
      * Replies the current edit layer
-     * 
+     *
      * @return the current edit layer. null, if no edit layer exists
      */
@@ -135,5 +136,5 @@
     /**
      * Replies the current dataset
-     * 
+     *
      * @return the current dataset. null, if no current dataset exists
      */
@@ -155,5 +156,5 @@
      * Override in subclasses to init the enabled state of an action when it is
      * created. Default behaviour is to call {@see #updateEnabledState()}
-     * 
+     *
      * @see #updateEnabledState()
      * @see #updateEnabledState(Collection)
@@ -166,10 +167,10 @@
      * Override in subclasses to update the enabled state of the action when
      * something in the JOSM state changes, i.e. when a layer is removed or added.
-     * 
+     *
      * See {@see #updateEnabledState(Collection)} to respond to changes in the collection
      * of selected primitives.
-     * 
+     *
      * Default behavior is empty.
-     * 
+     *
      * @see #updateEnabledState(Collection)
      * @see #initEnabledState()
@@ -183,7 +184,7 @@
      * new selection. Avoid calling getCurrentDataSet().getSelected() because this
      * loops over the complete data set.
-     * 
+     *
      * @param selection the collection of selected primitives
-     * 
+     *
      * @see #updateEnabledState()
      * @see #initEnabledState()
Index: trunk/src/org/openstreetmap/josm/actions/MoveAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/MoveAction.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/actions/MoveAction.java	(revision 2305)
@@ -81,4 +81,5 @@
                 disty = 0;
                 distx = -distx;
+                break;
             default:
                 disty = 0;
Index: trunk/src/org/openstreetmap/josm/actions/PasteAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/actions/PasteAction.java	(revision 2305)
@@ -8,20 +8,18 @@
 import java.awt.event.KeyEvent;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.command.AddCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.command.AddPrimitivesCommand;
 import org.openstreetmap.josm.data.coor.EastNorth;
-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.Way;
+import org.openstreetmap.josm.data.osm.NodeData;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
+import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
+import org.openstreetmap.josm.data.osm.RelationData;
+import org.openstreetmap.josm.data.osm.RelationMemberData;
+import org.openstreetmap.josm.data.osm.WayData;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -40,14 +38,17 @@
     }
 
-    public  void pasteData(DataSet pasteBuffer, Layer source, ActionEvent e) {
+    public  void pasteData(PrimitiveDeepCopy pasteBuffer, Layer source, ActionEvent e) {
         /* Find the middle of the pasteBuffer area */
         double maxEast = -1E100, minEast = 1E100, maxNorth = -1E100, minNorth = 1E100;
-        for (Node n : pasteBuffer.nodes) {
-            double east = n.getEastNorth().east();
-            double north = n.getEastNorth().north();
-            if (east > maxEast) { maxEast = east; }
-            if (east < minEast) { minEast = east; }
-            if (north > maxNorth) { maxNorth = north; }
-            if (north < minNorth) { minNorth = north; }
+        for (PrimitiveData data : pasteBuffer.getAll()) {
+            if (data instanceof NodeData) {
+                NodeData n = (NodeData)data;
+                double east = n.getEastNorth().east();
+                double north = n.getEastNorth().north();
+                if (east > maxEast) { maxEast = east; }
+                if (east < minEast) { minEast = east; }
+                if (north > maxNorth) { maxNorth = north; }
+                if (north < minNorth) { minNorth = north; }
+            }
         }
 
@@ -63,53 +64,41 @@
         double offsetNorth = mPosition.north() - (maxNorth + minNorth)/2.0;
 
-        HashMap<OsmPrimitive,OsmPrimitive> map = new HashMap<OsmPrimitive,OsmPrimitive>();
-        /* temporarily maps old nodes to new so we can do a true deep copy */
 
-        /* do the deep copy of the paste buffer contents, leaving the pasteBuffer unchanged */
-        for (Node n : pasteBuffer.nodes) {
-            Node nnew = new Node(n);
-            nnew.clearOsmId();
-            if (Main.map.mapView.getEditLayer() == source) {
-                nnew.setEastNorth(nnew.getEastNorth().add(offsetEast, offsetNorth));
-            }
-            map.put(n, nnew);
+
+        // Make a copy of pasteBuffer and map from old id to copied data id
+        List<PrimitiveData> bufferCopy = new ArrayList<PrimitiveData>();
+        Map<Long, Long> newIds = new HashMap<Long, Long>();
+        for (PrimitiveData data:pasteBuffer.getAll()) {
+            PrimitiveData copy = data.makeCopy();
+            copy.clearOsmId();
+            newIds.put(data.getId(), copy.getId());
+            bufferCopy.add(copy);
         }
-        for (Way w : pasteBuffer.ways) {
-            Way wnew = new Way();
-            wnew.cloneFrom(w);
-            wnew.clearOsmId();
-            /* make sure we reference the new nodes corresponding to the old ones */
-            List<Node> nodes = new ArrayList<Node>();
-            for (Node n : w.getNodes()) {
-                nodes.add((Node)map.get(n));
-            }
-            wnew.setNodes(nodes);
-            map.put(w, wnew);
-        }
-        for (Relation r : pasteBuffer.relations) {
-            Relation rnew = new Relation(r);
-            r.clearOsmId();
-            List<RelationMember> members = new ArrayList<RelationMember>();
-            for (RelationMember m : r.getMembers()) {
-                OsmPrimitive mo = map.get(m.getMember());
-                if(mo != null) /* FIXME - This only prevents illegal data, but kills the relation */
-                {
-                    RelationMember mnew = new RelationMember(m.getRole(), map.get(m.getMember()));
-                    members.add(mnew);
+
+        // Update references in copied buffer
+        for (PrimitiveData data:bufferCopy) {
+            if (data instanceof NodeData) {
+                NodeData nodeData = (NodeData)data;
+                if (Main.map.mapView.getEditLayer() == source) {
+                    nodeData.setEastNorth(nodeData.getEastNorth().add(offsetEast, offsetNorth));
+                }
+            } else if (data instanceof WayData) {
+                ListIterator<Long> it = ((WayData)data).getNodes().listIterator();
+                while (it.hasNext()) {
+                    it.set(newIds.get(it.next()));
+                }
+            } else if (data instanceof RelationData) {
+                ListIterator<RelationMemberData> it = ((RelationData)data).getMembers().listIterator();
+                while (it.hasNext()) {
+                    RelationMemberData member = it.next();
+                    it.set(new RelationMemberData(member.getRole(), member.getMemberType(), newIds.get(member.getMemberId())));
                 }
             }
-            rnew.setMembers(members);
-            map.put(r, rnew);
         }
 
-        /* Now execute the commands to add the dupicated contents of the paste buffer to the map */
-        Collection<OsmPrimitive> osms = map.values();
-        Collection<Command> clist = new LinkedList<Command>();
-        for (OsmPrimitive osm : osms) {
-            clist.add(new AddCommand(osm));
-        }
+        /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
 
-        Main.main.undoRedo.add(new SequenceCommand(tr("Paste"), clist));
-        getCurrentDataSet().setSelected(osms);
+        Main.main.undoRedo.add(new AddPrimitivesCommand(bufferCopy));
+        //getCurrentDataSet().setSelected(osms);
         Main.map.mapView.repaint();
     }
@@ -121,9 +110,5 @@
             return;
         }
-        setEnabled(
-                !Main.pasteBuffer.nodes.isEmpty()
-                || !Main.pasteBuffer.ways.isEmpty()
-                || !Main.pasteBuffer.relations.isEmpty()
-        );
+        setEnabled(!Main.pasteBuffer.isEmpty());
     }
 }
Index: trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java	(revision 2305)
@@ -18,11 +18,9 @@
 import org.openstreetmap.josm.command.Command;
 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.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
+import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
 import org.openstreetmap.josm.data.osm.TagCollection;
-import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
 import org.openstreetmap.josm.tools.Shortcut;
@@ -37,237 +35,232 @@
     }
 
-    static private List<Class<? extends OsmPrimitive>> osmPrimitiveClasses;
-    static {
-        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
-     * {@see OsmPrimitive}s of exactly one type
-     * 
-     * @return
-     */
-    protected boolean isHeteogeneousSource() {
-        int count = 0;
-        count = !getSourcePrimitivesByType(Node.class).isEmpty() ? count + 1 : count;
-        count = !getSourcePrimitivesByType(Way.class).isEmpty() ? count + 1 : count;
-        count = !getSourcePrimitivesByType(Relation.class).isEmpty() ? count + 1 : count;
-        return count > 1;
-    }
-
-    /**
-     * Replies all primitives of type <code>type</code> in the current selection.
-     * 
-     * @param <T>
-     * @param type  the type
-     * @return all primitives of type <code>type</code> in the current selection.
-     */
-    protected <T extends OsmPrimitive> Collection<? extends OsmPrimitive> getSourcePrimitivesByType(Class<T> type) {
-        return OsmPrimitive.getFilteredList(Main.pasteBuffer.getSelected(), type);
-    }
-
-    /**
-     * Replies the collection of tags for all primitives of type <code>type</code> in the current
-     * selection
-     * 
-     * @param <T>
-     * @param type  the type
-     * @return the collection of tags for all primitives of type <code>type</code> in the current
-     * selection
-     */
-    protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(Class<T> type) {
-        return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
-    }
-
-    /**
-     * Replies true if there is at least one tag in the current selection for primitives of
-     * type <code>type</code>
-     * 
-     * @param <T>
-     * @param type the type
-     * @return true if there is at least one tag in the current selection for primitives of
-     * type <code>type</code>
-     */
-    protected <T extends OsmPrimitive> boolean hasSourceTagsByType(Class<T> type) {
-        return ! getSourceTagsByType(type).isEmpty();
-    }
-
-    protected Command buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
-        List<Command> commands = new ArrayList<Command>();
-        for (String key : tc.getKeys()) {
-            String value = tc.getValues(key).iterator().next();
-            value = value.equals("") ? null : value;
-            commands.add(new ChangePropertyCommand(selection,key,value));
-        }
-        if (!commands.isEmpty()) {
-            String title1 = trn("Pasting {0} tag", "Pasting {0} tags", tc.getKeys().size(), tc.getKeys().size());
-            String title2 = trn("to {0} primitive", "to {0} primtives", selection.size(), selection.size());
-            return new SequenceCommand(
-                    title1 + " " + title2,
-                    commands
-            );
-        }
-        return null;
-    }
-
-    protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
-        HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
-        for (Class<? extends OsmPrimitive> type: osmPrimitiveClasses) {
-            if (!getSourceTagsByType(type).isEmpty()) {
-                ret.put(OsmPrimitiveType.from(type), getSourcePrimitivesByType(type).size());
-            }
-        }
-        return ret;
-    }
-
-    protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
-        HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
-        for (Class<? extends OsmPrimitive> type: osmPrimitiveClasses) {
-            int count = OsmPrimitive.getFilteredList(getEditLayer().data.getSelected(), type).size();
-            if (count > 0) {
-                ret.put(OsmPrimitiveType.from(type), count);
-            }
-        }
-        return ret;
-    }
-
-    /**
-     * Pastes the tags from a homogeneous source (i.e. the {@see Main#pasteBuffer}s selection consisting
-     * of one type of {@see OsmPrimitive}s only.
-     * 
-     * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
-     * regardless of their type, receive the same tags.
-     * 
-     * @param targets the collection of target primitives
-     */
-    protected void pasteFromHomogeneousSource(Collection<? extends OsmPrimitive> targets) {
-        TagCollection tc = null;
-        for (Class<? extends OsmPrimitive> type : osmPrimitiveClasses) {
-            TagCollection tc1 = getSourceTagsByType(type);
-            if (!tc1.isEmpty()) {
-                tc = tc1;
-            }
-        }
-        if (tc == null)
-            // no tags found to paste. Abort.
-            return;
-
-        if (!tc.isApplicableToPrimitive()) {
-            PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
-            dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
-            dialog.setVisible(true);
-            if (dialog.isCanceled())
+    public static class TagPaster {
+
+        private final Collection<PrimitiveData> source;
+        private final Collection<OsmPrimitive> target;
+        private final List<Command> commands = new ArrayList<Command>();
+
+        public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
+            this.source = source;
+            this.target = target;
+        }
+
+        /**
+         * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
+         * {@see OsmPrimitive}s of exactly one type
+         *
+         * @return
+         */
+        protected boolean isHeteogeneousSource() {
+            int count = 0;
+            count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? count + 1 : count;
+            count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? count + 1 : count;
+            count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? count + 1 : count;
+            return count > 1;
+        }
+
+        /**
+         * Replies all primitives of type <code>type</code> in the current selection.
+         *
+         * @param <T>
+         * @param type  the type
+         * @return all primitives of type <code>type</code> in the current selection.
+         */
+        protected <T extends PrimitiveData> Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
+            return PrimitiveData.getFilteredList(source, type);
+        }
+
+        /**
+         * Replies the collection of tags for all primitives of type <code>type</code> in the current
+         * selection
+         *
+         * @param <T>
+         * @param type  the type
+         * @return the collection of tags for all primitives of type <code>type</code> in the current
+         * selection
+         */
+        protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(OsmPrimitiveType type) {
+            return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
+        }
+
+        /**
+         * Replies true if there is at least one tag in the current selection for primitives of
+         * type <code>type</code>
+         *
+         * @param <T>
+         * @param type the type
+         * @return true if there is at least one tag in the current selection for primitives of
+         * type <code>type</code>
+         */
+        protected <T extends OsmPrimitive> boolean hasSourceTagsByType(OsmPrimitiveType type) {
+            return ! getSourceTagsByType(type).isEmpty();
+        }
+
+        protected Command buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
+            List<Command> commands = new ArrayList<Command>();
+            for (String key : tc.getKeys()) {
+                String value = tc.getValues(key).iterator().next();
+                value = value.equals("") ? null : value;
+                commands.add(new ChangePropertyCommand(selection,key,value));
+            }
+            if (!commands.isEmpty()) {
+                String title1 = trn("Pasting {0} tag", "Pasting {0} tags", tc.getKeys().size(), tc.getKeys().size());
+                String title2 = trn("to {0} primitive", "to {0} primtives", selection.size(), selection.size());
+                return new SequenceCommand(
+                        title1 + " " + title2,
+                        commands
+                );
+            }
+            return null;
+        }
+
+        protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
+            HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
+            for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
+                if (!getSourceTagsByType(type).isEmpty()) {
+                    ret.put(type, getSourcePrimitivesByType(type).size());
+                }
+            }
+            return ret;
+        }
+
+        protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
+            HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
+            for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
+                int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
+                if (count > 0) {
+                    ret.put(type, count);
+                }
+            }
+            return ret;
+        }
+
+        /**
+         * Pastes the tags from a homogeneous source (i.e. the {@see Main#pasteBuffer}s selection consisting
+         * of one type of {@see OsmPrimitive}s only.
+         *
+         * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
+         * regardless of their type, receive the same tags.
+         *
+         * @param targets the collection of target primitives
+         */
+        protected void pasteFromHomogeneousSource() {
+            TagCollection tc = null;
+            for (OsmPrimitiveType type : OsmPrimitiveType.values()) {
+                TagCollection tc1 = getSourceTagsByType(type);
+                if (!tc1.isEmpty()) {
+                    tc = tc1;
+                }
+            }
+            if (tc == null)
+                // no tags found to paste. Abort.
                 return;
-            Command cmd = buildChangeCommand(targets, dialog.getResolution());
-            Main.main.undoRedo.add(cmd);
-        } else {
-            // no conflicts in the source tags to resolve. Just apply the tags
-            // to the target primitives
-            //
-            Command cmd = buildChangeCommand(targets, tc);
-            Main.main.undoRedo.add(cmd);
-        }
-    }
-
-    /**
-     * Replies true if there is at least one primitive of type <code>type</code> in the collection
-     * <code>selection</code>
-     * 
-     * @param <T>
-     * @param selection  the collection of primitives
-     * @param type  the type to look for
-     * @return true if there is at least one primitive of type <code>type</code> in the collection
-     * <code>selection</code>
-     */
-    protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Collection<OsmPrimitive> selection, Class<T> type) {
-        return !OsmPrimitive.getFilteredList(selection, type).isEmpty();
-    }
-
-    /**
-     * Replies true if this a heterogeneous source can be pasted without conflict to targets
-     * 
-     * @param targets the collection of target primitives
-     * @return true if this a heterogeneous source can be pasted without conflicts to targets
-     */
-    protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<OsmPrimitive> targets) {
-        if (hasTargetPrimitives(targets, Node.class)) {
-            TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(Node.class));
-            if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
-                return false;
-        }
-        if (hasTargetPrimitives(targets, Way.class)) {
-            TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(Way.class));
-            if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
-                return false;
-        }
-        if (hasTargetPrimitives(targets, Relation.class)) {
-            TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(Relation.class));
-            if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
-                return false;
-        }
-        return true;
-    }
-
-    /**
-     * Pastes the tags in the current selection of the paste buffer to a set of target
-     * primitives.
-     * 
-     * @param targets the collection of target primitives
-     */
-    protected void pasteFromHeterogeneousSource(Collection<OsmPrimitive> targets) {
-        if (canPasteFromHeterogeneousSourceWithoutConflict(targets)) {
-            if (hasSourceTagsByType(Node.class) && hasTargetPrimitives(targets, Node.class)) {
-                Command cmd = buildChangeCommand(targets, getSourceTagsByType(Node.class));
-                Main.main.undoRedo.add(cmd);
-            }
-            if (hasSourceTagsByType(Way.class) && hasTargetPrimitives(targets, Way.class)) {
-                Command cmd = buildChangeCommand(targets, getSourceTagsByType(Way.class));
-                Main.main.undoRedo.add(cmd);
-            }
-            if (hasSourceTagsByType(Relation.class) && hasTargetPrimitives(targets, Relation.class)) {
-                Command cmd = buildChangeCommand(targets,getSourceTagsByType(Relation.class));
-                Main.main.undoRedo.add(cmd);
-            }
-        } else {
-            PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
-            dialog.populate(
-                    getSourceTagsByType(Node.class),
-                    getSourceTagsByType(Way.class),
-                    getSourceTagsByType(Relation.class),
-                    getSourceStatistics(),
-                    getTargetStatistics()
-            );
-            dialog.setVisible(true);
-            if (dialog.isCanceled())
-                return;
-            if (hasSourceTagsByType(Node.class) && hasTargetPrimitives(targets, Node.class)) {
-                Command cmd = buildChangeCommand(OsmPrimitive.getFilteredList(targets, Node.class), dialog.getResolution(OsmPrimitiveType.NODE));
-                Main.main.undoRedo.add(cmd);
-            }
-            if (hasSourceTagsByType(Way.class) && hasTargetPrimitives(targets, Way.class)) {
-                Command cmd = buildChangeCommand(OsmPrimitive.getFilteredList(targets, Way.class), dialog.getResolution(OsmPrimitiveType.WAY));
-                Main.main.undoRedo.add(cmd);
-            }
-            if (hasSourceTagsByType(Relation.class) && hasTargetPrimitives(targets, Relation.class)) {
-                Command cmd = buildChangeCommand(OsmPrimitive.getFilteredList(targets, Relation.class), dialog.getResolution(OsmPrimitiveType.RELATION));
-                Main.main.undoRedo.add(cmd);
-            }
-        }
-    }
+
+            if (!tc.isApplicableToPrimitive()) {
+                PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
+                dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
+                dialog.setVisible(true);
+                if (dialog.isCanceled())
+                    return;
+                Command cmd = buildChangeCommand(target, dialog.getResolution());
+                commands.add(cmd);
+            } else {
+                // no conflicts in the source tags to resolve. Just apply the tags
+                // to the target primitives
+                //
+                Command cmd = buildChangeCommand(target, tc);
+                commands.add(cmd);
+            }
+        }
+
+        /**
+         * Replies true if there is at least one primitive of type <code>type</code> in the collection
+         * <code>selection</code>
+         *
+         * @param <T>
+         * @param selection  the collection of primitives
+         * @param type  the type to look for
+         * @return true if there is at least one primitive of type <code>type</code> in the collection
+         * <code>selection</code>
+         */
+        protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Class<T> type) {
+            return !OsmPrimitive.getFilteredList(target, type).isEmpty();
+        }
+
+        /**
+         * Replies true if this a heterogeneous source can be pasted without conflict to targets
+         *
+         * @param targets the collection of target primitives
+         * @return true if this a heterogeneous source can be pasted without conflicts to targets
+         */
+        protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<OsmPrimitive> targets) {
+            for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
+                if (hasTargetPrimitives(type.getOsmClass())) {
+                    TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
+                    if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
+                        return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Pastes the tags in the current selection of the paste buffer to a set of target
+         * primitives.
+         *
+         * @param targets the collection of target primitives
+         */
+        protected void pasteFromHeterogeneousSource() {
+            if (canPasteFromHeterogeneousSourceWithoutConflict(target)) {
+                for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
+                    if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
+                        Command cmd = buildChangeCommand(target, getSourceTagsByType(type));
+                        commands.add(cmd);
+                    }
+                }
+            } else {
+                PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
+                dialog.populate(
+                        getSourceTagsByType(OsmPrimitiveType.NODE),
+                        getSourceTagsByType(OsmPrimitiveType.WAY),
+                        getSourceTagsByType(OsmPrimitiveType.RELATION),
+                        getSourceStatistics(),
+                        getTargetStatistics()
+                );
+                dialog.setVisible(true);
+                if (dialog.isCanceled())
+                    return;
+                for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
+                    if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
+                        Command cmd = buildChangeCommand(OsmPrimitive.getFilteredList(target, type.getOsmClass()), dialog.getResolution(type));
+                        commands.add(cmd);
+                    }
+                }
+            }
+        }
+
+        public List<Command> execute() {
+            commands.clear();
+            if (isHeteogeneousSource()) {
+                pasteFromHeterogeneousSource();
+            } else {
+                pasteFromHomogeneousSource();
+            }
+            return commands;
+        }
+
+    }
+
 
     public void actionPerformed(ActionEvent e) {
         if (getCurrentDataSet().getSelected().isEmpty())
             return;
-        if (isHeteogeneousSource()) {
-            pasteFromHeterogeneousSource(getCurrentDataSet().getSelected());
-        } else {
-            pasteFromHomogeneousSource(getCurrentDataSet().getSelected());
-        }
-    }
-
-    @Override public void pasteBufferChanged(DataSet newPasteBuffer) {
+        TagPaster tagPaster = new TagPaster(Main.pasteBuffer.getDirectlyAdded(), getCurrentDataSet().getSelected());
+        for (Command c:tagPaster.execute()) {
+            Main.main.undoRedo.add(c);
+        }
+    }
+
+    @Override public void pasteBufferChanged(PrimitiveDeepCopy newPasteBuffer) {
         updateEnabledState();
     }
@@ -281,5 +274,5 @@
         setEnabled(
                 !getCurrentDataSet().getSelected().isEmpty()
-                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getSelected()).isEmpty()
+                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
         );
     }
@@ -289,5 +282,5 @@
         setEnabled(
                 selection!= null && !selection.isEmpty()
-                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getSelected()).isEmpty()
+                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
         );
     }
Index: trunk/src/org/openstreetmap/josm/command/AddPrimitivesCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/command/AddPrimitivesCommand.java	(revision 2305)
+++ trunk/src/org/openstreetmap/josm/command/AddPrimitivesCommand.java	(revision 2305)
@@ -0,0 +1,62 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.command;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.MutableTreeNode;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveData;
+
+public class AddPrimitivesCommand extends Command {
+
+    private final List<PrimitiveData> data = new ArrayList<PrimitiveData>();
+
+    public AddPrimitivesCommand(List<PrimitiveData> data) {
+        this.data.addAll(data);
+    }
+
+    @Override public boolean executeCommand() {
+
+        List<OsmPrimitive> createdPrimitives = new ArrayList<OsmPrimitive>(data.size());
+
+        for (PrimitiveData pd:data) {
+            createdPrimitives.add(getLayer().data.getPrimitiveById(pd.getId(), OsmPrimitiveType.fromData(pd), true));
+        }
+
+        for (int i=0; i<createdPrimitives.size(); i++) {
+            createdPrimitives.get(i).load(data.get(i), getLayer().data);
+        }
+
+        return true;
+    }
+
+    @Override public void undoCommand() {
+        for (PrimitiveData p:data) {
+            getLayer().data.removePrimitive(p.getId(), OsmPrimitiveType.fromData(p));
+        }
+    }
+
+    @Override
+    public MutableTreeNode description() {
+         return new DefaultMutableTreeNode(
+                new JLabel(tr("Added {0} objects", data.size()), null,
+                        JLabel.HORIZONTAL
+                )
+        );
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
+            Collection<OsmPrimitive> added) {
+        // Does nothing because we don't want to create OsmPrimitives.
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/Changeset.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 2305)
@@ -50,5 +50,5 @@
     /**
      * Creates a changeset with id <code>id</code>. If id > 0, sets incomplete to true.
-     * 
+     *
      * @param id the id
      */
@@ -61,5 +61,5 @@
     /**
      * Creates a clone of <code>other</code>
-     * 
+     *
      * @param other the other changeset. If null, creates a new changeset with id 0.
      */
@@ -192,4 +192,9 @@
         this.tags.remove(key);
     }
+
+    public void removeAll() {
+        this.tags.clear();
+    }
+
 
     public boolean hasEqualSemanticAttributes(Changeset other) {
Index: trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 2305)
@@ -1,6 +1,4 @@
 // License: GPL. Copyright 2007 by Immanuel Scholz and others
 package org.openstreetmap.josm.data.osm;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
 import java.awt.geom.Area;
 import java.util.ArrayList;
@@ -16,5 +14,4 @@
 
 import org.openstreetmap.josm.data.SelectionChangedListener;
-import org.openstreetmap.josm.data.osm.QuadBuckets;
 
 /**
@@ -113,6 +110,4 @@
      */
     public void addPrimitive(OsmPrimitive primitive) {
-        if (primitive == null)
-            return;
         if (primitive instanceof Node) {
             nodes.add((Node) primitive);
@@ -121,4 +116,22 @@
         } else if (primitive instanceof Relation) {
             relations.add((Relation) primitive);
+        }
+    }
+
+    public OsmPrimitive addPrimitive(PrimitiveData data) {
+        if (data instanceof NodeData) {
+            Node node = new Node((NodeData)data, this);
+            nodes.add(node);
+            return node;
+        } else if (data instanceof WayData) {
+            Way way = new Way((WayData)data, this);
+            ways.add(way);
+            return way;
+        } else if (data instanceof RelationData) {
+            Relation relation = new Relation((RelationData)data, this);
+            relations.add(relation);
+            return relation;
+        } else {
+            throw new AssertionError();
         }
     }
@@ -145,4 +158,8 @@
     }
 
+    public void removePrimitive(long id, OsmPrimitiveType type) {
+        removePrimitive(getPrimitiveById(id, type));
+    }
+
     public Collection<OsmPrimitive> getSelectedNodesAndWays() {
         Collection<OsmPrimitive> sel = getSelected(nodes);
@@ -246,5 +263,5 @@
      * Sets the current selection to the primitives in <code>selection</code>.
      * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
-     * 
+     *
      * @param selection the selection
      * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
@@ -265,5 +282,5 @@
      * Sets the current selection to the primitives in <code>selection</code>
      * and notifies all {@see SelectionChangedListener}.
-     * 
+     *
      * @param selection the selection
      */
@@ -275,5 +292,5 @@
      * Adds   the primitives in <code>selection</code> to the current selection
      * and notifies all {@see SelectionChangedListener}.
-     * 
+     *
      * @param selection the selection
      */
@@ -285,5 +302,5 @@
      * Adds the primitives in <code>selection</code> to the current selection.
      * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
-     * 
+     *
      * @param selection the selection
      * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
@@ -443,16 +460,14 @@
      * exists
      *
-     * @param id  the id, > 0 required
+     * @param id  uniqueId of the primitive. Might be < 0 for newly created primitives
      * @param type the type of  the primitive. Must not be null.
      * @return the primitive
-     * @exception IllegalArgumentException thrown, if id <= 0
-     * @exception IllegalArgumentException thrown, if type is null
-     * @exception IllegalArgumentException thrown, if type is neither NODE, or WAY or RELATION
+     * @exception NullPointerException thrown, if type is null
      */
     public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
-        if (id <= 0)
-            throw new IllegalArgumentException(tr("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "id", id));
-        if (id <= 0)
-            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "type"));
+        return getPrimitiveById(id, type, false);
+    }
+
+    public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type, boolean createNew) {
         Collection<? extends OsmPrimitive> primitives = null;
         switch(type) {
@@ -462,7 +477,19 @@
         }
         for (OsmPrimitive primitive : primitives) {
-            if (primitive.getId() == id) return primitive;
-        }
-        return null;
+            if (primitive.getUniqueId() == id) return primitive;
+        }
+
+        if (createNew) {
+            OsmPrimitive result = null;
+            switch (type) {
+            case NODE: result = new Node(id, true); break;
+            case WAY: result = new Way(id, true); break;
+            case RELATION: result = new Relation(id, true); break;
+            }
+            addPrimitive(result);
+            return result;
+        } else {
+            return null;
+        }
     }
 
@@ -565,5 +592,5 @@
      * Replies true if there is at least one primitive in this dataset with
      * {@see OsmPrimitive#isModified()} == <code>true</code>.
-     * 
+     *
      * @return true if there is at least one primitive in this dataset with
      * {@see OsmPrimitive#isModified()} == <code>true</code>.
Index: trunk/src/org/openstreetmap/josm/data/osm/NodeData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/NodeData.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/NodeData.java	(revision 2305)
@@ -2,9 +2,29 @@
 package org.openstreetmap.josm.data.osm;
 
+import org.openstreetmap.josm.data.coor.CachedLatLon;
+import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
 
 public class NodeData extends PrimitiveData {
 
-    private LatLon coor;
+    private final CachedLatLon coor = new CachedLatLon(0, 0);
+
+    public NodeData() {
+
+    }
+
+    public NodeData(double lat, double lon, String... keys) {
+        setCoor(new LatLon(lat, lon));
+        setKeysAsList(keys);
+    }
+
+    public NodeData(String... keys) {
+        setKeysAsList(keys);
+    }
+
+    public NodeData(NodeData data) {
+        super(data);
+        setCoor(data.getCoor());
+    }
 
     public LatLon getCoor() {
@@ -13,5 +33,23 @@
 
     public void setCoor(LatLon coor) {
-        this.coor = coor;
+        this.coor.setCoor(coor);
+    }
+
+    public EastNorth getEastNorth() {
+        return this.coor.getEastNorth();
+    }
+
+    public void setEastNorth(EastNorth eastNorth) {
+        this.coor.setEastNorth(eastNorth);
+    }
+
+    @Override
+    public NodeData makeCopy() {
+        return new NodeData(this);
+    }
+
+    @Override
+    public Node makePrimitive(DataSet dataSet) {
+        return new Node(this, dataSet);
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 2305)
@@ -36,5 +36,9 @@
 abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, Tagged {
 
-    static final AtomicLong idCounter = new AtomicLong(0);
+    private static final AtomicLong idCounter = new AtomicLong(0);
+
+    static long generateUniqueId() {
+        return idCounter.decrementAndGet();
+    }
 
     private static final int FLAG_MODIFIED = 1 << 0;
@@ -164,5 +168,5 @@
                 throw new IllegalArgumentException(tr("Expected ID >= 0. Got {0}.", id));
             else if (id == 0) {
-                this.id = idCounter.decrementAndGet();
+                this.id = generateUniqueId();
             } else {
                 this.id = id;
@@ -400,5 +404,5 @@
      */
     public void clearOsmId() {
-        this.id = idCounter.decrementAndGet();
+        this.id = generateUniqueId();
         this.version = 0;
         this.incomplete = false;
@@ -456,5 +460,5 @@
         if(directionKeys == null) {
             directionKeys = Main.pref.getCollection("tags.direction",
-                    Arrays.asList(new String[]{"oneway","incline","incline_steep","aerialway"}));
+                    Arrays.asList("oneway","incline","incline_steep","aerialway"));
         }
         return directionKeys;
@@ -841,5 +845,5 @@
 
     protected void saveCommonAttributes(PrimitiveData data) {
-        data.setId(data.getId());
+        data.setId(id);
         data.getKeys().clear();
         data.getKeys().putAll(getKeys());
Index: trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java	(revision 2305)
@@ -5,16 +5,28 @@
 public enum OsmPrimitiveType {
 
-    NODE ("node"),
-    WAY  ("way"),
-    RELATION ("relation");
+    NODE ("node", Node.class, NodeData.class),
+    WAY  ("way", Way.class, WayData.class),
+    RELATION ("relation", Relation.class, RelationData.class);
 
-    private String apiTypeName;
+    private final String apiTypeName;
+    private final Class<? extends OsmPrimitive> osmClass;
+    private final Class<? extends PrimitiveData> dataClass;
 
-    OsmPrimitiveType(String apiTypeName) {
+    OsmPrimitiveType(String apiTypeName, Class<? extends OsmPrimitive> osmClass, Class<? extends PrimitiveData> dataClass) {
         this.apiTypeName = apiTypeName;
+        this.osmClass = osmClass;
+        this.dataClass = dataClass;
     }
 
     public String getAPIName() {
         return apiTypeName;
+    }
+
+    public Class<? extends OsmPrimitive> getOsmClass() {
+        return osmClass;
+    }
+
+    public Class<? extends PrimitiveData> getDataClass() {
+        return dataClass;
     }
 
@@ -30,9 +42,20 @@
     }
 
-    public static OsmPrimitiveType from(Class cls) {
+    public static OsmPrimitiveType from(Class<? extends OsmPrimitive> cls) {
         if (cls.equals(Node.class)) return NODE;
         if (cls.equals(Way.class)) return WAY;
         if (cls.equals(Relation.class)) return RELATION;
         throw new IllegalArgumentException(tr("Parameter ''{0}'' is not an acceptable class. Got ''{1}''.", "cls", cls.toString()));
+    }
+
+    public static OsmPrimitiveType fromData(Class<? extends PrimitiveData> cls) {
+        if (cls.equals(NodeData.class)) return NODE;
+        if (cls.equals(WayData.class)) return WAY;
+        if (cls.equals(RelationData.class)) return RELATION;
+        throw new IllegalArgumentException(tr("Parameter ''{0}'' is not an acceptable class. Got ''{1}''.", "cls", cls.toString()));
+    }
+
+    public static OsmPrimitiveType fromData(PrimitiveData data) {
+        return fromData(data.getClass());
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 2305)
@@ -2,5 +2,8 @@
 package org.openstreetmap.josm.data.osm;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
@@ -12,5 +15,5 @@
  *
  */
-public abstract class PrimitiveData {
+public abstract class PrimitiveData implements Tagged {
 
     // Useful?
@@ -19,4 +22,19 @@
     //private boolean selected;
     //private boolean highlighted;
+
+    public PrimitiveData() {
+        id = OsmPrimitive.generateUniqueId();
+    }
+
+    public PrimitiveData(PrimitiveData data) {
+        this.keys.putAll(data.keys);
+        this.modified = data.modified;
+        this.visible = data.visible;
+        this.deleted = data.deleted;
+        this.id = data.id;
+        this.user = data.user;
+        this.version = data.version;
+        this.timestamp = data.timestamp;
+    }
 
     private final Map<String, String> keys = new HashMap<String, String>();
@@ -75,4 +93,11 @@
     }
 
+    public void clearOsmId() {
+        id = OsmPrimitive.generateUniqueId();
+    }
+
+    public abstract PrimitiveData makeCopy();
+
+    public abstract OsmPrimitive makePrimitive(DataSet dataSet);
 
     @Override
@@ -94,4 +119,55 @@
     }
 
+    // Tagged implementation
+
+    public String get(String key) {
+        return keys.get(key);
+    }
+
+    public boolean hasKeys() {
+        return !keys.isEmpty();
+    }
+
+    public Collection<String> keySet() {
+        return keys.keySet();
+    }
+
+    public void put(String key, String value) {
+        keys.put(key, value);
+    }
+
+    public void remove(String key) {
+        keys.remove(key);
+    }
+
+    public void removeAll() {
+        keys.clear();
+    }
+
+    public void setKeys(Map<String, String> keys) {
+        this.keys.clear();
+        this.keys.putAll(keys);
+    }
+
+
+    @SuppressWarnings("unchecked")
+    static public <T extends PrimitiveData>  List<T> getFilteredList(Collection<T> list, OsmPrimitiveType type) {
+        List<T> ret = new ArrayList<T>();
+        for(PrimitiveData p: list) {
+            if (type.getDataClass().isInstance(p)) {
+                ret.add((T)p);
+            }
+        }
+        return ret;
+    }
+
+    protected void setKeysAsList(String... keys) {
+        assert keys.length % 2 == 0;
+        for (int i=0; i<keys.length/2; i++) {
+            this.keys.put(keys[i * 2], keys[i * 2 + 1]);
+        }
+    }
+
+
 
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java	(revision 2305)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java	(revision 2305)
@@ -0,0 +1,95 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
+
+/**
+ * This class allows to create and keep a deep copy of primitives. Provides methods to access directly added
+ * primitives and reference primitives
+ *
+ */
+public class PrimitiveDeepCopy {
+
+    private final List<PrimitiveData> directlyAdded = new ArrayList<PrimitiveData>();
+    private final List<PrimitiveData> referenced = new ArrayList<PrimitiveData>();
+
+    public PrimitiveDeepCopy() {
+
+    }
+
+    public PrimitiveDeepCopy(final Collection<OsmPrimitive> primitives) {
+        makeCopy(primitives);
+    }
+
+    /**
+     * Replace content of the object with copy of provided primitives
+     * @param primitives
+     */
+    public final void makeCopy(final Collection<OsmPrimitive> primitives) {
+        directlyAdded.clear();
+        referenced.clear();
+
+        final Set<Long> visitedIds = new HashSet<Long>();
+
+        new AbstractVisitor() {
+            boolean firstIteration;
+
+            public void visit(Node n) {
+                if (!visitedIds.add(n.getUniqueId()))
+                    return;
+                (firstIteration?directlyAdded:referenced).add(n.save());
+            }
+            public void visit(Way w) {
+                if (!visitedIds.add(w.getUniqueId()))
+                    return;
+                (firstIteration?directlyAdded:referenced).add(w.save());
+                firstIteration = false;
+                for (Node n : w.getNodes()) {
+                    visit(n);
+                }
+            }
+            public void visit(Relation e) {
+                if (!visitedIds.add(e.getUniqueId()))
+                    return;
+                (firstIteration?directlyAdded:referenced).add(e.save());
+                firstIteration = false;
+                for (RelationMember m : e.getMembers()) {
+                    m.getMember().visit(this);
+                }
+            }
+
+            public void visitAll() {
+                for (OsmPrimitive osm : primitives) {
+                    firstIteration = true;
+                    osm.visit(this);
+                }
+            }
+        }.visitAll();
+    }
+
+    public List<PrimitiveData> getDirectlyAdded() {
+        return directlyAdded;
+    }
+
+    public List<PrimitiveData> getReferenced() {
+        return referenced;
+    }
+
+    public List<PrimitiveData> getAll() {
+        List<PrimitiveData> result = new ArrayList<PrimitiveData>(directlyAdded.size() + referenced.size());
+        result.addAll(directlyAdded);
+        result.addAll(referenced);
+        return result;
+    }
+
+    public boolean isEmpty() {
+        return directlyAdded.isEmpty() && referenced.isEmpty();
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2305)
@@ -205,5 +205,5 @@
                 foundMember = nodes.get(member.getMemberId());
                 if (foundMember == nodeMarker) {
-                    foundMember = new Node(member.getMemberId(), true);
+                    throw new AssertionError("Data consistency problem - relation with missing member detected");
                 }
                 break;
@@ -211,5 +211,5 @@
                 foundMember = ways.get(member.getMemberId());
                 if (foundMember == wayMarker) {
-                    foundMember = new Way(member.getMemberId(), true);
+                    throw new AssertionError("Data consistency problem - relation with missing member detected");
                 }
                 break;
@@ -217,5 +217,5 @@
                 foundMember = relations.get(member.getMemberId());
                 if (foundMember == relationMarker) {
-                    foundMember = new Relation(member.getMemberId(), true);
+                    throw new AssertionError("Data consistency problem - relation with missing member detected");
                 }
                 break;
Index: trunk/src/org/openstreetmap/josm/data/osm/RelationData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/RelationData.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/RelationData.java	(revision 2305)
@@ -9,6 +9,25 @@
     private final List<RelationMemberData> members = new ArrayList<RelationMemberData>();
 
+    public RelationData() {
+
+    }
+
+    public RelationData(RelationData data) {
+        super(data);
+        members.addAll(data.members);
+    }
+
     public List<RelationMemberData> getMembers() {
         return members;
+    }
+
+    @Override
+    public RelationData makeCopy() {
+        return new RelationData(this);
+    }
+
+    @Override
+    public Relation makePrimitive(DataSet dataSet) {
+        return new Relation(this, dataSet);
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/RelationMemberData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/RelationMemberData.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/RelationMemberData.java	(revision 2305)
@@ -4,16 +4,16 @@
 public class RelationMemberData {
 
-    private String role;
-    private long memberId;
-    private OsmPrimitiveType memberType;
+    private final String role;
+    private final long memberId;
+    private final OsmPrimitiveType memberType;
 
-    public RelationMemberData() {
-
+    public RelationMemberData(String role, OsmPrimitiveType type, long id) {
+        this.role = role;
+        this.memberType = type;
+        this.memberId = id;
     }
 
     public RelationMemberData(String role, OsmPrimitive primitive) {
-        this.role = role;
-        this.memberId = primitive.getUniqueId();
-        this.memberType = OsmPrimitiveType.from(primitive);
+        this(role, OsmPrimitiveType.from(primitive), primitive.getUniqueId());
     }
 
@@ -21,18 +21,9 @@
         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/Tag.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Tag.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/Tag.java	(revision 2305)
@@ -2,8 +2,4 @@
 package org.openstreetmap.josm.data.osm;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
 
 /**
@@ -21,6 +17,5 @@
      */
     public Tag(){
-        this.key = "";
-        this.value = "";
+        this("", "");
     }
 
@@ -28,10 +23,9 @@
      * Create a tag whose key is <code>key</code> and whose value is
      * empty.
-     * 
+     *
      * @param key the key. If null, it is set to the empty key.
      */
     public Tag(String key) {
-        this();
-        this.key = key == null ? "" : key;
+        this(key, "");
     }
 
@@ -39,5 +33,5 @@
      * Creates a tag for a key and a value. If key and/or value are null,
      * the empty value "" is assumed.
-     * 
+     *
      * @param key the key
      * @param value  the value
@@ -50,17 +44,14 @@
     /**
      * Creates clone of the tag <code>tag</code>.
-     * 
-     * @param tag the tag. If null, creates an empty tag.
+     *
+     * @param tag the tag.
      */
     public Tag(Tag tag) {
-        if (tag != null) {
-            key = tag.getKey();
-            value = tag.getValue();
-        }
+        this(tag.getKey(), tag.getValue());
     }
 
     /**
      * Replies the key of the tag. This is never null.
-     * 
+     *
      * @return the key of the tag
      */
@@ -71,5 +62,5 @@
     /**
      * Replies the value of the tag. This is never null.
-     * 
+     *
      * @return the value of the tag
      */
@@ -86,12 +77,9 @@
      * Replies true if the key of this tag is equal to <code>key</code>.
      * If <code>key</code> is null, assumes the empty key.
-     * 
+     *
      * @param key the key
      * @return true if the key of this tag is equal to <code>key</code>
      */
     public boolean matchesKey(String key) {
-        if (key == null) {
-            key = "";
-        }
         return this.key.equals(key);
     }
@@ -102,5 +90,5 @@
      *   <li>removing leading and trailing white space</li>
      * <ul>
-     * 
+     *
      */
     public void normalize() {
@@ -113,6 +101,6 @@
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((key == null) ? 0 : key.hashCode());
-        result = prime * result + ((value == null) ? 0 : value.hashCode());
+        result = prime * result + key.hashCode();
+        result = prime * result + value.hashCode();
         return result;
     }
@@ -120,22 +108,15 @@
     @Override
     public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
+        if (obj instanceof Tag) {
+            Tag other = (Tag) obj;
+            return key.equals(other.getKey()) && value.equals(other.getValue());
+        } else {
             return false;
-        if (getClass() != obj.getClass())
-            return false;
-        Tag other = (Tag) obj;
-        if (key == null) {
-            if (other.key != null)
-                return false;
-        } else if (!key.equals(other.key))
-            return false;
-        if (value == null) {
-            if (other.value != null)
-                return false;
-        } else if (!value.equals(other.value))
-            return false;
-        return true;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return key + "=" + value;
     }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java	(revision 2305)
@@ -17,5 +17,5 @@
  * TagCollection is a collection of tags which can be used to manipulate
  * tags managed by {@see OsmPrimitive}s.
- * 
+ *
  * A TagCollection can be created:
  * <ul>
@@ -25,11 +25,11 @@
  *  <li>from the intersection of all tags managed by a collection of primitives with {@see #commonToAllPrimitives(Collection)}</li>
  * </ul>
- * 
+ *
  * It  provides methods to query the collection, like {@see #size()}, {@see #hasTagsFor(String)}, etc.
- * 
+ *
  * Basic set operations allow to create the union, the intersection and  the difference
  * of tag collections, see {@see #union(TagCollection)}, {@see #intersect(TagCollection)},
  * and {@see #minus(TagCollection)}.
- * 
+ *
  *
  */
@@ -40,10 +40,10 @@
      * {@see OsmPrimitive}. If <code>primitive</code> is null, replies
      * an empty tag collection.
-     * 
+     *
      * @param primitive  the primitive
      * @return a tag collection with the tags managed by a specific
      * {@see OsmPrimitive}
      */
-    public static TagCollection from(OsmPrimitive primitive) {
+    public static TagCollection from(Tagged primitive) {
         TagCollection tags = new TagCollection();
         for (String key: primitive.keySet()) {
@@ -62,8 +62,8 @@
      * a collection of primitives
      */
-    public static TagCollection unionOfAllPrimitives(Collection<? extends OsmPrimitive> primitives) {
+    public static TagCollection unionOfAllPrimitives(Collection<? extends Tagged> primitives) {
         TagCollection tags = new TagCollection();
         if (primitives == null) return tags;
-        for (OsmPrimitive primitive: primitives) {
+        for (Tagged primitive: primitives) {
             if (primitive == null) {
                 continue;
@@ -78,9 +78,9 @@
      * <code>primitives</code>. Replies an empty tag collection of <code>primitives</code>
      * is null.
-     * 
+     *
      * @param primitives the primitives
      * @return  a tag collection with the tags which are common to all primitives
      */
-    public static TagCollection commonToAllPrimitives(Collection<? extends OsmPrimitive> primitives) {
+    public static TagCollection commonToAllPrimitives(Collection<? extends Tagged> primitives) {
         TagCollection tags = new TagCollection();
         if (primitives == null || primitives.isEmpty()) return tags;
@@ -91,5 +91,5 @@
         // intersect with the others
         //
-        for (OsmPrimitive primitive: primitives) {
+        for (Tagged primitive: primitives) {
             if (primitive == null) {
                 continue;
@@ -103,5 +103,5 @@
      * Replies a tag collection with the union of the tags which are common to all primitives in
      * the dataset <code>ds</code>. Returns an empty tag collection of <code>ds</code> is null.
-     * 
+     *
      * @param ds the dataset
      * @return a tag collection with the union of the tags which are common to all primitives in
@@ -129,5 +129,5 @@
      * Creates a clone of the tag collection <code>other</code>. Creats an empty
      * tag collection if <code>other</code> is null.
-     * 
+     *
      * @param other the other collection
      */
@@ -141,5 +141,5 @@
     /**
      * Replies the number of tags in this tag collection
-     * 
+     *
      * @return the number of tags in this tag collection
      */
@@ -150,5 +150,5 @@
     /**
      * Replies true if this tag collection is empty
-     * 
+     *
      * @return true if this tag collection is empty; false, otherwise
      */
@@ -159,5 +159,5 @@
     /**
      * Adds a tag to the tag collection. If <code>tag</code> is null, nothing is added.
-     * 
+     *
      * @param tag the tag to add
      */
@@ -171,5 +171,5 @@
      * Adds a collection of tags to the tag collection. If <code>tags</code> is null, nothing
      * is added. null values in the collection are ignored.
-     * 
+     *
      * @param tags the collection of tags
      */
@@ -184,5 +184,5 @@
      * Adds the tags of another tag collection to this collection. Adds nothing, if
      * <code>tags</code> is null.
-     * 
+     *
      * @param tags the other tag collection
      */
@@ -195,5 +195,5 @@
      * Removes a specific tag from the tag collection. Does nothing if <code>tag</code> is
      * null.
-     * 
+     *
      * @param tag the tag to be removed
      */
@@ -206,5 +206,5 @@
      * Removes a collection of tags from the tag collection. Does nothing if <code>tags</code> is
      * null.
-     * 
+     *
      * @param tags the tags to be removed
      */
@@ -217,5 +217,5 @@
      * Removes all tags in the tag collection <code>tags</code> from the current tag collection.
      * Does nothing if <code>tags</code> is null.
-     * 
+     *
      * @param tags the tag collection to be removed.
      */
@@ -228,5 +228,5 @@
      * Removes all tags whose keys are equal to  <code>key</code>. Does nothing if <code>key</code>
      * is null.
-     * 
+     *
      * @param key the key to be removed
      */
@@ -244,5 +244,5 @@
      * Removes all tags whose key is in the collection <code>keys</code>. Does nothing if
      * <code>keys</code> is null.
-     * 
+     *
      * @param keys the collection of keys to be removed
      */
@@ -256,5 +256,5 @@
     /**
      * Replies true if the this tag collection contains <code>tag</code>.
-     * 
+     *
      * @param tag the tag to look up
      * @return true if the this tag collection contains <code>tag</code>; false, otherwise
@@ -266,5 +266,5 @@
     /**
      * Replies true if this tag collection contains at least one tag with key <code>key</code>.
-     * 
+     *
      * @param key the key to look up
      * @return true if this tag collection contains at least one tag with key <code>key</code>; false, otherwise
@@ -281,5 +281,5 @@
      * Replies true if this tag collection contains all tags in <code>tags</code>. Replies
      * false, if tags is null.
-     * 
+     *
      * @param tags the tags to look up
      * @return true if this tag collection contains all tags in <code>tags</code>. Replies
@@ -288,5 +288,5 @@
     public boolean containsAll(Collection<Tag> tags) {
         if (tags == null) return false;
-        return tags.containsAll(tags);
+        return this.tags.containsAll(tags);
     }
 
@@ -294,5 +294,5 @@
      * Replies true if this tag collection at least one tag for every key in <code>keys</code>.
      * Replies false, if <code>keys</code> is null. null values in <code>keys</code> are ignored.
-     * 
+     *
      * @param keys the keys to lookup
      * @return true if this tag collection at least one tag for every key in <code>keys</code>.
@@ -311,5 +311,5 @@
     /**
      * Replies the number of tags with key <code>key</code>
-     * 
+     *
      * @param key the key to look up
      * @return the number of tags with key <code>key</code>. 0, if key is null.
@@ -328,5 +328,5 @@
     /**
      * Replies true if there is at least one tag for the given key.
-     * 
+     *
      * @param key the key to look up
      * @return true if there is at least one tag for the given key. false, if key is null.
@@ -339,5 +339,5 @@
      * Replies true it there is at least one tag with a non empty value for key.
      * Replies false if key is null.
-     * 
+     *
      * @param key the key
      * @return true it there is at least one tag with a non empty value for key.
@@ -354,5 +354,5 @@
      * if the value of this tag is not empty. Replies false if key is
      * null.
-     * 
+     *
      * @param key the key
      * @return true if there is exactly one tag for <code>key</code> and
@@ -368,5 +368,5 @@
      * Replies true if there is a tag with an empty value for <code>key</code>.
      * Replies false, if key is null.
-     * 
+     *
      * @param key the key
      * @return true if there is a tag with an empty value for <code>key</code>
@@ -381,5 +381,5 @@
      * Replies true if there is exactly one tag for <code>key</code> and if
      * the value for this tag is empty. Replies false if key is null.
-     * 
+     *
      * @param key the key
      * @return  true if there is exactly one tag for <code>key</code> and if
@@ -395,5 +395,5 @@
      * Replies a tag collection with the tags for a given key. Replies an empty collection
      * if key is null.
-     * 
+     *
      * @param key the key to look up
      * @return a tag collection with the tags for a given key. Replies an empty collection
@@ -415,5 +415,5 @@
      * Replies a tag collection with all tags whose key is equal to one of the keys in
      * <code>keys</code>. Replies an empty collection if keys is null.
-     * 
+     *
      * @param keys the keys to look up
      * @return a tag collection with all tags whose key is equal to one of the keys in
@@ -434,5 +434,5 @@
     /**
      * Replies the tags of this tag collection as set
-     * 
+     *
      * @return the tags of this tag collection as set
      */
@@ -444,5 +444,5 @@
      * Replies the tags of this tag collection as list.
      * Note that the order of the list is not preserved between method invocations.
-     * 
+     *
      * @return the tags of this tag collection as list.
      */
@@ -453,5 +453,5 @@
     /**
      * Replies an iterator to iterate over the tags in this collection
-     * 
+     *
      * @return the iterator
      */
@@ -462,5 +462,5 @@
     /**
      * Replies the set of keys of this tag collection.
-     * 
+     *
      * @return the set of keys of this tag collection
      */
@@ -475,5 +475,5 @@
     /**
      * Replies the set of keys which have at least 2 matching tags.
-     * 
+     *
      * @return the set of keys which have at least 2 matching tags.
      */
@@ -496,5 +496,5 @@
      * Sets a unique tag for the key of this tag. All other tags with the same key are
      * removed from the collection. Does nothing if tag is null.
-     * 
+     *
      * @param tag the tag to set
      */
@@ -509,5 +509,5 @@
      * removed from the collection. Assume the empty string for key and value if either
      * key or value is null.
-     * 
+     *
      * @param key the key
      * @param value the value
@@ -520,5 +520,5 @@
     /**
      * Replies the set of values in this tag collection
-     * 
+     *
      * @return the set of values
      */
@@ -534,5 +534,5 @@
      * Replies the set of values for a given key. Replies an empty collection if there
      * are no values for the given key.
-     * 
+     *
      * @param key the key to look up
      * @return the set of values for a given key. Replies an empty collection if there
@@ -552,5 +552,5 @@
     /**
      * Replies true if for every key there is one tag only, i.e. exactly one value.
-     * 
+     *
      * @return
      */
@@ -562,10 +562,10 @@
      * Applies this tag collection to an {@see OsmPrimitive}. Does nothing if
      * primitive is null
-     * 
+     *
      * @param primitive  the primitive
      * @throws IllegalStateException thrown if this tag collection can't be applied
      * because there are keys with multiple values
      */
-    public void applyTo(OsmPrimitive primitive) throws IllegalStateException {
+    public void applyTo(Tagged primitive) throws IllegalStateException {
         if (primitive == null) return;
         if (! isApplicableToPrimitive())
@@ -583,14 +583,14 @@
      * Applies this tag collection to a collection of {@see OsmPrimitive}s. Does nothing if
      * primitives is null
-     * 
+     *
      * @param primitives  the collection of primitives
      * @throws IllegalStateException thrown if this tag collection can't be applied
      * because there are keys with multiple values
      */
-    public void applyTo(Collection<? extends OsmPrimitive> primitives) throws IllegalStateException{
+    public void applyTo(Collection<? extends Tagged> primitives) throws IllegalStateException{
         if (primitives == null) return;
         if (! isApplicableToPrimitive())
             throw new IllegalStateException(tr("Tag collection can't be applied to a primitive because there are keys with multiple values."));
-        for (OsmPrimitive primitive: primitives) {
+        for (Tagged primitive: primitives) {
             applyTo(primitive);
         }
@@ -600,10 +600,10 @@
      * Replaces the tags of an {@see OsmPrimitive} by the tags in this collection . Does nothing if
      * primitive is null
-     * 
+     *
      * @param primitive  the primitive
      * @throws IllegalStateException thrown if this tag collection can't be applied
      * because there are keys with multiple values
      */
-    public void replaceTagsOf(OsmPrimitive primitive) throws IllegalStateException {
+    public void replaceTagsOf(Tagged primitive) throws IllegalStateException {
         if (primitive == null) return;
         if (! isApplicableToPrimitive())
@@ -618,14 +618,14 @@
      * Replaces the tags of a collection of{@see OsmPrimitive}s by the tags in this collection.
      * Does nothing if primitives is null
-     * 
+     *
      * @param primitive  the collection of primitives
      * @throws IllegalStateException thrown if this tag collection can't be applied
      * because there are keys with multiple values
      */
-    public void replaceTagsOf(Collection<? extends OsmPrimitive> primitives) throws IllegalStateException {
+    public void replaceTagsOf(Collection<? extends Tagged> primitives) throws IllegalStateException {
         if (primitives == null) return;
         if (! isApplicableToPrimitive())
             throw new IllegalStateException(tr("Tag collection can't be applied to a primitive because there are keys with multiple values."));
-        for (OsmPrimitive primitive: primitives) {
+        for (Tagged primitive: primitives) {
             replaceTagsOf(primitive);
         }
@@ -634,5 +634,5 @@
     /**
      * Builds the intersection of this tag collection and another tag collection
-     * 
+     *
      * @param other the other tag collection. If null, replies an empty tag collection.
      * @return the intersection of this tag collection and another tag collection
@@ -653,5 +653,5 @@
     /**
      * Replies the difference of this tag collection and another tag collection
-     * 
+     *
      * @param other the other tag collection. May be null.
      * @return the difference of this tag collection and another tag collection
@@ -667,5 +667,5 @@
     /**
      * Replies the union of this tag collection and another tag collection
-     * 
+     *
      * @param other the other tag collection. May be null.
      * @return the union of this tag collection and another tag collection
@@ -690,5 +690,5 @@
     /**
      * Replies the concatenation of all tag values (concatenated by a semicolon)
-     * 
+     *
      * @return the concatenation of all tag values
      */
@@ -707,3 +707,8 @@
         return buffer.toString();
     }
+
+    @Override
+    public String toString() {
+        return tags.toString();
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/Tagged.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Tagged.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/Tagged.java	(revision 2305)
@@ -6,6 +6,6 @@
 /**
  * Objects implement Tagged if they provide a map of key/value pairs.
- * 
- * 
+ *
+ *
  */
 // FIXME: better naming? setTags(), getTags(), getKeys() instead of keySet() ?
@@ -14,5 +14,5 @@
     /**
      * Sets the map of key/value pairs
-     * 
+     *
      * @param keys the map of key value pairs. If null, reset to the empty map.
      */
@@ -21,5 +21,5 @@
     /**
      * Replies the map of key/value pairs. Never null, but may be the empty map.
-     * 
+     *
      * @return the map of key/value pairs
      */
@@ -28,5 +28,5 @@
     /**
      * Sets a key/value pairs
-     * 
+     *
      * @param key the key
      * @param value the value. If null, removes the key/value pair.
@@ -36,5 +36,5 @@
     /**
      * Replies the value of the given key; null, if there is no value for this key
-     * 
+     *
      * @param key the key
      * @return the value
@@ -44,5 +44,5 @@
     /**
      * Removes a given key/value pair
-     * 
+     *
      * @param key the key
      */
@@ -51,5 +51,5 @@
     /**
      * Replies true, if there is at least one key/value pair; false, otherwise
-     * 
+     *
      * @return true, if there is at least one key/value pair; false, otherwise
      */
@@ -58,7 +58,12 @@
     /**
      * Replies the set of keys
-     * 
+     *
      * @return the set of keys
      */
     Collection<String> keySet();
+
+    /**
+     * Removes all tags
+     */
+    void removeAll();
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/Way.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/Way.java	(revision 2305)
@@ -196,5 +196,5 @@
                 newNodes.add(foundNodes.get(nodeId));
             } else {
-                newNodes.add(new Node(nodeId, true));
+                throw new AssertionError("Data consistency problem - way with missing node detected");
             }
         }
Index: trunk/src/org/openstreetmap/josm/data/osm/WayData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/WayData.java	(revision 2304)
+++ trunk/src/org/openstreetmap/josm/data/osm/WayData.java	(revision 2305)
@@ -9,6 +9,25 @@
     private final List<Long> nodes = new ArrayList<Long>();
 
+    public WayData() {
+
+    }
+
+    public WayData(WayData data) {
+        super(data);
+        nodes.addAll(data.getNodes());
+    }
+
     public List<Long> getNodes() {
         return nodes;
+    }
+
+    @Override
+    public WayData makeCopy() {
+        return new WayData(this);
+    }
+
+    @Override
+    public OsmPrimitive makePrimitive(DataSet dataSet) {
+        return new Way(this, dataSet);
     }
 
