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 }