001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.data.osm.event;
003
004 import java.util.ArrayList;
005 import java.util.Arrays;
006 import java.util.List;
007 import java.util.Queue;
008 import java.util.concurrent.CopyOnWriteArrayList;
009 import java.util.concurrent.LinkedBlockingQueue;
010
011 import javax.swing.SwingUtilities;
012
013 import org.openstreetmap.josm.data.osm.DataSet;
014 import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
015 import org.openstreetmap.josm.gui.MapView;
016 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
017
018 /**
019 * This class allows to add DatasetListener to currently active dataset. If active
020 * layer is changed, listeners are automatically registered at new active dataset
021 * (it's no longer necessary to register for layer events and reregister every time
022 * new layer is selected)
023 *
024 * Events in EDT are supported, see {@link #addDatasetListener(DataSetListener, FireMode}
025 *
026 */
027 public class DatasetEventManager implements MapView.EditLayerChangeListener, Listener {
028
029 private static final DatasetEventManager instance = new DatasetEventManager();
030
031 public enum FireMode {
032 IMMEDIATELY,
033 IN_EDT,
034 /**
035 * Fire in event dispatch thread. If more than one event arrived when event queue is checked, merged them to
036 * one event
037 */
038 IN_EDT_CONSOLIDATED}
039
040 private static class ListenerInfo {
041 final DataSetListener listener;
042 final boolean consolidate;
043
044 public ListenerInfo(DataSetListener listener, boolean consolidate) {
045 this.listener = listener;
046 this.consolidate = consolidate;
047 }
048
049 @Override
050 public int hashCode() {
051 return listener.hashCode();
052 }
053
054 @Override
055 public boolean equals(Object o) {
056 return o instanceof ListenerInfo && ((ListenerInfo)o).listener == listener;
057 }
058 }
059
060 public static DatasetEventManager getInstance() {
061 return instance;
062 }
063
064 private final Queue<AbstractDatasetChangedEvent> eventsInEDT = new LinkedBlockingQueue<AbstractDatasetChangedEvent>();
065 private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<ListenerInfo>();
066 private final CopyOnWriteArrayList<ListenerInfo> normalListeners = new CopyOnWriteArrayList<ListenerInfo>();
067 private final DataSetListener myListener = new DataSetListenerAdapter(this);
068
069 public DatasetEventManager() {
070 MapView.addEditLayerChangeListener(this);
071 }
072
073 /**
074 * Register listener, that will receive events from currently active dataset
075 * @param listener
076 * @param fireInEDT If true, listener will be notified in event dispatch thread
077 * instead of thread that caused the dataset change
078 */
079 public void addDatasetListener(DataSetListener listener, FireMode fireMode) {
080 if (fireMode == FireMode.IN_EDT || fireMode == FireMode.IN_EDT_CONSOLIDATED) {
081 inEDTListeners.addIfAbsent(new ListenerInfo(listener, fireMode == FireMode.IN_EDT_CONSOLIDATED));
082 } else {
083 normalListeners.addIfAbsent(new ListenerInfo(listener, false));
084 }
085 }
086
087 public void removeDatasetListener(DataSetListener listener) {
088 ListenerInfo searchListener = new ListenerInfo(listener, false);
089 inEDTListeners.remove(searchListener);
090 normalListeners.remove(searchListener);
091 }
092
093 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
094 if (oldLayer != null) {
095 oldLayer.data.removeDataSetListener(myListener);
096 }
097
098 if (newLayer != null) {
099 newLayer.data.addDataSetListener(myListener);
100 processDatasetEvent(new DataChangedEvent(newLayer.data));
101 } else {
102 processDatasetEvent(new DataChangedEvent(null));
103 }
104 }
105
106 private void fireEvents(List<ListenerInfo> listeners, AbstractDatasetChangedEvent event) {
107 for (ListenerInfo listener: listeners) {
108 if (!listener.consolidate) {
109 event.fire(listener.listener);
110 }
111 }
112 }
113
114 private void fireConsolidatedEvents(List<ListenerInfo> listeners, AbstractDatasetChangedEvent event) {
115 for (ListenerInfo listener: listeners) {
116 if (listener.consolidate) {
117 event.fire(listener.listener);
118 }
119 }
120 }
121
122 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
123 fireEvents(normalListeners, event);
124 eventsInEDT.add(event);
125 SwingUtilities.invokeLater(edtRunnable);
126 }
127
128 private final Runnable edtRunnable = new Runnable() {
129 public void run() {
130 while (!eventsInEDT.isEmpty()) {
131 List<AbstractDatasetChangedEvent> events = new ArrayList<AbstractDatasetChangedEvent>();
132 events.addAll(eventsInEDT);
133
134 DataSet dataSet = null;
135 AbstractDatasetChangedEvent consolidatedEvent = null;
136 AbstractDatasetChangedEvent event = null;
137
138 while ((event = eventsInEDT.poll()) != null) {
139 fireEvents(inEDTListeners, event);
140
141 // DataSet changed - fire consolidated event early
142 if (consolidatedEvent != null && dataSet != event.getDataset()) {
143 fireConsolidatedEvents(inEDTListeners, consolidatedEvent);
144 consolidatedEvent = null;
145 }
146
147 dataSet = event.getDataset();
148
149 // Build consolidated event
150 if (event instanceof DataChangedEvent) {
151 // DataChangeEvent can contains other events, so it gets special handling
152 DataChangedEvent dataEvent = (DataChangedEvent) event;
153 if (dataEvent.getEvents() == null) {
154 consolidatedEvent = dataEvent; // Dataset was completely changed, we can ignore older events
155 } else {
156 if (consolidatedEvent == null) {
157 consolidatedEvent = new DataChangedEvent(dataSet, dataEvent.getEvents());
158 } else if (consolidatedEvent instanceof DataChangedEvent) {
159 List<AbstractDatasetChangedEvent> evts = ((DataChangedEvent) consolidatedEvent).getEvents();
160 if (evts != null) {
161 evts.addAll(dataEvent.getEvents());
162 }
163 } else {
164 AbstractDatasetChangedEvent oldConsolidateEvent = consolidatedEvent;
165 consolidatedEvent = new DataChangedEvent(dataSet, dataEvent.getEvents());
166 ((DataChangedEvent) consolidatedEvent).getEvents().add(oldConsolidateEvent);
167 }
168 }
169 } else {
170 // Normal events
171 if (consolidatedEvent == null) {
172 consolidatedEvent = event;
173 } else if (consolidatedEvent instanceof DataChangedEvent) {
174 List<AbstractDatasetChangedEvent> evs = ((DataChangedEvent) consolidatedEvent).getEvents();
175 if (evs != null) {
176 evs.add(event);
177 }
178 } else {
179 consolidatedEvent = new DataChangedEvent(dataSet,
180 new ArrayList<AbstractDatasetChangedEvent>(Arrays.asList(consolidatedEvent)));
181 }
182
183 }
184 }
185
186 // Fire consolidated event
187 fireConsolidatedEvents(inEDTListeners, consolidatedEvent);
188 }
189 }
190 };
191 }