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}