source: josm/trunk/src/org/openstreetmap/josm/gui/layer/LayerManager.java@ 10998

Last change on this file since 10998 was 10998, checked in by michael2402, 8 years ago

Make layer remove event fire after the layer has been removed. This fixes #13485. Fixes #13516.

File size: 14.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.IdentityHashMap;
8import java.util.Iterator;
9import java.util.List;
10import java.util.Set;
11import java.util.concurrent.CopyOnWriteArrayList;
12
13import org.openstreetmap.josm.Main;
14import org.openstreetmap.josm.gui.util.GuiHelper;
15import org.openstreetmap.josm.tools.Utils;
16import org.openstreetmap.josm.tools.bugreport.BugReport;
17
18/**
19 * This class handles the layer management.
20 * <p>
21 * This manager handles a list of layers with the first layer being the front layer.
22 * <h1>Threading</h1>
23 * Methods of this manager may be called from any thread in any order.
24 * Listeners are called while this layer manager is locked, so they should not block.
25 *
26 * @author Michael Zangl
27 * @since 10273
28 */
29public class LayerManager {
30 /**
31 * Interface to notify listeners of a layer change.
32 */
33 public interface LayerChangeListener {
34 /**
35 * Notifies this listener that a layer has been added.
36 * <p>
37 * Listeners are called in the EDT thread.
38 * @param e The new added layer event
39 */
40 void layerAdded(LayerAddEvent e);
41
42 /**
43 * Notifies this listener that a alayer was just removed.
44 * <p>
45 * Listeners are called in the EDT thread after the layer was removed.
46 * Use {@link LayerRemoveEvent#scheduleRemoval(Collection)} to remove more layers.
47 * @param e The layer to be removed (as event)
48 */
49 void layerRemoving(LayerRemoveEvent e);
50
51 /**
52 * Notifies this listener that the order of layers was changed.
53 * <p>
54 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
55 * @param e The order change event.
56 */
57 void layerOrderChanged(LayerOrderChangeEvent e);
58 }
59
60 protected static class LayerManagerEvent {
61 private final LayerManager source;
62
63 LayerManagerEvent(LayerManager source) {
64 this.source = source;
65 }
66
67 public LayerManager getSource() {
68 return source;
69 }
70 }
71
72 /**
73 * The event that is fired whenever a layer was added.
74 * @author Michael Zangl
75 */
76 public static class LayerAddEvent extends LayerManagerEvent {
77 private final Layer addedLayer;
78
79 LayerAddEvent(LayerManager source, Layer addedLayer) {
80 super(source);
81 this.addedLayer = addedLayer;
82 }
83
84 /**
85 * Gets the layer that was added.
86 * @return The added layer.
87 */
88 public Layer getAddedLayer() {
89 return addedLayer;
90 }
91
92 @Override
93 public String toString() {
94 return "LayerAddEvent [addedLayer=" + addedLayer + ']';
95 }
96 }
97
98 /**
99 * The event that is fired before removing a layer.
100 * @author Michael Zangl
101 */
102 public static class LayerRemoveEvent extends LayerManagerEvent {
103 private final Layer removedLayer;
104 private final boolean lastLayer;
105 private final Collection<Layer> scheduleForRemoval = new ArrayList<>();
106
107 LayerRemoveEvent(LayerManager source, Layer removedLayer) {
108 super(source);
109 this.removedLayer = removedLayer;
110 this.lastLayer = source.getLayers().size() == 1;
111 }
112
113 /**
114 * Gets the layer that is about to be removed.
115 * @return The layer.
116 */
117 public Layer getRemovedLayer() {
118 return removedLayer;
119 }
120
121 /**
122 * Check if the layer that was removed is the last layer in the list.
123 * @return <code>true</code> if this was the last layer.
124 * @since 10432
125 */
126 public boolean isLastLayer() {
127 return lastLayer;
128 }
129
130 /**
131 * Schedule the removal of other layers after this layer has been deleted.
132 * <p>
133 * Dupplicate removal requests are ignored.
134 * @param layers The layers to remove.
135 * @since 10507
136 */
137 public void scheduleRemoval(Collection<? extends Layer> layers) {
138 for (Layer layer : layers) {
139 getSource().checkContainsLayer(layer);
140 }
141 scheduleForRemoval.addAll(layers);
142 }
143
144 @Override
145 public String toString() {
146 return "LayerRemoveEvent [removedLayer=" + removedLayer + ", lastLayer=" + lastLayer + ']';
147 }
148 }
149
150 /**
151 * An event that is fired whenever the order of layers changed.
152 * <p>
153 * We currently do not report the exact changes.
154 * @author Michael Zangl
155 */
156 public static class LayerOrderChangeEvent extends LayerManagerEvent {
157 LayerOrderChangeEvent(LayerManager source) {
158 super(source);
159 }
160
161 @Override
162 public String toString() {
163 return "LayerOrderChangeEvent []";
164 }
165 }
166
167 /**
168 * This is the list of layers we manage.
169 */
170 private final List<Layer> layers = new ArrayList<>();
171
172 private final List<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
173
174 /**
175 * Add a layer. The layer will be added at a given position.
176 * @param layer The layer to add
177 */
178 public void addLayer(final Layer layer) {
179 // we force this on to the EDT Thread to make events fire from there.
180 // The synchronization lock needs to be held by the EDT.
181 GuiHelper.runInEDTAndWaitWithException(() -> realAddLayer(layer));
182 }
183
184 protected synchronized void realAddLayer(Layer layer) {
185 if (containsLayer(layer)) {
186 throw new IllegalArgumentException("Cannot add a layer twice.");
187 }
188 LayerPositionStrategy positionStrategy = layer.getDefaultLayerPosition();
189 int position = positionStrategy.getPosition(this);
190 checkPosition(position);
191 insertLayerAt(layer, position);
192 fireLayerAdded(layer);
193 if (Main.map != null) {
194 layer.hookUpMapView(); // needs to be after fireLayerAdded
195 }
196 }
197
198 /**
199 * Remove the layer from the mapview. If the layer was in the list before,
200 * an LayerChange event is fired.
201 * @param layer The layer to remove
202 */
203 public void removeLayer(final Layer layer) {
204 // we force this on to the EDT Thread to make events fire from there.
205 // The synchronization lock needs to be held by the EDT.
206 GuiHelper.runInEDTAndWaitWithException(() -> realRemoveLayer(layer));
207 }
208
209 protected synchronized void realRemoveLayer(Layer layer) {
210 GuiHelper.assertCallFromEdt();
211 Set<Layer> toRemove = Collections.newSetFromMap(new IdentityHashMap<Layer, Boolean>());
212 toRemove.add(layer);
213
214 while (!toRemove.isEmpty()) {
215 Iterator<Layer> iterator = toRemove.iterator();
216 Layer layerToRemove = iterator.next();
217 iterator.remove();
218 checkContainsLayer(layerToRemove);
219
220 Collection<Layer> newToRemove = realRemoveSingleLayer(layerToRemove);
221 toRemove.addAll(newToRemove);
222 }
223 }
224
225 protected Collection<Layer> realRemoveSingleLayer(Layer layerToRemove) {
226 layers.remove(layerToRemove);
227 return fireLayerRemoving(layerToRemove);
228 }
229
230 /**
231 * Move a layer to a new position.
232 * @param layer The layer to move.
233 * @param position The position.
234 * @throws IndexOutOfBoundsException if the position is out of bounds.
235 */
236 public void moveLayer(final Layer layer, final int position) {
237 // we force this on to the EDT Thread to make events fire from there.
238 // The synchronization lock needs to be held by the EDT.
239 GuiHelper.runInEDTAndWaitWithException(() -> realMoveLayer(layer, position));
240 }
241
242 protected synchronized void realMoveLayer(Layer layer, int position) {
243 checkContainsLayer(layer);
244 checkPosition(position);
245
246 int curLayerPos = layers.indexOf(layer);
247 if (position == curLayerPos)
248 return; // already in place.
249 layers.remove(curLayerPos);
250 insertLayerAt(layer, position);
251 fireLayerOrderChanged();
252 }
253
254 /**
255 * Insert a layer at a given position.
256 * @param layer The layer to add.
257 * @param position The position on which we should add it.
258 */
259 private void insertLayerAt(Layer layer, int position) {
260 if (position == layers.size()) {
261 layers.add(layer);
262 } else {
263 layers.add(position, layer);
264 }
265 }
266
267 /**
268 * Check if the (new) position is valid
269 * @param position The position index
270 * @throws IndexOutOfBoundsException if it is not.
271 */
272 private void checkPosition(int position) {
273 if (position < 0 || position > layers.size()) {
274 throw new IndexOutOfBoundsException("Position " + position + " out of range.");
275 }
276 }
277
278 /**
279 * Gets an unmodifiable list of all layers that are currently in this manager. This list won't update once layers are added or removed.
280 * @return The list of layers.
281 */
282 public synchronized List<Layer> getLayers() {
283 return Collections.unmodifiableList(new ArrayList<>(layers));
284 }
285
286 /**
287 * Replies an unmodifiable list of layers of a certain type.
288 *
289 * Example:
290 * <pre>
291 * List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
292 * </pre>
293 * @param <T> The layer type
294 * @param ofType The layer type.
295 * @return an unmodifiable list of layers of a certain type.
296 */
297 public synchronized <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
298 return new ArrayList<>(Utils.filteredCollection(getLayers(), ofType));
299 }
300
301 /**
302 * replies true if the list of layers managed by this map view contain layer
303 *
304 * @param layer the layer
305 * @return true if the list of layers managed by this map view contain layer
306 */
307 public synchronized boolean containsLayer(Layer layer) {
308 return layers.contains(layer);
309 }
310
311 protected void checkContainsLayer(Layer layer) {
312 if (!containsLayer(layer)) {
313 throw new IllegalArgumentException(layer + " is not managed by us.");
314 }
315 }
316
317 /**
318 * Adds a layer change listener
319 *
320 * @param listener the listener.
321 * @throws IllegalArgumentException If the listener was added twice.
322 */
323 public synchronized void addLayerChangeListener(LayerChangeListener listener) {
324 addLayerChangeListener(listener, false);
325 }
326
327 /**
328 * Adds a layer change listener
329 *
330 * @param listener the listener.
331 * @param fireAdd if we should fire an add event for every layer in this manager.
332 * @throws IllegalArgumentException If the listener was added twice.
333 */
334 public synchronized void addLayerChangeListener(LayerChangeListener listener, boolean fireAdd) {
335 if (layerChangeListeners.contains(listener)) {
336 throw new IllegalArgumentException("Listener already registered.");
337 }
338 layerChangeListeners.add(listener);
339 if (fireAdd) {
340 for (Layer l : getLayers()) {
341 listener.layerAdded(new LayerAddEvent(this, l));
342 }
343 }
344 }
345
346 /**
347 * Removes a layer change listener
348 *
349 * @param listener the listener. Ignored if null or already registered.
350 */
351 public synchronized void removeLayerChangeListener(LayerChangeListener listener) {
352 removeLayerChangeListener(listener, false);
353 }
354
355 /**
356 * Removes a layer change listener
357 *
358 * @param listener the listener.
359 * @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
360 * {@link LayerRemoveEvent#scheduleRemoval(Collection)} is ignored.
361 */
362 public synchronized void removeLayerChangeListener(LayerChangeListener listener, boolean fireRemove) {
363 if (!layerChangeListeners.remove(listener)) {
364 throw new IllegalArgumentException("Listener was not registered before: " + listener);
365 } else {
366 if (fireRemove) {
367 for (Layer l : getLayers()) {
368 listener.layerRemoving(new LayerRemoveEvent(this, l));
369 }
370 }
371 }
372 }
373
374 private void fireLayerAdded(Layer layer) {
375 GuiHelper.assertCallFromEdt();
376 LayerAddEvent e = new LayerAddEvent(this, layer);
377 for (LayerChangeListener l : layerChangeListeners) {
378 try {
379 l.layerAdded(e);
380 } catch (RuntimeException t) {
381 throw BugReport.intercept(t).put("listener", l).put("event", e);
382 }
383 }
384 }
385
386 /**
387 * Fire the layer remove event
388 * @param layer The layer that was removed
389 * @return A list of layers that should be removed afterwards.
390 */
391 private Collection<Layer> fireLayerRemoving(Layer layer) {
392 GuiHelper.assertCallFromEdt();
393 LayerRemoveEvent e = new LayerRemoveEvent(this, layer);
394 for (LayerChangeListener l : layerChangeListeners) {
395 try {
396 l.layerRemoving(e);
397 } catch (RuntimeException t) {
398 throw BugReport.intercept(t).put("listener", l).put("event", e).put("layer", layer);
399 }
400 }
401 return e.scheduleForRemoval;
402 }
403
404 private void fireLayerOrderChanged() {
405 GuiHelper.assertCallFromEdt();
406 LayerOrderChangeEvent e = new LayerOrderChangeEvent(this);
407 for (LayerChangeListener l : layerChangeListeners) {
408 try {
409 l.layerOrderChanged(e);
410 } catch (RuntimeException t) {
411 throw BugReport.intercept(t).put("listener", l).put("event", e);
412 }
413 }
414 }
415
416 /**
417 * Reset all layer manager state. This includes removing all layers and then unregistering all listeners
418 * @since 10432
419 */
420 public synchronized void resetState() {
421 // some layer remove listeners remove other layers.
422 while (!getLayers().isEmpty()) {
423 removeLayer(getLayers().get(0));
424 }
425
426 layerChangeListeners.clear();
427 }
428}
Note: See TracBrowser for help on using the repository browser.