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