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