Index: /trunk/src/org/openstreetmap/josm/actions/AbstractUploadAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AbstractUploadAction.java	(revision 15608)
+++ /trunk/src/org/openstreetmap/josm/actions/AbstractUploadAction.java	(revision 15609)
@@ -12,5 +12,5 @@
  * Abstract super-class of all upload actions.
  * Listens to layer change events to update its enabled state.
- * @since xxx
+ * @since 15513
  */
 public abstract class AbstractUploadAction extends JosmAction {
Index: /trunk/src/org/openstreetmap/josm/data/osm/AbstractDataSourceChangeEvent.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/AbstractDataSourceChangeEvent.java	(revision 15609)
+++ /trunk/src/org/openstreetmap/josm/data/osm/AbstractDataSourceChangeEvent.java	(revision 15609)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.Set;
+
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * The base class for data source change events
+ *
+ * @author Taylor Smock
+ * @since 15609
+ */
+public abstract class AbstractDataSourceChangeEvent implements DataSourceChangeEvent {
+
+    private DataSet source;
+    private Set<DataSource> old;
+
+    /**
+     * Create a Data Source change event
+     *
+     * @param source The DataSet that is originating the change
+     * @param old    The previous set of DataSources
+     */
+    public AbstractDataSourceChangeEvent(DataSet source, Set<DataSource> old) {
+        CheckParameterUtil.ensureParameterNotNull(source, "source");
+        CheckParameterUtil.ensureParameterNotNull(old, "old");
+        this.source = source;
+        this.old = old;
+    }
+
+    @Override
+    public Set<DataSource> getOldDataSources() {
+        return old;
+    }
+
+    @Override
+    public DataSet getSource() {
+        return source;
+    }
+}
+
Index: /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 15608)
+++ /trunk/src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 15609)
@@ -11,4 +11,5 @@
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -43,4 +44,6 @@
 import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
 import org.openstreetmap.josm.data.osm.event.DataSetListener;
+import org.openstreetmap.josm.data.osm.event.DataSourceAddedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSourceRemovedEvent;
 import org.openstreetmap.josm.data.osm.event.FilterChangedEvent;
 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
@@ -166,4 +169,9 @@
     private final Collection<DataSource> dataSources = new LinkedList<>();
 
+    /**
+     * A list of listeners that listen to DataSource changes on this layer
+     */
+    private final ListenerList<DataSourceListener> dataSourceListeners = ListenerList.create();
+
     private final ConflictCollection conflicts = new ConflictCollection();
 
@@ -226,7 +234,10 @@
                         .collect(Collectors.toList()));
             }
+            DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(this,
+                    new LinkedHashSet<>(dataSources), copyFrom.dataSources.stream());
             for (DataSource source : copyFrom.dataSources) {
                 dataSources.add(new DataSource(source));
             }
+            dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
             version = copyFrom.version;
             uploadPolicy = copyFrom.uploadPolicy;
@@ -272,4 +283,6 @@
      */
     public synchronized boolean addDataSources(Collection<DataSource> sources) {
+        DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(this,
+                new LinkedHashSet<>(dataSources), sources.stream());
         boolean changed = dataSources.addAll(sources);
         if (changed) {
@@ -277,4 +290,5 @@
             cachedDataSourceBounds = null;
         }
+        dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
         return changed;
     }
@@ -573,4 +587,28 @@
     public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
         highlightUpdateListeners.removeListener(listener);
+    }
+
+    /**
+     * Adds a listener that gets notified whenever the data sources change
+     *
+     * @param listener The listener
+     * @see #removeDataSourceListener
+     * @see #getDataSources
+     * @since 15609
+     */
+    public void addDataSourceListener(DataSourceListener listener) {
+        dataSourceListeners.addListener(listener);
+    }
+
+    /**
+     * Removes a listener that gets notified whenever the data sources change
+     *
+     * @param listener The listener
+     * @see #addDataSourceListener
+     * @see #getDataSources
+     * @since 15609
+     */
+    public void removeDataSourceListener(DataSourceListener listener) {
+        dataSourceListeners.removeListener(listener);
     }
 
@@ -1085,5 +1123,10 @@
             synchronized (from) {
                 if (!from.dataSources.isEmpty()) {
-                    if (dataSources.addAll(from.dataSources)) {
+                    DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(
+                            this, new LinkedHashSet<>(dataSources), from.dataSources.stream());
+                    DataSourceRemovedEvent clearEvent = new DataSourceRemovedEvent(
+                            this, new LinkedHashSet<>(from.dataSources), from.dataSources.stream());
+                    if (from.dataSources.stream().filter(dataSource -> !dataSources.contains(dataSource))
+                            .map(dataSources::add).filter(Boolean.TRUE::equals).count() > 0) {
                         cachedDataSourceArea = null;
                         cachedDataSourceBounds = null;
@@ -1092,4 +1135,6 @@
                     from.cachedDataSourceArea = null;
                     from.cachedDataSourceBounds = null;
+                    dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
+                    from.dataSourceListeners.fireEvent(d -> d.dataSourceChange(clearEvent));
                 }
             }
Index: /trunk/src/org/openstreetmap/josm/data/osm/DataSourceChangeEvent.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/DataSourceChangeEvent.java	(revision 15609)
+++ /trunk/src/org/openstreetmap/josm/data/osm/DataSourceChangeEvent.java	(revision 15609)
@@ -0,0 +1,77 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.Set;
+
+import org.openstreetmap.josm.data.DataSource;
+
+/**
+ * The event that is fired when the data source list is changed.
+ *
+ * @author Taylor Smock
+ * @since 15609
+ */
+public interface DataSourceChangeEvent {
+    /**
+     * Gets the previous data source list
+     * <p>
+     * This collection cannot be modified and will not change.
+     *
+     * @return The old data source list
+     */
+    Set<DataSource> getOldDataSources();
+
+    /**
+     * Gets the new data sources. New data sources are added to the end of the
+     * collection.
+     * <p>
+     * This collection cannot be modified and will not change.
+     *
+     * @return The new data sources
+     */
+    Set<DataSource> getDataSources();
+
+    /**
+     * Gets the Data Sources that have been removed from the selection.
+     * <p>
+     * Those are the primitives contained in {@link #getOldDataSources()} but not in
+     * {@link #getDataSources()}
+     * <p>
+     * This collection cannot be modified and will not change.
+     *
+     * @return The DataSources that were removed
+     */
+    Set<DataSource> getRemoved();
+
+    /**
+     * Gets the data sources that have been added to the selection.
+     * <p>
+     * Those are the data sources contained in {@link #getDataSources()} but not in
+     * {@link #getOldDataSources()}
+     * <p>
+     * This collection cannot be modified and will not change.
+     *
+     * @return The data sources that were added
+     */
+    Set<DataSource> getAdded();
+
+    /**
+     * Gets the data set that triggered this selection event.
+     *
+     * @return The data set.
+     */
+    DataSet getSource();
+
+    /**
+     * Test if this event did not change anything.
+     * <p>
+     * This will return <code>false</code> for all events that are sent to
+     * listeners, so you don't need to test it.
+     *
+     * @return <code>true</code> if this did not change the selection.
+     */
+    default boolean isNop() {
+        return getAdded().isEmpty() && getRemoved().isEmpty();
+    }
+}
+
Index: /trunk/src/org/openstreetmap/josm/data/osm/DataSourceListener.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/DataSourceListener.java	(revision 15609)
+++ /trunk/src/org/openstreetmap/josm/data/osm/DataSourceListener.java	(revision 15609)
@@ -0,0 +1,22 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+/**
+ * This is a listener that listens to selection change events in the data set.
+ *
+ * @author Taylor Smock
+ * @since 15609
+ */
+@FunctionalInterface
+public interface DataSourceListener {
+    /**
+     * Called whenever the data source list is changed.
+     *
+     * You get notified about the new data source list, the sources that were added
+     * and removed and the dataset that triggered the event.
+     *
+     * @param event The data source change event.
+     * @see DataSourceChangeEvent
+     */
+    void dataSourceChange(DataSourceChangeEvent event);
+}
Index: /trunk/src/org/openstreetmap/josm/data/osm/event/DataSourceAddedEvent.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/event/DataSourceAddedEvent.java	(revision 15609)
+++ /trunk/src/org/openstreetmap/josm/data/osm/event/DataSourceAddedEvent.java	(revision 15609)
@@ -0,0 +1,66 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.event;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.osm.AbstractDataSourceChangeEvent;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * There is a new data source
+ *
+ * @author Taylor Smock
+ * @since 15609
+ */
+public class DataSourceAddedEvent extends AbstractDataSourceChangeEvent {
+    private Set<DataSource> current;
+    private Set<DataSource> removed;
+    private final Set<DataSource> added;
+
+    /**
+     * Create a Data Source change event
+     *
+     * @param source         The DataSet that is originating the change
+     * @param old            The previous set of DataSources
+     * @param newDataSources The data sources that are being added
+     */
+    public DataSourceAddedEvent(DataSet source, Set<DataSource> old, Stream<DataSource> newDataSources) {
+        super(source, old);
+        CheckParameterUtil.ensureParameterNotNull(newDataSources, "newDataSources");
+        this.added = newDataSources.collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+
+    @Override
+    public Set<DataSource> getDataSources() {
+        if (current == null) {
+            current = new LinkedHashSet<>(getOldDataSources());
+            current.addAll(added);
+        }
+        return current;
+    }
+
+    @Override
+    public synchronized Set<DataSource> getRemoved() {
+        if (removed == null) {
+            removed = getOldDataSources().stream().filter(s -> !getDataSources().contains(s))
+                    .collect(Collectors.toCollection(LinkedHashSet::new));
+        }
+        return removed;
+    }
+
+    @Override
+    public synchronized Set<DataSource> getAdded() {
+        return added;
+    }
+
+    @Override
+    public String toString() {
+        return "DataSourceAddedEvent [current=" + current + ", removed=" + removed + ", added=" + added + ']';
+    }
+}
+
Index: /trunk/src/org/openstreetmap/josm/data/osm/event/DataSourceRemovedEvent.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/event/DataSourceRemovedEvent.java	(revision 15609)
+++ /trunk/src/org/openstreetmap/josm/data/osm/event/DataSourceRemovedEvent.java	(revision 15609)
@@ -0,0 +1,66 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.event;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.osm.AbstractDataSourceChangeEvent;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * A data source was removed
+ *
+ * @author Taylor Smock
+ * @since 15609
+ */
+public class DataSourceRemovedEvent extends AbstractDataSourceChangeEvent {
+    private Set<DataSource> current;
+    private final Set<DataSource> removed;
+    private Set<DataSource> added;
+
+    /**
+     * Create a Data Source change event
+     *
+     * @param source             The DataSet that is originating the change
+     * @param old                The previous set of DataSources
+     * @param removedDataSources The data sources that are being removed
+     */
+    public DataSourceRemovedEvent(DataSet source, Set<DataSource> old, Stream<DataSource> removedDataSources) {
+        super(source, old);
+        CheckParameterUtil.ensureParameterNotNull(removedDataSources, "removedDataSources");
+        this.removed = removedDataSources.collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+
+    @Override
+    public Set<DataSource> getDataSources() {
+        if (current == null) {
+            current = getOldDataSources().stream().filter(s -> !removed.contains(s))
+                    .collect(Collectors.toCollection(LinkedHashSet::new));
+        }
+        return current;
+    }
+
+    @Override
+    public synchronized Set<DataSource> getRemoved() {
+        return removed;
+    }
+
+    @Override
+    public synchronized Set<DataSource> getAdded() {
+        if (added == null) {
+            added = getDataSources().stream().filter(s -> !getOldDataSources().contains(s))
+                    .collect(Collectors.toCollection(LinkedHashSet::new));
+        }
+        return added;
+    }
+
+    @Override
+    public String toString() {
+        return "DataSourceAddedEvent [current=" + current + ", removed=" + removed + ", added=" + added + ']';
+    }
+}
+
Index: /trunk/test/unit/org/openstreetmap/josm/data/osm/DataSetTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/osm/DataSetTest.java	(revision 15608)
+++ /trunk/test/unit/org/openstreetmap/josm/data/osm/DataSetTest.java	(revision 15609)
@@ -14,5 +14,9 @@
 import org.junit.Rule;
 import org.junit.Test;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.DataSource;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.event.DataSourceAddedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSourceRemovedEvent;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
@@ -248,3 +252,39 @@
         assertTrue(UploadPolicy.DISCOURAGED.compareTo(UploadPolicy.NORMAL) > 0);
     }
+
+    /**
+     * Checks that data source listeners get called when a data source is added
+     */
+    @Test
+    public void testAddDataSourceListener() {
+        DataSourceListener addListener = new DataSourceListener() {
+            @Override
+            public void dataSourceChange(DataSourceChangeEvent event) {
+                assertTrue(event instanceof DataSourceAddedEvent);
+            }
+        };
+
+        DataSet ds = new DataSet();
+        ds.addDataSourceListener(addListener);
+        ds.addDataSource(new DataSource(new Bounds(0, 0, 0.1, 0.1), "fake source"));
+
+    }
+
+    /**
+     * Checks that data source listeners get called when a data source is removed
+     */
+    @Test
+    public void testRemoveDataSourceListener() {
+        DataSourceListener removeListener = new DataSourceListener() {
+            @Override
+            public void dataSourceChange(DataSourceChangeEvent event) {
+                assertTrue(event instanceof DataSourceRemovedEvent);
+            }
+        };
+
+        DataSet ds = new DataSet();
+        ds.addDataSource(new DataSource(new Bounds(0, 0, 0.1, 0.1), "fake source"));
+        ds.addDataSourceListener(removeListener);
+        new DataSet().mergeFrom(ds);
+    }
 }
Index: /trunk/test/unit/org/openstreetmap/josm/data/osm/event/DataSourceAddedEventTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/osm/event/DataSourceAddedEventTest.java	(revision 15609)
+++ /trunk/test/unit/org/openstreetmap/josm/data/osm/event/DataSourceAddedEventTest.java	(revision 15609)
@@ -0,0 +1,84 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.event;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.junit.Test;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSourceChangeEvent;
+
+/**
+ * Test class for {@link DataSourceAddedEvent}
+ *
+ * @author Taylor Smock
+ */
+public class DataSourceAddedEventTest {
+    /**
+     * Get getting the originating data source
+     */
+    @Test
+    public void testGetDataEventSource() {
+        DataSource fakeAdd = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        DataSet ds = new DataSet();
+        assertSame(ds, new DataSourceAddedEvent(ds, Collections.emptySet(), Stream.of(fakeAdd)).getSource());
+    }
+
+    /**
+     * Test that added sources are processed properly
+     */
+    @Test
+    public void testGetAddedSource() {
+        DataSource fakeAdd = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        assertTrue(
+                new DataSourceAddedEvent(new DataSet(), Collections.emptySet(), Stream.empty()).getAdded().isEmpty());
+        DataSourceChangeEvent event = new DataSourceAddedEvent(new DataSet(), Collections.emptySet(),
+                Stream.of(fakeAdd));
+        assertSame(event.getAdded(), event.getAdded());
+        assertSame(fakeAdd, new DataSourceAddedEvent(new DataSet(), Collections.emptySet(), Stream.of(fakeAdd))
+                .getAdded().iterator().next());
+    }
+
+    /**
+     * Test that there are no removed sources
+     */
+    @Test
+    public void testGetRemovedSource() {
+        DataSource fakeAdd = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        assertTrue(
+                new DataSourceAddedEvent(new DataSet(), Collections.emptySet(), Stream.empty()).getRemoved().isEmpty());
+        DataSourceChangeEvent event = new DataSourceAddedEvent(new DataSet(), Collections.emptySet(),
+                Stream.of(fakeAdd));
+        assertSame(event.getRemoved(), event.getRemoved());
+        assertTrue(new DataSourceAddedEvent(new DataSet(), Collections.emptySet(), Stream.of(fakeAdd)).getRemoved()
+                .isEmpty());
+    }
+
+    /**
+     * Check that the sources include newly added data
+     */
+    @Test
+    public void testGetDataSources() {
+        DataSource fakeAdd = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        DataSourceChangeEvent event = new DataSourceAddedEvent(new DataSet(), Collections.emptySet(),
+                Stream.of(fakeAdd));
+        assertSame(event.getDataSources(), event.getDataSources());
+        assertSame(fakeAdd, event.getDataSources().iterator().next());
+    }
+
+    /**
+     * Check that a string is returned with added/current/deleted
+     */
+    @Test
+    public void testToString() {
+        String toString = new DataSourceAddedEvent(new DataSet(), Collections.emptySet(), Stream.empty()).toString();
+        assertTrue(toString.contains("added"));
+        assertTrue(toString.contains("current"));
+        assertTrue(toString.contains("removed"));
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/osm/event/DataSourceRemovedEventTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/osm/event/DataSourceRemovedEventTest.java	(revision 15609)
+++ /trunk/test/unit/org/openstreetmap/josm/data/osm/event/DataSourceRemovedEventTest.java	(revision 15609)
@@ -0,0 +1,91 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.event;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.junit.Test;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DataSourceChangeEvent;
+
+/**
+ * Test class for {@link DataSourceRemovedEvent}
+ *
+ * @author Taylor Smock
+ */
+
+public class DataSourceRemovedEventTest {
+    /**
+     * Get getting the originating data source
+     */
+    @Test
+    public void testGetDataEventSource() {
+        DataSource fakeRemove = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        DataSet ds = new DataSet();
+        assertSame(ds, new DataSourceRemovedEvent(ds, Collections.emptySet(), Stream.of(fakeRemove)).getSource());
+    }
+
+    /**
+     * Test that no sources are added
+     */
+    @Test
+    public void testGetAddedSource() {
+        DataSource fakeRemove = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        assertTrue(
+                new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(), Stream.empty()).getAdded().isEmpty());
+        DataSourceChangeEvent event = new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(),
+                Stream.of(fakeRemove));
+        assertSame(event.getAdded(), event.getAdded());
+        assertTrue(new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(), Stream.of(fakeRemove)).getAdded()
+                .isEmpty());
+    }
+
+    /**
+     * Test that the getting the removed source(s) works properly
+     */
+    @Test
+    public void testGetRemovedSource() {
+        DataSource fakeRemove = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        assertTrue(new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(), Stream.empty()).getRemoved()
+                .isEmpty());
+        DataSourceChangeEvent event = new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(),
+                Stream.of(fakeRemove));
+        assertSame(event.getRemoved(), event.getRemoved());
+        assertSame(fakeRemove, new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(), Stream.of(fakeRemove))
+                .getRemoved().iterator().next());
+    }
+
+    /**
+     * Check that the sources don't include removed data
+     */
+    @Test
+    public void testGetDataSources() {
+        DataSource fakeRemove = new DataSource(new Bounds(0, 0, 0, 0), "fake-source");
+        DataSourceChangeEvent event = new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(),
+                Stream.of(fakeRemove));
+        assertSame(event.getDataSources(), event.getDataSources());
+        assertFalse(event.getDataSources().contains(fakeRemove));
+        assertTrue(new DataSourceRemovedEvent(new DataSet(), Collections.singleton(fakeRemove), Stream.of(fakeRemove))
+                .getDataSources().isEmpty());
+        assertSame(fakeRemove,
+                new DataSourceRemovedEvent(new DataSet(), Collections.singleton(fakeRemove), Stream.empty())
+                        .getDataSources().iterator().next());
+    }
+
+    /**
+     * Check that a string is returned with added/current/deleted
+     */
+    @Test
+    public void testToString() {
+        String toString = new DataSourceRemovedEvent(new DataSet(), Collections.emptySet(), Stream.empty()).toString();
+        assertTrue(toString.contains("added"));
+        assertTrue(toString.contains("current"));
+        assertTrue(toString.contains("removed"));
+    }
+}
