001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.IdentityHashMap;
008import java.util.Iterator;
009import java.util.List;
010import java.util.Set;
011import java.util.concurrent.CopyOnWriteArrayList;
012import java.util.function.Consumer;
013
014import org.openstreetmap.josm.Main;
015import org.openstreetmap.josm.gui.util.GuiHelper;
016import org.openstreetmap.josm.tools.Utils;
017import org.openstreetmap.josm.tools.bugreport.BugReport;
018
019/**
020 * This class handles the layer management.
021 * <p>
022 * This manager handles a list of layers with the first layer being the front layer.
023 * <h1>Threading</h1>
024 * Synchronization of the layer manager is done by synchronizing all read/write access. All changes are internally done in the EDT thread.
025 *
026 * Methods of this manager may be called from any thread in any order.
027 * Listeners are called while this layer manager is locked, so they should not block on other threads.
028 *
029 * @author Michael Zangl
030 * @since 10273
031 */
032public class LayerManager {
033    /**
034     * Interface to notify listeners of a layer change.
035     */
036    public interface LayerChangeListener {
037        /**
038         * Notifies this listener that a layer has been added.
039         * <p>
040         * Listeners are called in the EDT thread. You should not do blocking or long-running tasks in this method.
041         * @param e The new added layer event
042         */
043        void layerAdded(LayerAddEvent e);
044
045        /**
046         * Notifies this listener that a alayer was just removed.
047         * <p>
048         * Listeners are called in the EDT thread after the layer was removed.
049         * Use {@link LayerRemoveEvent#scheduleRemoval(Collection)} to remove more layers.
050         * You should not do blocking or long-running tasks in this method.
051         * @param e The layer to be removed (as event)
052         */
053        void layerRemoving(LayerRemoveEvent e);
054
055        /**
056         * Notifies this listener that the order of layers was changed.
057         * <p>
058         * Listeners are called in the EDT thread.
059         *  You should not do blocking or long-running tasks in this method.
060         * @param e The order change event.
061         */
062        void layerOrderChanged(LayerOrderChangeEvent e);
063    }
064
065    protected static class LayerManagerEvent {
066        private final LayerManager source;
067
068        LayerManagerEvent(LayerManager source) {
069            this.source = source;
070        }
071
072        public LayerManager getSource() {
073            return source;
074        }
075    }
076
077    /**
078     * The event that is fired whenever a layer was added.
079     * @author Michael Zangl
080     */
081    public static class LayerAddEvent extends LayerManagerEvent {
082        private final Layer addedLayer;
083
084        LayerAddEvent(LayerManager source, Layer addedLayer) {
085            super(source);
086            this.addedLayer = addedLayer;
087        }
088
089        /**
090         * Gets the layer that was added.
091         * @return The added layer.
092         */
093        public Layer getAddedLayer() {
094            return addedLayer;
095        }
096
097        @Override
098        public String toString() {
099            return "LayerAddEvent [addedLayer=" + addedLayer + ']';
100        }
101    }
102
103    /**
104     * The event that is fired before removing a layer.
105     * @author Michael Zangl
106     */
107    public static class LayerRemoveEvent extends LayerManagerEvent {
108        private final Layer removedLayer;
109        private final boolean lastLayer;
110        private final Collection<Layer> scheduleForRemoval = new ArrayList<>();
111
112        LayerRemoveEvent(LayerManager source, Layer removedLayer) {
113            super(source);
114            this.removedLayer = removedLayer;
115            this.lastLayer = source.getLayers().size() == 1;
116        }
117
118        /**
119         * Gets the layer that is about to be removed.
120         * @return The layer.
121         */
122        public Layer getRemovedLayer() {
123            return removedLayer;
124        }
125
126        /**
127         * Check if the layer that was removed is the last layer in the list.
128         * @return <code>true</code> if this was the last layer.
129         * @since 10432
130         */
131        public boolean isLastLayer() {
132            return lastLayer;
133        }
134
135        /**
136         * Schedule the removal of other layers after this layer has been deleted.
137         * <p>
138         * Dupplicate removal requests are ignored.
139         * @param layers The layers to remove.
140         * @since 10507
141         */
142        public void scheduleRemoval(Collection<? extends Layer> layers) {
143            for (Layer layer : layers) {
144                getSource().checkContainsLayer(layer);
145            }
146            scheduleForRemoval.addAll(layers);
147        }
148
149        @Override
150        public String toString() {
151            return "LayerRemoveEvent [removedLayer=" + removedLayer + ", lastLayer=" + lastLayer + ']';
152        }
153    }
154
155    /**
156     * An event that is fired whenever the order of layers changed.
157     * <p>
158     * We currently do not report the exact changes.
159     * @author Michael Zangl
160     */
161    public static class LayerOrderChangeEvent extends LayerManagerEvent {
162        LayerOrderChangeEvent(LayerManager source) {
163            super(source);
164        }
165
166        @Override
167        public String toString() {
168            return "LayerOrderChangeEvent []";
169        }
170    }
171
172    /**
173     * This is the list of layers we manage. The list is unmodifyable. That way, read access does not need to be synchronized.
174     *
175     * It is only changed in the EDT.
176     * @see LayerManager#updateLayers(Consumer)
177     */
178    private volatile List<Layer> layers = Collections.emptyList();
179
180    private final List<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
181
182    /**
183     * Add a layer. The layer will be added at a given position.
184     * @param layer The layer to add
185     */
186    public void addLayer(final Layer layer) {
187        // we force this on to the EDT Thread to make events fire from there.
188        // The synchronization lock needs to be held by the EDT.
189        GuiHelper.runInEDTAndWaitWithException(() -> realAddLayer(layer));
190    }
191
192    protected synchronized void realAddLayer(Layer layer) {
193        if (containsLayer(layer)) {
194            throw new IllegalArgumentException("Cannot add a layer twice: " + layer);
195        }
196        LayerPositionStrategy positionStrategy = layer.getDefaultLayerPosition();
197        int position = positionStrategy.getPosition(this);
198        checkPosition(position);
199        insertLayerAt(layer, position);
200        fireLayerAdded(layer);
201        if (Main.map != null) {
202            layer.hookUpMapView(); // needs to be after fireLayerAdded
203        }
204    }
205
206    /**
207     * Remove the layer from the mapview. If the layer was in the list before,
208     * an LayerChange event is fired.
209     * @param layer The layer to remove
210     */
211    public void removeLayer(final Layer layer) {
212        // we force this on to the EDT Thread to make events fire from there.
213        // The synchronization lock needs to be held by the EDT.
214        GuiHelper.runInEDTAndWaitWithException(() -> realRemoveLayer(layer));
215    }
216
217    protected synchronized void realRemoveLayer(Layer layer) {
218        GuiHelper.assertCallFromEdt();
219        Set<Layer> toRemove = Collections.newSetFromMap(new IdentityHashMap<Layer, Boolean>());
220        toRemove.add(layer);
221
222        while (!toRemove.isEmpty()) {
223            Iterator<Layer> iterator = toRemove.iterator();
224            Layer layerToRemove = iterator.next();
225            iterator.remove();
226            checkContainsLayer(layerToRemove);
227
228            Collection<Layer> newToRemove = realRemoveSingleLayer(layerToRemove);
229            toRemove.addAll(newToRemove);
230        }
231    }
232
233    protected Collection<Layer> realRemoveSingleLayer(Layer layerToRemove) {
234        updateLayers(mutableLayers -> mutableLayers.remove(layerToRemove));
235        return fireLayerRemoving(layerToRemove);
236    }
237
238    /**
239     * Move a layer to a new position.
240     * @param layer The layer to move.
241     * @param position The position.
242     * @throws IndexOutOfBoundsException if the position is out of bounds.
243     */
244    public void moveLayer(final Layer layer, final int position) {
245        // we force this on to the EDT Thread to make events fire from there.
246        // The synchronization lock needs to be held by the EDT.
247        GuiHelper.runInEDTAndWaitWithException(() -> realMoveLayer(layer, position));
248    }
249
250    protected synchronized void realMoveLayer(Layer layer, int position) {
251        checkContainsLayer(layer);
252        checkPosition(position);
253
254        int curLayerPos = getLayers().indexOf(layer);
255        if (position == curLayerPos)
256            return; // already in place.
257        // update needs to be done in one run
258        updateLayers(mutableLayers -> {
259            mutableLayers.remove(curLayerPos);
260            insertLayerAt(mutableLayers, layer, position);
261        });
262        fireLayerOrderChanged();
263    }
264
265    /**
266     * Insert a layer at a given position.
267     * @param layer The layer to add.
268     * @param position The position on which we should add it.
269     */
270    private void insertLayerAt(Layer layer, int position) {
271        updateLayers(mutableLayers -> insertLayerAt(mutableLayers, layer, position));
272    }
273
274    private static void insertLayerAt(List<Layer> layers, Layer layer, int position) {
275        if (position == layers.size()) {
276            layers.add(layer);
277        } else {
278            layers.add(position, layer);
279        }
280    }
281
282    /**
283     * Check if the (new) position is valid
284     * @param position The position index
285     * @throws IndexOutOfBoundsException if it is not.
286     */
287    private void checkPosition(int position) {
288        if (position < 0 || position > getLayers().size()) {
289            throw new IndexOutOfBoundsException("Position " + position + " out of range.");
290        }
291    }
292
293    /**
294     * Update the {@link #layers} field. This method should be used instead of a direct field access.
295     * @param mutator A method that gets the writable list of layers and should modify it.
296     */
297    private void updateLayers(Consumer<List<Layer>> mutator) {
298        GuiHelper.assertCallFromEdt();
299        ArrayList<Layer> newLayers = new ArrayList<>(getLayers());
300        mutator.accept(newLayers);
301        layers = Collections.unmodifiableList(newLayers);
302    }
303
304    /**
305     * Gets an unmodifiable list of all layers that are currently in this manager. This list won't update once layers are added or removed.
306     * @return The list of layers.
307     */
308    public List<Layer> getLayers() {
309        return layers;
310    }
311
312    /**
313     * Replies an unmodifiable list of layers of a certain type.
314     *
315     * Example:
316     * <pre>
317     *     List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
318     * </pre>
319     * @param <T> The layer type
320     * @param ofType The layer type.
321     * @return an unmodifiable list of layers of a certain type.
322     */
323    public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
324        return new ArrayList<>(Utils.filteredCollection(getLayers(), ofType));
325    }
326
327    /**
328     * replies true if the list of layers managed by this map view contain layer
329     *
330     * @param layer the layer
331     * @return true if the list of layers managed by this map view contain layer
332     */
333    public boolean containsLayer(Layer layer) {
334        return getLayers().contains(layer);
335    }
336
337    protected void checkContainsLayer(Layer layer) {
338        if (!containsLayer(layer)) {
339            throw new IllegalArgumentException(layer + " is not managed by us.");
340        }
341    }
342
343    /**
344     * Adds a layer change listener
345     *
346     * @param listener the listener.
347     * @throws IllegalArgumentException If the listener was added twice.
348     */
349    public synchronized void addLayerChangeListener(LayerChangeListener listener) {
350        addLayerChangeListener(listener, false);
351    }
352
353    /**
354     * Adds a layer change listener
355     *
356     * @param listener the listener.
357     * @param fireAdd if we should fire an add event for every layer in this manager.
358     * @throws IllegalArgumentException If the listener was added twice.
359     */
360    public synchronized void addLayerChangeListener(LayerChangeListener listener, boolean fireAdd) {
361        if (layerChangeListeners.contains(listener)) {
362            throw new IllegalArgumentException("Listener already registered.");
363        }
364        layerChangeListeners.add(listener);
365        if (fireAdd) {
366            for (Layer l : getLayers()) {
367                listener.layerAdded(new LayerAddEvent(this, l));
368            }
369        }
370    }
371
372    /**
373     * Removes a layer change listener
374     *
375     * @param listener the listener. Ignored if null or already registered.
376     */
377    public synchronized void removeLayerChangeListener(LayerChangeListener listener) {
378        removeLayerChangeListener(listener, false);
379    }
380
381    /**
382     * Removes a layer change listener
383     *
384     * @param listener the listener.
385     * @param fireRemove if we should fire a remove event for every layer in this manager. The event is fired as if the layer was deleted but
386     * {@link LayerRemoveEvent#scheduleRemoval(Collection)} is ignored.
387     */
388    public synchronized void removeLayerChangeListener(LayerChangeListener listener, boolean fireRemove) {
389        if (!layerChangeListeners.remove(listener)) {
390            throw new IllegalArgumentException("Listener was not registered before: " + listener);
391        } else {
392            if (fireRemove) {
393                for (Layer l : getLayers()) {
394                    listener.layerRemoving(new LayerRemoveEvent(this, l));
395                }
396            }
397        }
398    }
399
400    private void fireLayerAdded(Layer layer) {
401        GuiHelper.assertCallFromEdt();
402        LayerAddEvent e = new LayerAddEvent(this, layer);
403        for (LayerChangeListener l : layerChangeListeners) {
404            try {
405                l.layerAdded(e);
406            } catch (RuntimeException t) {
407                throw BugReport.intercept(t).put("listener", l).put("event", e);
408            }
409        }
410    }
411
412    /**
413     * Fire the layer remove event
414     * @param layer The layer that was removed
415     * @return A list of layers that should be removed afterwards.
416     */
417    private Collection<Layer> fireLayerRemoving(Layer layer) {
418        GuiHelper.assertCallFromEdt();
419        LayerRemoveEvent e = new LayerRemoveEvent(this, layer);
420        for (LayerChangeListener l : layerChangeListeners) {
421            try {
422                l.layerRemoving(e);
423            } catch (RuntimeException t) {
424                throw BugReport.intercept(t).put("listener", l).put("event", e).put("layer", layer);
425            }
426        }
427        return e.scheduleForRemoval;
428    }
429
430    private void fireLayerOrderChanged() {
431        GuiHelper.assertCallFromEdt();
432        LayerOrderChangeEvent e = new LayerOrderChangeEvent(this);
433        for (LayerChangeListener l : layerChangeListeners) {
434            try {
435                l.layerOrderChanged(e);
436            } catch (RuntimeException t) {
437                throw BugReport.intercept(t).put("listener", l).put("event", e);
438            }
439        }
440    }
441
442    /**
443     * Reset all layer manager state. This includes removing all layers and then unregistering all listeners
444     * @since 10432
445     */
446    public void resetState() {
447        // we force this on to the EDT Thread to have a clean synchronization
448        // The synchronization lock needs to be held by the EDT.
449        GuiHelper.runInEDTAndWaitWithException(this::realResetState);
450    }
451
452    protected synchronized void realResetState() {
453        // The listeners trigger the removal of other layers
454        while (!getLayers().isEmpty()) {
455            removeLayer(getLayers().get(0));
456        }
457
458        layerChangeListeners.clear();
459    }
460}