// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm.event;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.SwingUtilities;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
/**
* This class allows to add DatasetListener to currently active dataset. If active
* layer is changed, listeners are automatically registered at new active dataset
* (it's no longer necessary to register for layer events and reregister every time
* new layer is selected)
*
* Events in EDT are supported, see {@link #addDatasetListener(DataSetListener, FireMode}
*
*/
public class DatasetEventManager implements MapView.EditLayerChangeListener, Listener {
private static final DatasetEventManager instance = new DatasetEventManager();
public enum FireMode {
IMMEDIATELY,
IN_EDT,
/**
* Fire in event dispatch thread. If more than one event arrived when event queue is checked, merged them to
* one event
*/
IN_EDT_CONSOLIDATED}
private static class ListenerInfo {
final DataSetListener listener;
final boolean consolidate;
public ListenerInfo(DataSetListener listener, boolean consolidate) {
this.listener = listener;
this.consolidate = consolidate;
}
@Override
public int hashCode() {
return listener.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof ListenerInfo && ((ListenerInfo)o).listener == listener;
}
}
public static DatasetEventManager getInstance() {
return instance;
}
private final Queue<AbstractDatasetChangedEvent> eventsInEDT = new LinkedBlockingQueue<AbstractDatasetChangedEvent>();
private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<ListenerInfo>();
private final CopyOnWriteArrayList<ListenerInfo> normalListeners = new CopyOnWriteArrayList<ListenerInfo>();
private final DataSetListener myListener = new DataSetListenerAdapter(this);
public DatasetEventManager() {
MapView.addEditLayerChangeListener(this);
}
/**
* Register listener, that will receive events from currently active dataset
* @param listener
* @param fireInEDT If true, listener will be notified in event dispatch thread
* instead of thread that caused the dataset change
*/
public void addDatasetListener(DataSetListener listener, FireMode fireMode) {
if (fireMode == FireMode.IN_EDT || fireMode == FireMode.IN_EDT_CONSOLIDATED) {
inEDTListeners.addIfAbsent(new ListenerInfo(listener, fireMode == FireMode.IN_EDT_CONSOLIDATED));
} else {
normalListeners.addIfAbsent(new ListenerInfo(listener, false));
}
}
public void removeDatasetListener(DataSetListener listener) {
ListenerInfo searchListener = new ListenerInfo(listener, false);
inEDTListeners.remove(searchListener);
normalListeners.remove(searchListener);
}
public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
if (oldLayer != null) {
oldLayer.data.removeDataSetListener(myListener);
}
if (newLayer != null) {
newLayer.data.addDataSetListener(myListener);
processDatasetEvent(new DataChangedEvent(newLayer.data));
} else {
processDatasetEvent(new DataChangedEvent(null));
}
}
private void fireEvents(List<ListenerInfo> listeners, AbstractDatasetChangedEvent event) {
for (ListenerInfo listener: listeners) {
if (!listener.consolidate) {
event.fire(listener.listener);
}
}
}
private void fireConsolidatedEvents(List<ListenerInfo> listeners, AbstractDatasetChangedEvent event) {
for (ListenerInfo listener: listeners) {
if (listener.consolidate) {
event.fire(listener.listener);
}
}
}
public void processDatasetEvent(AbstractDatasetChangedEvent event) {
fireEvents(normalListeners, event);
eventsInEDT.add(event);
SwingUtilities.invokeLater(edtRunnable);
}
private final Runnable edtRunnable = new Runnable() {
public void run() {
while (!eventsInEDT.isEmpty()) {
List<AbstractDatasetChangedEvent> events = new ArrayList<AbstractDatasetChangedEvent>();
events.addAll(eventsInEDT);
DataSet dataSet = null;
AbstractDatasetChangedEvent consolidatedEvent = null;
AbstractDatasetChangedEvent event = null;
while ((event = eventsInEDT.poll()) != null) {
fireEvents(inEDTListeners, event);
// DataSet changed - fire consolidated event early
if (consolidatedEvent != null && dataSet != event.getDataset()) {
fireConsolidatedEvents(inEDTListeners, consolidatedEvent);
consolidatedEvent = null;
}
dataSet = event.getDataset();
// Build consolidated event
if (event instanceof DataChangedEvent) {
// DataChangeEvent can contains other events, so it gets special handling
DataChangedEvent dataEvent = (DataChangedEvent) event;
if (dataEvent.getEvents() == null) {
consolidatedEvent = dataEvent; // Dataset was completely changed, we can ignore older events
} else {
if (consolidatedEvent == null) {
consolidatedEvent = new DataChangedEvent(dataSet, dataEvent.getEvents());
} else if (consolidatedEvent instanceof DataChangedEvent) {
List<AbstractDatasetChangedEvent> evts = ((DataChangedEvent) consolidatedEvent).getEvents();
if (evts != null) {
evts.addAll(dataEvent.getEvents());
}
} else {
AbstractDatasetChangedEvent oldConsolidateEvent = consolidatedEvent;
consolidatedEvent = new DataChangedEvent(dataSet, dataEvent.getEvents());
((DataChangedEvent) consolidatedEvent).getEvents().add(oldConsolidateEvent);
}
}
} else {
// Normal events
if (consolidatedEvent == null) {
consolidatedEvent = event;
} else if (consolidatedEvent instanceof DataChangedEvent) {
List<AbstractDatasetChangedEvent> evs = ((DataChangedEvent) consolidatedEvent).getEvents();
if (evs != null) {
evs.add(event);
}
} else {
consolidatedEvent = new DataChangedEvent(dataSet,
new ArrayList<AbstractDatasetChangedEvent>(Arrays.asList(consolidatedEvent)));
}
}
}
// Fire consolidated event
fireConsolidatedEvents(inEDTListeners, consolidatedEvent);
}
}
};
}