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<AbstractDatasetChangedEvent>(); 071 private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<ListenerInfo>(); 072 private final CopyOnWriteArrayList<ListenerInfo> normalListeners = new CopyOnWriteArrayList<ListenerInfo>(); 073 private final DataSetListener myListener = new DataSetListenerAdapter(this); 074 075 public DatasetEventManager() { 076 MapView.addEditLayerChangeListener(this); 077 } 078 079 /** 080 * Register listener, that will receive events from currently active dataset 081 * @param listener the listener to be registered 082 * @param fireMode If {@link FireMode#IN_EDT} or {@link FireMode#IN_EDT_CONSOLIDATED}, 083 * listener will be notified in event dispatch thread instead of thread that caused 084 * the dataset change 085 */ 086 public void addDatasetListener(DataSetListener listener, FireMode fireMode) { 087 if (fireMode == FireMode.IN_EDT || fireMode == FireMode.IN_EDT_CONSOLIDATED) { 088 inEDTListeners.addIfAbsent(new ListenerInfo(listener, fireMode == FireMode.IN_EDT_CONSOLIDATED)); 089 } else { 090 normalListeners.addIfAbsent(new ListenerInfo(listener, false)); 091 } 092 } 093 094 public void removeDatasetListener(DataSetListener listener) { 095 ListenerInfo searchListener = new ListenerInfo(listener, false); 096 inEDTListeners.remove(searchListener); 097 normalListeners.remove(searchListener); 098 } 099 100 @Override 101 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 102 if (oldLayer != null) { 103 oldLayer.data.removeDataSetListener(myListener); 104 } 105 106 if (newLayer != null) { 107 newLayer.data.addDataSetListener(myListener); 108 processDatasetEvent(new DataChangedEvent(newLayer.data)); 109 } else { 110 processDatasetEvent(new DataChangedEvent(null)); 111 } 112 } 113 114 private void fireEvents(List<ListenerInfo> listeners, AbstractDatasetChangedEvent event) { 115 for (ListenerInfo listener: listeners) { 116 if (!listener.consolidate) { 117 event.fire(listener.listener); 118 } 119 } 120 } 121 122 private void fireConsolidatedEvents(List<ListenerInfo> listeners, AbstractDatasetChangedEvent event) { 123 for (ListenerInfo listener: listeners) { 124 if (listener.consolidate) { 125 event.fire(listener.listener); 126 } 127 } 128 } 129 130 @Override 131 public void processDatasetEvent(AbstractDatasetChangedEvent event) { 132 fireEvents(normalListeners, event); 133 eventsInEDT.add(event); 134 SwingUtilities.invokeLater(edtRunnable); 135 } 136 137 private final Runnable edtRunnable = new Runnable() { 138 @Override 139 public void run() { 140 while (!eventsInEDT.isEmpty()) { 141 List<AbstractDatasetChangedEvent> events = new ArrayList<AbstractDatasetChangedEvent>(); 142 events.addAll(eventsInEDT); 143 144 DataSet dataSet = null; 145 AbstractDatasetChangedEvent consolidatedEvent = null; 146 AbstractDatasetChangedEvent event = null; 147 148 while ((event = eventsInEDT.poll()) != null) { 149 fireEvents(inEDTListeners, event); 150 151 // DataSet changed - fire consolidated event early 152 if (consolidatedEvent != null && dataSet != event.getDataset()) { 153 fireConsolidatedEvents(inEDTListeners, consolidatedEvent); 154 consolidatedEvent = null; 155 } 156 157 dataSet = event.getDataset(); 158 159 // Build consolidated event 160 if (event instanceof DataChangedEvent) { 161 // DataChangeEvent can contains other events, so it gets special handling 162 DataChangedEvent dataEvent = (DataChangedEvent) event; 163 if (dataEvent.getEvents() == null) { 164 consolidatedEvent = dataEvent; // Dataset was completely changed, we can ignore older events 165 } else { 166 if (consolidatedEvent == null) { 167 consolidatedEvent = new DataChangedEvent(dataSet, dataEvent.getEvents()); 168 } else if (consolidatedEvent instanceof DataChangedEvent) { 169 List<AbstractDatasetChangedEvent> evts = ((DataChangedEvent) consolidatedEvent).getEvents(); 170 if (evts != null) { 171 evts.addAll(dataEvent.getEvents()); 172 } 173 } else { 174 AbstractDatasetChangedEvent oldConsolidateEvent = consolidatedEvent; 175 consolidatedEvent = new DataChangedEvent(dataSet, dataEvent.getEvents()); 176 ((DataChangedEvent) consolidatedEvent).getEvents().add(oldConsolidateEvent); 177 } 178 } 179 } else { 180 // Normal events 181 if (consolidatedEvent == null) { 182 consolidatedEvent = event; 183 } else if (consolidatedEvent instanceof DataChangedEvent) { 184 List<AbstractDatasetChangedEvent> evs = ((DataChangedEvent) consolidatedEvent).getEvents(); 185 if (evs != null) { 186 evs.add(event); 187 } 188 } else { 189 consolidatedEvent = new DataChangedEvent(dataSet, 190 new ArrayList<AbstractDatasetChangedEvent>(Arrays.asList(consolidatedEvent))); 191 } 192 193 } 194 } 195 196 // Fire consolidated event 197 fireConsolidatedEvents(inEDTListeners, consolidatedEvent); 198 } 199 } 200 }; 201}