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    }