Index: src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/DataSet.java	(Revision 12028)
+++ src/org/openstreetmap/josm/data/osm/DataSet.java	(Arbeitskopie)
@@ -5,13 +5,11 @@
 
 import java.awt.geom.Area;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -21,8 +19,10 @@
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
@@ -32,6 +32,11 @@
 import org.openstreetmap.josm.data.SelectionChangedListener;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent;
+import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent;
+import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent;
+import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent;
+import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent;
 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
 import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
 import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
@@ -41,6 +46,7 @@
 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
+import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
@@ -166,8 +172,21 @@
     private UploadPolicy uploadPolicy;
 
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    /**
+     * The mutex lock that is used to synchronize selection changes.
+     */
     private final Object selectionLock = new Object();
+    /**
+     * The current selected primitives. This is always a unmodifiable set.
+     */
+    private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet();
 
+    /**
+     * A list of listeners that listen to selection changes on this layer.
+     */
+    private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
+
     private Area cachedDataSourceArea;
     private List<Bounds> cachedDataSourceBounds;
 
@@ -183,6 +202,7 @@
         // Transparently register as projection change listener. No need to explicitly remove
         // the listener, projection change listeners are managed as WeakReferences.
         Main.addProjectionChangeListener(this);
+        addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection()));
     }
 
     /**
@@ -636,10 +656,7 @@
             }
             if (!success)
                 throw new JosmRuntimeException("failed to remove primitive: "+primitive);
-            synchronized (selectionLock) {
-                selectedPrimitives.remove(primitive);
-                selectionSnapshot = null;
-            }
+            clearSelection(primitiveId);
             allPrimitives.remove(primitive);
             primitive.setDataset(null);
             firePrimitivesRemoved(Collections.singletonList(primitive), false);
@@ -653,6 +670,30 @@
      *---------------------------------------------------*/
 
     /**
+     * Add a listener that listens to selection changes in this specific data set.
+     * @param listener The listener.
+     * @see #removeSelectionListener(DataSelectionListener)
+     * @see SelectionEventManager#addSelectionListener(SelectionChangedListener, org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
+     *      To add a global listener.
+     */
+    public void addSelectionListener(DataSelectionListener listener) {
+        selectionListeners.addListener(listener);
+    }
+
+    /**
+     * Remove a listener that listens to selection changes in this specific data set.
+     * @param listener The listener.
+     * @see #addSelectionListener(DataSelectionListener)
+     */
+    public void removeSelectionListener(DataSelectionListener listener) {
+        selectionListeners.removeListener(listener);
+    }
+
+    /*---------------------------------------------------
+     *   OLD SELECTION HANDLING
+     *---------------------------------------------------*/
+
+    /**
      * A list of listeners to selection changed events. The list is static, as listeners register
      * themselves for any dataset selection changes that occur, regardless of the current active
      * dataset. (However, the selection does only change in the active layer)
@@ -662,6 +703,8 @@
     /**
      * Adds a new selection listener.
      * @param listener The selection listener to add
+     * @see #addSelectionListener(DataSelectionListener)
+     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
      */
     public static void addSelectionListener(SelectionChangedListener listener) {
         ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener);
@@ -670,6 +713,8 @@
     /**
      * Removes a selection listener.
      * @param listener The selection listener to remove
+     * @see #removeSelectionListener(DataSelectionListener)
+     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
      */
     public static void removeSelectionListener(SelectionChangedListener listener) {
         selListeners.remove(listener);
@@ -678,18 +723,19 @@
     /**
      * Notifies all registered {@link SelectionChangedListener} about the current selection in
      * this dataset.
-     *
+     * @deprecated You should never need to do this from the outside.
      */
+    @Deprecated
     public void fireSelectionChanged() {
-        Collection<? extends OsmPrimitive> currentSelection = getAllSelected();
+        fireDreprecatedSelectionChange(getAllSelected());
+    }
+
+    private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
         for (SelectionChangedListener l : selListeners) {
             l.selectionChanged(currentSelection);
         }
     }
 
-    private Set<OsmPrimitive> selectedPrimitives = new LinkedHashSet<>();
-    private Collection<OsmPrimitive> selectionSnapshot;
-
     /**
      * Returns selected nodes and ways.
      * @return selected nodes and ways
@@ -753,14 +799,7 @@
      * @return unmodifiable collection of primitives
      */
     public Collection<OsmPrimitive> getAllSelected() {
-        Collection<OsmPrimitive> currentList;
-        synchronized (selectionLock) {
-            if (selectionSnapshot == null) {
-                selectionSnapshot = Collections.unmodifiableList(new ArrayList<>(selectedPrimitives));
-            }
-            currentList = selectionSnapshot;
-        }
-        return currentList;
+        return currentSelectedPrimitives;
     }
 
     /**
@@ -792,7 +831,7 @@
      * @return whether the selection is empty or not
      */
     public boolean selectionEmpty() {
-        return selectedPrimitives.isEmpty();
+        return currentSelectedPrimitives.isEmpty();
     }
 
     /**
@@ -801,48 +840,10 @@
      * @return whether {@code osm} is selected or not
      */
     public boolean isSelected(OsmPrimitive osm) {
-        return selectedPrimitives.contains(osm);
+        return currentSelectedPrimitives.contains(osm);
     }
 
     /**
-     * Toggles the selected state of the given collection of primitives.
-     * @param osm The primitives to toggle
-     */
-    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
-        boolean changed = false;
-        synchronized (selectionLock) {
-            for (PrimitiveId o : osm) {
-                changed = changed | this.dotoggleSelected(o);
-            }
-            if (changed) {
-                selectionSnapshot = null;
-            }
-        }
-        if (changed) {
-            fireSelectionChanged();
-        }
-    }
-
-    /**
-     * Toggles the selected state of the given collection of primitives.
-     * @param osm The primitives to toggle
-     */
-    public void toggleSelected(PrimitiveId... osm) {
-        toggleSelected(Arrays.asList(osm));
-    }
-
-    private boolean dotoggleSelected(PrimitiveId primitiveId) {
-        OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
-        if (primitive == null)
-            return false;
-        if (!selectedPrimitives.remove(primitive)) {
-            selectedPrimitives.add(primitive);
-        }
-        selectionSnapshot = null;
-        return true;
-    }
-
-    /**
      * set what virtual nodes should be highlighted. Requires a Collection of
      * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
      * otherwise.
@@ -874,23 +875,11 @@
      *
      * @param selection the selection
      * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
+     * @deprecated Use {@link #setSelected(Collection)} instead. To bee removed end of 2017. Does not seem to be used by plugins.
      */
+    @Deprecated
     public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
-        boolean changed;
-        synchronized (selectionLock) {
-            Set<OsmPrimitive> oldSelection = new LinkedHashSet<>(selectedPrimitives);
-            selectedPrimitives = new LinkedHashSet<>();
-            addSelected(selection, false);
-            changed = !oldSelection.equals(selectedPrimitives);
-            if (changed) {
-                selectionSnapshot = null;
-            }
-        }
-
-        if (changed && fireSelectionChangeEvent) {
-            // If selection is not empty then event was already fired in addSelecteds
-            fireSelectionChanged();
-        }
+        setSelected(selection);
     }
 
     /**
@@ -900,24 +889,24 @@
      * @param selection the selection
      */
     public void setSelected(Collection<? extends PrimitiveId> selection) {
-        setSelected(selection, true /* fire selection change event */);
+        setSelected(selection.stream());
     }
 
     /**
      * Sets the current selection to the primitives in <code>osm</code>
      * and notifies all {@link SelectionChangedListener}.
      *
-     * @param osm the primitives to set
+     * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
      */
     public void setSelected(PrimitiveId... osm) {
-        if (osm.length == 1 && osm[0] == null) {
-            setSelected();
-            return;
-        }
-        List<PrimitiveId> list = Arrays.asList(osm);
-        setSelected(list);
+        setSelected(Stream.of(osm).filter(Objects::nonNull));
     }
 
+    private void setSelected(Stream<? extends PrimitiveId> stream) {
+        doSelectionChange(old -> new SelectionReplaceEvent(this, old,
+                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
+    }
+
     /**
      * Adds the primitives in <code>selection</code> to the current selection
      * and notifies all {@link SelectionChangedListener}.
@@ -925,7 +914,7 @@
      * @param selection the selection
      */
     public void addSelected(Collection<? extends PrimitiveId> selection) {
-        addSelected(selection, true /* fire selection change event */);
+        addSelected(selection.stream());
     }
 
     /**
@@ -935,93 +924,100 @@
      * @param osm the primitives to add
      */
     public void addSelected(PrimitiveId... osm) {
-        addSelected(Arrays.asList(osm));
+        addSelected(Stream.of(osm));
     }
 
+    private void addSelected(Stream<? extends PrimitiveId> stream) {
+        doSelectionChange(old -> new SelectionAddEvent(this, old,
+                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
+    }
+
     /**
-     * Adds the primitives in <code>selection</code> to the current selection.
-     * Notifies all {@link 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
-     * @return if the selection was changed in the process
+     * Removes the selection from every value in the collection.
+     * @param osm The collection of ids to remove the selection from.
      */
-    private boolean addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
-        boolean changed = false;
-        synchronized (selectionLock) {
-            for (PrimitiveId id: selection) {
-                OsmPrimitive primitive = getPrimitiveByIdChecked(id);
-                if (primitive != null) {
-                    changed = changed | selectedPrimitives.add(primitive);
-                }
-            }
-            if (changed) {
-                selectionSnapshot = null;
-            }
-        }
-        if (fireSelectionChangeEvent && changed) {
-            fireSelectionChanged();
-        }
-        return changed;
+    public void clearSelection(PrimitiveId... osm) {
+        clearSelection(Stream.of(osm));
     }
 
     /**
-     * clear all highlights of virtual nodes
+     * Removes the selection from every value in the collection.
+     * @param list The collection of ids to remove the selection from.
      */
-    public void clearHighlightedVirtualNodes() {
-        setHighlightedVirtualNodes(new ArrayList<WaySegment>());
+    public void clearSelection(Collection<? extends PrimitiveId> list) {
+        clearSelection(list.stream());
     }
 
     /**
-     * clear all highlights of way segments
+     * Clears the current selection.
      */
-    public void clearHighlightedWaySegments() {
-        setHighlightedWaySegments(new ArrayList<WaySegment>());
+    public void clearSelection() {
+        setSelected(Stream.empty());
     }
 
+    private void clearSelection(Stream<? extends PrimitiveId> stream) {
+        doSelectionChange(old -> new SelectionRemoveEvent(this, old,
+                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
+    }
+
     /**
-     * Removes the selection from every value in the collection.
-     * @param osm The collection of ids to remove the selection from.
+     * Toggles the selected state of the given collection of primitives.
+     * @param osm The primitives to toggle
      */
-    public void clearSelection(PrimitiveId... osm) {
-        clearSelection(Arrays.asList(osm));
+    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
+        toggleSelected(osm.stream());
     }
 
     /**
-     * Removes the selection from every value in the collection.
-     * @param list The collection of ids to remove the selection from.
+     * Toggles the selected state of the given collection of primitives.
+     * @param osm The primitives to toggle
      */
-    public void clearSelection(Collection<? extends PrimitiveId> list) {
-        boolean changed = false;
-        synchronized (selectionLock) {
-            for (PrimitiveId id:list) {
-                OsmPrimitive primitive = getPrimitiveById(id);
-                if (primitive != null) {
-                    changed = changed | selectedPrimitives.remove(primitive);
-                }
-            }
-            if (changed) {
-                selectionSnapshot = null;
-            }
-        }
-        if (changed) {
-            fireSelectionChanged();
-        }
+    public void toggleSelected(PrimitiveId... osm) {
+        toggleSelected(Stream.of(osm));
     }
 
+    private void toggleSelected(Stream<? extends PrimitiveId> stream) {
+        doSelectionChange(old -> new SelectionToggleEvent(this, old,
+                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
+    }
+
     /**
-     * Clears the current selection.
+     * Do a selection change.
+     * <p>
+     * This is the only method that changes the current selection state.
+     * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
+     * @return if the command did change the selection.
      */
-    public void clearSelection() {
-        if (!selectedPrimitives.isEmpty()) {
+    private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
+        lock.readLock().lock();
+        try {
             synchronized (selectionLock) {
-                selectedPrimitives.clear();
-                selectionSnapshot = null;
+                SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
+                if (event.isNop()) {
+                    return false;
+                }
+                currentSelectedPrimitives = event.getSelection();
+                selectionListeners.fireEvent(l -> l.selectionChanged(event));
+                return true;
             }
-            fireSelectionChanged();
+        } finally {
+            lock.readLock().unlock();
         }
     }
 
+    /**
+     * clear all highlights of virtual nodes
+     */
+    public void clearHighlightedVirtualNodes() {
+        setHighlightedVirtualNodes(new ArrayList<WaySegment>());
+    }
+
+    /**
+     * clear all highlights of way segments
+     */
+    public void clearHighlightedWaySegments() {
+        setHighlightedWaySegments(new ArrayList<WaySegment>());
+    }
     @Override
     public synchronized Area getDataSourceArea() {
         if (cachedDataSourceArea == null) {
@@ -1371,40 +1367,18 @@
     public void cleanupDeletedPrimitives() {
         beginUpdate();
         try {
-            boolean changed = cleanupDeleted(nodes.iterator());
-            if (cleanupDeleted(ways.iterator())) {
-                changed = true;
-            }
-            if (cleanupDeleted(relations.iterator())) {
-                changed = true;
-            }
-            if (changed) {
-                fireSelectionChanged();
-            }
+            cleanupDeleted(Stream.concat(
+                    nodes.stream(), Stream.concat(ways.stream(), relations.stream())));
         } finally {
             endUpdate();
         }
     }
 
-    private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
-        boolean changed = false;
-        synchronized (selectionLock) {
-            while (it.hasNext()) {
-                OsmPrimitive primitive = it.next();
-                if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) {
-                    selectedPrimitives.remove(primitive);
-                    selectionSnapshot = null;
-                    allPrimitives.remove(primitive);
-                    primitive.setDataset(null);
-                    changed = true;
-                    it.remove();
-                }
-            }
-            if (changed) {
-                selectionSnapshot = null;
-            }
-        }
-        return changed;
+    private void cleanupDeleted(Stream<? extends OsmPrimitive> it) {
+        clearSelection(it
+                .filter(primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()))
+                .peek(allPrimitives::remove)
+                .peek(primitive -> primitive.setDataset(null)));
     }
 
     /**
Index: src/org/openstreetmap/josm/data/osm/DataSelectionListener.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/DataSelectionListener.java	(Revision 0)
+++ src/org/openstreetmap/josm/data/osm/DataSelectionListener.java	(Revision 0)
@@ -0,0 +1,290 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * This is a listener that listens to selection change events in the data set.
+ * @author Michael Zangl
+ * @since xxx
+ */
+@FunctionalInterface
+public interface DataSelectionListener {
+
+    /**
+     * Called whenever the selection is changed.
+     * @param e The selection change event.
+     */
+    void selectionChanged(SelectionChangeEvent e);
+
+    /**
+     * The event that is fired when the selection changed.
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public static interface SelectionChangeEvent {
+        /**
+         * Gets the previous selection
+         * <p>
+         * This collection cannot be modified and will not change.
+         * @return The old selection
+         */
+        public Set<OsmPrimitive> getOldSelection();
+
+        /**
+         * Gets the new selection
+         * <p>
+         * This collection cannot be modified and will not change.
+         * @return The new selection
+         */
+        public Set<OsmPrimitive> getSelection();
+
+        /**
+         * Gets the primitives that have been removed from the selection.
+         * <p>
+         * Those are the primitives contained in {@link #getOldSelection()} but not in {@link #getSelection()}
+         * <p>
+         * This collection cannot be modified and will not change.
+         * @return The primitives
+         */
+        public Set<OsmPrimitive> getRemoved();
+
+        /**
+         * Gets the primitives that have been added to the selection.
+         * <p>
+         * Those are the primitives contained in {@link #getSelection()} but not in {@link #getOldSelection()}
+         * <p>
+         * This collection cannot be modified and will not change.
+         * @return The primitives
+         */
+        public Set<OsmPrimitive> getAdded();
+
+        /**
+         * Gets the data set that triggered this selection event.
+         * @return The data set.
+         */
+        public DataSet getSource();
+
+        /**
+         * Test if this event did not change anything.
+         * <p>
+         * Should return true for all events that are fired.
+         * @return <code>true</code> if this did not change the selection.
+         */
+        default boolean isNop() {
+            return getAdded().isEmpty() && getRemoved().isEmpty();
+        }
+    }
+
+    /**
+     * The base class for selection events
+     * @author Michael Zangl
+     * @since xxx
+     */
+    abstract static class AbstractSelectionEvent implements SelectionChangeEvent {
+        private final DataSet source;
+        private final Set<OsmPrimitive> old;
+
+        public AbstractSelectionEvent(DataSet source, Set<OsmPrimitive> old) {
+            CheckParameterUtil.ensureParameterNotNull(source, "source");
+            CheckParameterUtil.ensureParameterNotNull(old, "old");
+            this.source = source;
+            this.old = Collections.unmodifiableSet(old);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getOldSelection() {
+            return old;
+        }
+
+        @Override
+        public DataSet getSource() {
+            return source;
+        }
+    }
+
+
+    /**
+     * The selection is replaced by a new selection
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public static class SelectionReplaceEvent extends AbstractSelectionEvent {
+        private final Set<OsmPrimitive> current;
+        private Set<OsmPrimitive> removed;
+        private Set<OsmPrimitive> added;
+
+        /**
+         * Create a {@link SelectionReplaceEvent}
+         * @param source The source dataset
+         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
+         * @param newSelection The primitives of the new selection.
+         */
+        public SelectionReplaceEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> newSelection) {
+            super(source, old);
+            this.current = newSelection.collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<OsmPrimitive> getSelection() {
+            return current;
+        }
+
+        @Override
+        public synchronized Set<OsmPrimitive> getRemoved() {
+            if (removed == null) {
+                removed = getOldSelection().stream().filter(p -> !current.contains(p)).collect(Collectors.toSet());
+            }
+            return removed;
+        }
+
+        @Override
+        public synchronized Set<OsmPrimitive> getAdded() {
+            if (added == null) {
+                added = current.stream().filter(p -> !getOldSelection().contains(p)).collect(Collectors.toSet());
+            }
+            return added;
+        }
+    }
+
+    /**
+     * Primitives are added to the selection
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public static class SelectionAddEvent extends AbstractSelectionEvent {
+        private final Set<OsmPrimitive> add;
+        private final Set<OsmPrimitive> current;
+
+        /**
+         * Create a {@link SelectionAddEvent}
+         * @param source The source dataset
+         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
+         * @param toAdd The primitives to add.
+         */
+        public SelectionAddEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toAdd) {
+            super(source, old);
+            this.add = toAdd.filter(p -> !old.contains(p)).collect(Collectors.toSet());
+            if (this.add.isEmpty()) {
+                this.current = this.getOldSelection();
+            } else {
+                this.current = new HashSet<>(old);
+                this.current.addAll(add);
+            }
+        }
+
+        @Override
+        public Set<OsmPrimitive> getSelection() {
+            return Collections.unmodifiableSet(current);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getRemoved() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public Set<OsmPrimitive> getAdded() {
+            return Collections.unmodifiableSet(add);
+        }
+    }
+
+    /**
+     * Primitives are removed from the selection
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public static class SelectionRemoveEvent extends AbstractSelectionEvent {
+        private final Set<OsmPrimitive> remove;
+        private final Set<OsmPrimitive> current;
+
+        /**
+         * Create a {@link SelectionRemoveEvent}
+         * @param source The source dataset
+         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
+         * @param toRemove The primitives to remove.
+         */
+        public SelectionRemoveEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toRemove) {
+            super(source, old);
+            this.remove = toRemove.filter(old::contains).collect(Collectors.toSet());
+            if (this.remove.isEmpty()) {
+                this.current = this.getOldSelection();
+            } else {
+                HashSet<OsmPrimitive> currentSet = new HashSet<>(old);
+                currentSet.removeAll(remove);
+                current = Collections.unmodifiableSet(currentSet);
+            }
+        }
+
+        @Override
+        public Set<OsmPrimitive> getSelection() {
+            return Collections.unmodifiableSet(current);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getRemoved() {
+            return Collections.unmodifiableSet(remove);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getAdded() {
+            return Collections.emptySet();
+        }
+    }
+
+    /**
+     * Toggle the selected state of a primitive
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public static class SelectionToggleEvent extends AbstractSelectionEvent {
+        private final Set<OsmPrimitive> current;
+        private final Set<OsmPrimitive> remove;
+        private final Set<OsmPrimitive> add;
+
+        /**
+         * Create a {@link SelectionToggleEvent}
+         * @param source The source dataset
+         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
+         * @param toToggle The primitives to toggle.
+         */
+        public SelectionToggleEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toToggle) {
+            super(source, old);
+            HashSet<OsmPrimitive> currentSet = new HashSet<>(old);
+            HashSet<OsmPrimitive> removeSet = new HashSet<>();
+            HashSet<OsmPrimitive> addSet = new HashSet<>();
+            toToggle.forEach(p -> {
+                if (currentSet.remove(p)) {
+                    removeSet.add(p);
+                } else {
+                    addSet.add(p);
+                    currentSet.add(p);
+                }
+            });
+            this.current = Collections.unmodifiableSet(currentSet);
+            this.remove = Collections.unmodifiableSet(removeSet);
+            this.add = Collections.unmodifiableSet(addSet);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getSelection() {
+            return Collections.unmodifiableSet(current);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getRemoved() {
+            return Collections.unmodifiableSet(remove);
+        }
+
+        @Override
+        public Set<OsmPrimitive> getAdded() {
+            return Collections.unmodifiableSet(add);
+        }
+    }
+}
Index: src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java	(Revision 12028)
+++ src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java	(Arbeitskopie)
@@ -2,23 +2,37 @@
 package org.openstreetmap.josm.data.osm.event;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Stream;
 
 import javax.swing.SwingUtilities;
 
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSelectionListener;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
+import org.openstreetmap.josm.gui.layer.MainLayerManager;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 
 /**
- * Similar like {@link DatasetEventManager}, just for selection events. Because currently selection changed
- * event are global, only FIRE_IN_EDT and FIRE_EDT_CONSOLIDATED modes are really useful
+ * Similar like {@link DatasetEventManager}, just for selection events.
+ *
+ * It allows to register listeners to global selection events for the selection in the current edit layer.
+ *
+ * If you want to listen to selections to a specific data layer,
+ * you can register a listener to that layer by using {@link DataSet#addSelectionListener(DataSelectionListener)}
+ *
  * @since 2912
  */
-public class SelectionEventManager implements SelectionChangedListener {
+public class SelectionEventManager implements DataSelectionListener, ActiveLayerChangeListener {
 
     private static final SelectionEventManager instance = new SelectionEventManager();
 
@@ -58,8 +72,11 @@
     /**
      * Constructs a new {@code SelectionEventManager}.
      */
-    public SelectionEventManager() {
-        DataSet.addSelectionListener(this);
+    protected SelectionEventManager() {
+        MainLayerManager layerManager = Main.getLayerManager();
+        // We do not allow for destructing this object.
+        // Currently, this is a singleton class, so this is not required.
+        layerManager.addAndFireActiveLayerChangeListener(this);
     }
 
     /**
@@ -88,7 +105,31 @@
     }
 
     @Override
-    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
+        DataSet oldDataSet = e.getPreviousEditDataSet();
+        if (oldDataSet != null) {
+            // Fake a selection removal
+            // Relying on this allows components to not have to monitor layer changes.
+            // If we would not do this, e.g. the move command would have a hard time tracking which layer
+            // the last moved selection was in.
+            SelectionReplaceEvent event = new SelectionReplaceEvent(oldDataSet,
+                    new HashSet<>(oldDataSet.getAllSelected()), Stream.empty());
+            selectionChanged(event);
+            oldDataSet.removeSelectionListener(this);
+        }
+        DataSet newDataSet = e.getSource().getEditDataSet();
+        if (newDataSet != null) {
+            newDataSet.addSelectionListener(this);
+            // Fake a selection add
+            SelectionReplaceEvent event = new SelectionReplaceEvent(newDataSet,
+                    Collections.emptySet(), newDataSet.getAllSelected().stream());
+            selectionChanged(event);
+        }
+    }
+
+    @Override
+    public void selectionChanged(SelectionChangeEvent e) {
+        Set<OsmPrimitive> newSelection = e.getSelection();
         fireEvents(normalListeners, newSelection);
         selection = newSelection;
         SwingUtilities.invokeLater(edtRunnable);
Index: src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(Revision 12028)
+++ src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(Arbeitskopie)
@@ -849,7 +849,6 @@
             updateKeyModifiers(e);
             if (ctrl) mergePrims(e.getPoint());
         }
-        getLayerManager().getEditDataSet().fireSelectionChanged();
     }
 
     static class ConfirmMoveDialog extends ExtendedDialog {
Index: src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java
===================================================================
--- src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java	(Revision 12028)
+++ src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java	(Arbeitskopie)
@@ -159,6 +159,6 @@
         } while (way != null);
 
         if (selectionChanged)
-            data.setSelected(selection, true);
+            data.setSelected(selection);
     }
 }
Index: src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java	(Revision 12028)
+++ src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java	(Arbeitskopie)
@@ -75,7 +75,6 @@
 
         // make sure everybody is notified about the changes
         //
-        layer.data.fireSelectionChanged();
         editor.setRelation(newRelation);
         if (editor instanceof RelationEditor) {
             RelationDialogManager.getRelationDialogManager().updateContext(
@@ -107,7 +106,6 @@
         memberTableModel.applyToRelation(editedRelation);
         if (!editedRelation.hasEqualSemanticAttributes(editor.getRelation(), false)) {
             Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation));
-            layer.data.fireSelectionChanged();
         }
     }
 
Index: test/unit/org/openstreetmap/josm/data/osm/event/SelectionEventManagerTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/osm/event/SelectionEventManagerTest.java	(Revision 0)
+++ test/unit/org/openstreetmap/josm/data/osm/event/SelectionEventManagerTest.java	(Revision 0)
@@ -0,0 +1,81 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.event;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.CommandTest.CommandTestDataWithRelation;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Tests the {@link SelectionEventManager}
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class SelectionEventManagerTest {
+    private final class SelectionListener implements SelectionChangedListener {
+        private Collection<? extends OsmPrimitive> newSelection;
+
+        @Override
+        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+            this.newSelection = newSelection;
+        }
+    }
+
+    /**
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences();
+
+    /**
+     * Tests that events in the active layer are propagated.
+     */
+    @Test
+    public void test() {
+        // automatically adds the layers
+        CommandTestDataWithRelation testData1 = new CommandTestDataWithRelation();
+        CommandTestDataWithRelation testData2 = new CommandTestDataWithRelation();
+        Main.getLayerManager().setActiveLayer(testData1.layer);
+        assertEquals(testData1.layer, Main.getLayerManager().getEditLayer());
+
+        SelectionListener listener = new SelectionListener();
+        SelectionEventManager.getInstance().addSelectionListener(listener , FireMode.IMMEDIATELY);
+        assertNull(listener.newSelection);
+
+        // active layer, should change
+        testData1.layer.data.setSelected(testData1.existingNode.getPrimitiveId());
+        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList(testData1.existingNode)), listener.newSelection);
+
+        listener.newSelection = null;
+        testData1.layer.data.clearSelection(testData1.existingNode.getPrimitiveId());
+        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList()), listener.newSelection);
+
+        listener.newSelection = null;
+        testData1.layer.data.addSelected(testData1.existingNode2.getPrimitiveId());
+        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList(testData1.existingNode2)), listener.newSelection);
+
+        // changing to other dataset should trigger a empty selection
+        listener.newSelection = null;
+        Main.getLayerManager().setActiveLayer(testData2.layer);
+        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList()), listener.newSelection);
+
+        // This should not trigger anything, since the layer is not active any more.
+        listener.newSelection = null;
+        testData1.layer.data.clearSelection(testData1.existingNode.getPrimitiveId());
+        assertNull(listener.newSelection);
+    }
+
+}
