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