001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.List;
007import java.util.concurrent.CopyOnWriteArrayList;
008
009import org.openstreetmap.josm.gui.util.GuiHelper;
010import org.openstreetmap.josm.tools.Utils;
011
012/**
013 * This class handles the layer management.
014 * <p>
015 * This manager handles a list of layers with the first layer being the front layer.
016 * <h1>Threading</h1>
017 * Methods of this manager may be called from any thread in any order.
018 * Listeners are called while this layer manager is locked, so they should not block.
019 *
020 * @author Michael Zangl
021 * @since 10273
022 */
023public class LayerManager {
024    /**
025     * Interface to notify listeners of a layer change.
026     */
027    public interface LayerChangeListener {
028        /**
029         * Notifies this listener that a layer has been added.
030         * <p>
031         * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
032         * @param e The new added layer event
033         */
034        void layerAdded(LayerAddEvent e);
035
036        /**
037         * Notifies this listener that a layer is about to be removed.
038         * <p>
039         * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
040         * @param e The layer to be removed (as event)
041         */
042        void layerRemoving(LayerRemoveEvent e);
043
044        /**
045         * Notifies this listener that the order of layers was changed.
046         * <p>
047         * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
048         * @param e The order change event.
049         */
050        void layerOrderChanged(LayerOrderChangeEvent e);
051    }
052
053    protected static class LayerManagerEvent {
054        private final LayerManager source;
055
056        LayerManagerEvent(LayerManager source) {
057            this.source = source;
058        }
059
060        public LayerManager getSource() {
061            return source;
062        }
063    }
064
065    /**
066     * The event that is fired whenever a layer was added.
067     * @author Michael Zangl
068     */
069    public static class LayerAddEvent extends LayerManagerEvent {
070        private final Layer addedLayer;
071
072        LayerAddEvent(LayerManager source, Layer addedLayer) {
073            super(source);
074            this.addedLayer = addedLayer;
075        }
076
077        /**
078         * Gets the layer that was added.
079         * @return The added layer.
080         */
081        public Layer getAddedLayer() {
082            return addedLayer;
083        }
084    }
085
086    /**
087     * The event that is fired before removing a layer.
088     * @author Michael Zangl
089     */
090    public static class LayerRemoveEvent extends LayerManagerEvent {
091        private final Layer removedLayer;
092
093        LayerRemoveEvent(LayerManager source, Layer removedLayer) {
094            super(source);
095            this.removedLayer = removedLayer;
096        }
097
098        /**
099         * Gets the layer that is about to be removed.
100         * @return The layer.
101         */
102        public Layer getRemovedLayer() {
103            return removedLayer;
104        }
105    }
106
107    /**
108     * An event that is fired whenever the order of layers changed.
109     * <p>
110     * We currently do not report the exact changes.
111     * @author Michael Zangl
112     */
113    public static class LayerOrderChangeEvent extends LayerManagerEvent {
114        LayerOrderChangeEvent(LayerManager source) {
115            super(source);
116        }
117
118    }
119
120    /**
121     * This is the list of layers we manage.
122     */
123    private final List<Layer> layers = new ArrayList<>();
124
125    private final List<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
126
127    /**
128     * Add a layer. The layer will be added at a given psoition.
129     * @param layer The layer to add
130     */
131    public void addLayer(final Layer layer) {
132        // we force this on to the EDT Thread to make events fire from there.
133        // The synchronization lock needs to be held by the EDT.
134        GuiHelper.runInEDTAndWaitWithException(new Runnable() {
135            @Override
136            public void run() {
137                realAddLayer(layer);
138            }
139        });
140    }
141
142    protected synchronized void realAddLayer(Layer layer) {
143        if (containsLayer(layer)) {
144            throw new IllegalArgumentException("Cannot add a layer twice.");
145        }
146        LayerPositionStrategy positionStrategy = layer.getDefaultLayerPosition();
147        int position = positionStrategy.getPosition(this);
148        checkPosition(position);
149        insertLayerAt(layer, position);
150        fireLayerAdded(layer);
151    }
152
153    /**
154     * Remove the layer from the mapview. If the layer was in the list before,
155     * an LayerChange event is fired.
156     * @param layer The layer to remove
157     */
158    public void removeLayer(final Layer layer) {
159        // we force this on to the EDT Thread to make events fire from there.
160        // The synchronization lock needs to be held by the EDT.
161        GuiHelper.runInEDTAndWaitWithException(new Runnable() {
162            @Override
163            public void run() {
164                realRemoveLayer(layer);
165            }
166        });
167    }
168
169    protected synchronized void realRemoveLayer(Layer layer) {
170        checkContainsLayer(layer);
171
172        fireLayerRemoving(layer);
173        layers.remove(layer);
174    }
175
176    /**
177     * Move a layer to a new position.
178     * @param layer The layer to move.
179     * @param position The position.
180     * @throws IndexOutOfBoundsException if the position is out of bounds.
181     */
182    public void moveLayer(final Layer layer, final int position) {
183        // we force this on to the EDT Thread to make events fire from there.
184        // The synchronization lock needs to be held by the EDT.
185        GuiHelper.runInEDTAndWaitWithException(new Runnable() {
186            @Override
187            public void run() {
188                realMoveLayer(layer, position);
189            }
190        });
191    }
192
193    protected synchronized void realMoveLayer(Layer layer, int position) {
194        checkContainsLayer(layer);
195        checkPosition(position);
196
197        int curLayerPos = layers.indexOf(layer);
198        if (position == curLayerPos)
199            return; // already in place.
200        layers.remove(curLayerPos);
201        insertLayerAt(layer, position);
202        fireLayerOrderChanged();
203    }
204
205    /**
206     * Insert a layer at a given position.
207     * @param layer The layer to add.
208     * @param position The position on which we should add it.
209     */
210    private void insertLayerAt(Layer layer, int position) {
211        if (position == layers.size()) {
212            layers.add(layer);
213        } else {
214            layers.add(position, layer);
215        }
216    }
217
218    /**
219     * Check if the (new) position is valid
220     * @param position The position index
221     * @throws IndexOutOfBoundsException if it is not.
222     */
223    private void checkPosition(int position) {
224        if (position < 0 || position > layers.size()) {
225            throw new IndexOutOfBoundsException("Position " + position + " out of range.");
226        }
227    }
228
229    /**
230     * Gets an unmodifiable list of all layers that are currently in this manager. This list won't update once layers are added or removed.
231     * @return The list of layers.
232     */
233    public List<Layer> getLayers() {
234        return Collections.unmodifiableList(new ArrayList<>(layers));
235    }
236
237    /**
238     * Replies an unmodifiable list of layers of a certain type.
239     *
240     * Example:
241     * <pre>
242     *     List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
243     * </pre>
244     * @param <T> The layer type
245     * @param ofType The layer type.
246     * @return an unmodifiable list of layers of a certain type.
247     */
248    public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
249        return new ArrayList<>(Utils.filteredCollection(getLayers(), ofType));
250    }
251
252    /**
253     * replies true if the list of layers managed by this map view contain layer
254     *
255     * @param layer the layer
256     * @return true if the list of layers managed by this map view contain layer
257     */
258    public synchronized boolean containsLayer(Layer layer) {
259        return layers.contains(layer);
260    }
261
262    protected void checkContainsLayer(Layer layer) {
263        if (!containsLayer(layer)) {
264            throw new IllegalArgumentException(layer + " is not managed by us.");
265        }
266    }
267
268    /**
269     * Adds a layer change listener
270     *
271     * @param listener the listener.
272     * @throws IllegalArgumentException If the listener was added twice.
273     */
274    public synchronized void addLayerChangeListener(LayerChangeListener listener) {
275        addLayerChangeListener(listener, false);
276    }
277
278    /**
279     * Adds a layer change listener
280     *
281     * @param listener the listener.
282     * @param fireAdd if we should fire an add event for every layer in this manager.
283     * @throws IllegalArgumentException If the listener was added twice.
284     */
285    public synchronized void addLayerChangeListener(LayerChangeListener listener, boolean fireAdd) {
286        if (layerChangeListeners.contains(listener)) {
287            throw new IllegalArgumentException("Listener already registered.");
288        }
289        layerChangeListeners.add(listener);
290        if (fireAdd) {
291            for (Layer l : getLayers()) {
292                listener.layerAdded(new LayerAddEvent(this, l));
293            }
294        }
295    }
296
297    /**
298     * Removes a layer change listener
299     *
300     * @param listener the listener. Ignored if null or already registered.
301     */
302    public synchronized void removeLayerChangeListener(LayerChangeListener listener) {
303        removeLayerChangeListener(listener, false);
304    }
305
306
307    /**
308     * Removes a layer change listener
309     *
310     * @param listener the listener.
311     * @param fireRemove if we should fire a remove event for every layer in this manager.
312     */
313    public synchronized void removeLayerChangeListener(LayerChangeListener listener, boolean fireRemove) {
314        if (!layerChangeListeners.remove(listener)) {
315            throw new IllegalArgumentException("Listener was not registered before: " + listener);
316        } else {
317            if (fireRemove) {
318                for (Layer l : getLayers()) {
319                    listener.layerRemoving(new LayerRemoveEvent(this, l));
320                }
321            }
322        }
323    }
324
325    private void fireLayerAdded(Layer layer) {
326        GuiHelper.assertCallFromEdt();
327        LayerAddEvent e = new LayerAddEvent(this, layer);
328        for (LayerChangeListener l : layerChangeListeners) {
329            l.layerAdded(e);
330        }
331    }
332
333    private void fireLayerRemoving(Layer layer) {
334        GuiHelper.assertCallFromEdt();
335        LayerRemoveEvent e = new LayerRemoveEvent(this, layer);
336        for (LayerChangeListener l : layerChangeListeners) {
337            l.layerRemoving(e);
338        }
339    }
340
341    private void fireLayerOrderChanged() {
342        GuiHelper.assertCallFromEdt();
343        LayerOrderChangeEvent e = new LayerOrderChangeEvent(this);
344        for (LayerChangeListener l : layerChangeListeners) {
345            l.layerOrderChanged(e);
346        }
347    }
348}