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

Last change on this file was 19106, checked in by taylor.smock, 16 months ago

Cleanup some new PMD warnings from PMD 7.x (followup of r19101)

File size: 18.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;
12import java.util.function.Consumer;
13
14import org.openstreetmap.josm.gui.MainApplication;
15import org.openstreetmap.josm.gui.util.GuiHelper;
16import org.openstreetmap.josm.tools.JosmRuntimeException;
17import org.openstreetmap.josm.tools.Utils;
18import org.openstreetmap.josm.tools.bugreport.BugReport;
19
20/**
21 * This class handles the layer management.
22 * <p>
23 * This manager handles a list of layers with the first layer being the front layer.
24 * <h1>Threading</h1>
25 * Synchronization of the layer manager is done by synchronizing all read/write access. All changes are internally done in the EDT thread.
26 * <p>
27 * Methods of this manager may be called from any thread in any order.
28 * Listeners are called while this layer manager is locked, so they should not block on other threads.
29 *
30 * @author Michael Zangl
31 * @since 10273
32 */
33public class LayerManager {
34 /**
35 * Interface to notify listeners of a layer change.
36 */
37 public interface LayerChangeListener {
38 /**
39 * Notifies this listener that a layer has been added.
40 * <p>
41 * Listeners are called in the EDT thread. You should not do blocking or long-running tasks in this method.
42 * @param e The new added layer event
43 */
44 void layerAdded(LayerAddEvent e);
45
46 /**
47 * Notifies this listener that a layer was just removed.
48 * <p>
49 * Listeners are called in the EDT thread after the layer was removed.
50 * Use {@link LayerRemoveEvent#scheduleRemoval(Collection)} to remove more layers.
51 * You should not do blocking or long-running tasks in this method.
52 * @param e The layer to be removed (as event)
53 */
54 void layerRemoving(LayerRemoveEvent e);
55
56 /**
57 * Notifies this listener that the order of layers was changed.
58 * <p>
59 * Listeners are called in the EDT thread.
60 * You should not do blocking or long-running tasks in this method.
61 * @param e The order change event.
62 */
63 void layerOrderChanged(LayerOrderChangeEvent e);
64 }
65
66 /**
67 * Base class of layer manager events.
68 */
69 protected static class LayerManagerEvent {
70 private final LayerManager source;
71
72 LayerManagerEvent(LayerManager source) {
73 this.source = source;
74 }
75
76 /**
77 * Returns the {@code LayerManager} at the origin of this event.
78 * @return the {@code LayerManager} at the origin of this event
79 */
80 public LayerManager getSource() {
81 return source;
82 }
83 }
84
85 /**
86 * The event that is fired whenever a layer was added.
87 * @author Michael Zangl
88 */
89 public static class LayerAddEvent extends LayerManagerEvent {
90 private final Layer addedLayer;
91 private final boolean requiresZoom;
92
93 LayerAddEvent(LayerManager source, Layer addedLayer, boolean requiresZoom) {
94 super(source);
95 this.addedLayer = addedLayer;
96 this.requiresZoom = requiresZoom;
97 }
98
99 /**
100 * Gets the layer that was added.
101 * @return The added layer.
102 */
103 public Layer getAddedLayer() {
104 return addedLayer;
105 }
106
107 /**
108 * Determines if an initial zoom is required.
109 * @return {@code true} if a zoom is required when this layer is added
110 * @since 11774
111 */
112 public final boolean isZoomRequired() {
113 return requiresZoom;
114 }
115
116 @Override
117 public String toString() {
118 return "LayerAddEvent [addedLayer=" + addedLayer + ']';
119 }
120 }
121
122 /**
123 * The event that is fired before removing a layer.
124 * @author Michael Zangl
125 */
126 public static class LayerRemoveEvent extends LayerManagerEvent {
127 private final Layer removedLayer;
128 private final boolean lastLayer;
129 private final Collection<Layer> scheduleForRemoval = new ArrayList<>();
130
131 LayerRemoveEvent(LayerManager source, Layer removedLayer) {
132 super(source);
133 this.removedLayer = removedLayer;
134 this.lastLayer = source.getLayers().isEmpty();
135 }
136
137 /**
138 * Gets the layer that is about to be removed.
139 * @return The layer.
140 */
141 public Layer getRemovedLayer() {
142 return removedLayer;
143 }
144
145 /**
146 * Check if the layer that was removed is the last layer in the list.
147 * @return <code>true</code> if this was the last layer.
148 * @since 10432
149 */
150 public boolean isLastLayer() {
151 return lastLayer;
152 }
153
154 /**
155 * Schedule the removal of other layers after this layer has been deleted.
156 * <p>
157 * Duplicate removal requests are ignored.
158 * @param layers The layers to remove.
159 * @since 10507
160 */
161 public void scheduleRemoval(Collection<? extends Layer> layers) {
162 for (Layer layer : layers) {
163 getSource().checkContainsLayer(layer);
164 }
165 scheduleForRemoval.addAll(layers);
166 }
167
168 @Override
169 public String toString() {
170 return "LayerRemoveEvent [removedLayer=" + removedLayer + ", lastLayer=" + lastLayer + ']';
171 }
172 }
173
174 /**
175 * An event that is fired whenever the order of layers changed.
176 * <p>
177 * We currently do not report the exact changes.
178 * @author Michael Zangl
179 */
180 public static class LayerOrderChangeEvent extends LayerManagerEvent {
181 LayerOrderChangeEvent(LayerManager source) {
182 super(source);
183 }
184
185 @Override
186 public String toString() {
187 return "LayerOrderChangeEvent []";
188 }
189 }
190
191 private static final String LISTENER = "listener";
192 private static final String EVENT = "event";
193
194 /**
195 * This is the list of layers we manage. The list is unmodifiable. That way, read access does
196 * not need to be synchronized.
197 * <p>
198 * It is only changed in the EDT.
199 * @see LayerManager#updateLayers(Consumer)
200 */
201 private volatile List<Layer> layers = Collections.emptyList();
202
203 private final List<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
204
205 /**
206 * Add a layer. The layer will be added at a given position and the mapview zoomed at its projection bounds.
207 * @param layer The layer to add
208 */
209 public void addLayer(final Layer layer) {
210 addLayer(layer, true);
211 }
212
213 /**
214 * Add a layer. The layer will be added at a given position.
215 * @param layer The layer to add
216 * @param initialZoom whether if the mapview must be zoomed at layer projection bounds
217 */
218 public void addLayer(final Layer layer, final boolean initialZoom) {
219 // we force this on to the EDT Thread to make events fire from there.
220 // The synchronization lock needs to be held by the EDT.
221 GuiHelper.runInEDTAndWaitWithException(() -> realAddLayer(layer, initialZoom));
222 }
223
224 /**
225 * Add a layer (implementation).
226 * @param layer The layer to add
227 * @param initialZoom whether if the mapview must be zoomed at layer projection bounds
228 */
229 protected synchronized void realAddLayer(Layer layer, boolean initialZoom) {
230 if (containsLayer(layer)) {
231 throw new IllegalArgumentException("Cannot add a layer twice: " + layer);
232 }
233 LayerPositionStrategy positionStrategy = layer.getDefaultLayerPosition();
234 int position = positionStrategy.getPosition(this);
235 checkPosition(position);
236 insertLayerAt(layer, position);
237 fireLayerAdded(layer, initialZoom);
238 if (MainApplication.getMap() != null) {
239 layer.hookUpMapView(); // needs to be after fireLayerAdded
240 }
241 }
242
243 /**
244 * Remove the layer from the mapview. If the layer was in the list before,
245 * an LayerChange event is fired.
246 * @param layer The layer to remove
247 */
248 public void removeLayer(final Layer layer) {
249 // we force this on to the EDT Thread to make events fire from there.
250 // The synchronization lock needs to be held by the EDT.
251 GuiHelper.runInEDTAndWaitWithException(() -> realRemoveLayer(layer));
252 }
253
254 /**
255 * Remove the layer from the mapview (implementation).
256 * @param layer The layer to remove
257 */
258 protected synchronized void realRemoveLayer(Layer layer) {
259 GuiHelper.assertCallFromEdt();
260 Set<Layer> toRemove = Collections.newSetFromMap(new IdentityHashMap<>());
261 toRemove.add(layer);
262
263 while (!toRemove.isEmpty()) {
264 Iterator<Layer> iterator = toRemove.iterator();
265 Layer layerToRemove = iterator.next();
266 iterator.remove();
267 checkContainsLayer(layerToRemove);
268
269 Collection<Layer> newToRemove = realRemoveSingleLayer(layerToRemove);
270 toRemove.addAll(newToRemove);
271 }
272 }
273
274 /**
275 * Remove a single layer from the mapview (implementation).
276 * @param layerToRemove The layer to remove
277 * @return A list of layers that should be removed afterwards.
278 */
279 protected Collection<Layer> realRemoveSingleLayer(Layer layerToRemove) {
280 updateLayers(mutableLayers -> mutableLayers.remove(layerToRemove));
281 return fireLayerRemoving(layerToRemove);
282 }
283
284 /**
285 * Move a layer to a new position.
286 * @param layer The layer to move.
287 * @param position The position.
288 * @throws IndexOutOfBoundsException if the position is out of bounds.
289 */
290 public void moveLayer(final Layer layer, final int position) {
291 // we force this on to the EDT Thread to make events fire from there.
292 // The synchronization lock needs to be held by the EDT.
293 GuiHelper.runInEDTAndWaitWithException(() -> realMoveLayer(layer, position));
294 }
295
296 /**
297 * Move a layer to a new position (implementation).
298 * @param layer The layer to move.
299 * @param position The position.
300 * @throws IndexOutOfBoundsException if the position is out of bounds.
301 */
302 protected synchronized void realMoveLayer(Layer layer, int position) {
303 checkContainsLayer(layer);
304 checkPosition(position);
305
306 int curLayerPos = getLayers().indexOf(layer);
307 if (position == curLayerPos)
308 return; // already in place.
309 // update needs to be done in one run
310 updateLayers(mutableLayers -> {
311 mutableLayers.remove(curLayerPos);
312 insertLayerAt(mutableLayers, layer, position);
313 });
314 fireLayerOrderChanged();
315 }
316
317 /**
318 * Insert a layer at a given position.
319 * @param layer The layer to add.
320 * @param position The position on which we should add it.
321 */
322 private void insertLayerAt(Layer layer, int position) {
323 updateLayers(mutableLayers -> insertLayerAt(mutableLayers, layer, position));
324 }
325
326 private static void insertLayerAt(List<Layer> layers, Layer layer, int position) {
327 if (position == layers.size()) {
328 layers.add(layer);
329 } else {
330 layers.add(position, layer);
331 }
332 }
333
334 /**
335 * Check if the (new) position is valid
336 * @param position The position index
337 * @throws IndexOutOfBoundsException if it is not.
338 */
339 private void checkPosition(int position) {
340 if (position < 0 || position > getLayers().size()) {
341 throw new IndexOutOfBoundsException("Position " + position + " out of range.");
342 }
343 }
344
345 /**
346 * Update the {@link #layers} field. This method should be used instead of a direct field access.
347 * @param mutator A method that gets the writable list of layers and should modify it.
348 */
349 private void updateLayers(Consumer<List<Layer>> mutator) {
350 GuiHelper.assertCallFromEdt();
351 ArrayList<Layer> newLayers = new ArrayList<>(getLayers());
352 mutator.accept(newLayers);
353 layers = Collections.unmodifiableList(newLayers);
354 }
355
356 /**
357 * Gets an unmodifiable list of all layers that are currently in this manager. This list won't update once layers are added or removed.
358 * @return The list of layers.
359 */
360 public List<Layer> getLayers() {
361 return layers;
362 }
363
364 /**
365 * Replies an unmodifiable list of layers of a certain type.
366 * <p>
367 * Example:
368 * <pre>
369 * List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
370 * </pre>
371 * @param <T> The layer type
372 * @param ofType The layer type.
373 * @return an unmodifiable list of layers of a certain type.
374 */
375 public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
376 return new ArrayList<>(Utils.filteredCollection(getLayers(), ofType));
377 }
378
379 /**
380 * replies true if the list of layers managed by this map view contain layer
381 *
382 * @param layer the layer
383 * @return true if the list of layers managed by this map view contain layer
384 */
385 public boolean containsLayer(Layer layer) {
386 return getLayers().contains(layer);
387 }
388
389 /**
390 * Checks if the specified layer is handled by this layer manager.
391 * @param layer layer to check
392 * @throws IllegalArgumentException if layer is not handled by this layer manager
393 */
394 protected void checkContainsLayer(Layer layer) {
395 if (!containsLayer(layer)) {
396 throw new IllegalArgumentException(layer + " is not managed by us.");
397 }
398 }
399
400 /**
401 * Adds a layer change listener
402 *
403 * @param listener the listener.
404 * @throws IllegalArgumentException If the listener was added twice.
405 * @see #addAndFireLayerChangeListener
406 */
407 public synchronized void addLayerChangeListener(LayerChangeListener listener) {
408 if (layerChangeListeners.contains(listener)) {
409 throw new IllegalArgumentException("Listener already registered.");
410 }
411 layerChangeListeners.add(listener);
412 }
413
414 /**
415 * Adds a layer change listener and fire an add event for every layer in this manager.
416 *
417 * @param listener the listener.
418 * @throws IllegalArgumentException If the listener was added twice.
419 * @see #addLayerChangeListener
420 * @since 11905
421 */
422 public synchronized void addAndFireLayerChangeListener(LayerChangeListener listener) {
423 addLayerChangeListener(listener);
424 for (Layer l : getLayers()) {
425 listener.layerAdded(new LayerAddEvent(this, l, true));
426 }
427 }
428
429 /**
430 * Removes a layer change listener
431 *
432 * @param listener the listener. Ignored if null or already registered.
433 * @see #removeAndFireLayerChangeListener
434 */
435 public synchronized void removeLayerChangeListener(LayerChangeListener listener) {
436 if (!layerChangeListeners.remove(listener)) {
437 throw new IllegalArgumentException("Listener was not registered before: " + listener);
438 }
439 }
440
441 /**
442 * Removes a layer change listener and fire a remove event for every layer in this manager.
443 * The event is fired as if the layer was deleted but
444 * {@link LayerRemoveEvent#scheduleRemoval(Collection)} is ignored.
445 *
446 * @param listener the listener.
447 * @see #removeLayerChangeListener
448 * @since 11905
449 */
450 public synchronized void removeAndFireLayerChangeListener(LayerChangeListener listener) {
451 removeLayerChangeListener(listener);
452 for (Layer l : getLayers()) {
453 listener.layerRemoving(new LayerRemoveEvent(this, l));
454 }
455 }
456
457 private void fireLayerAdded(Layer layer, boolean initialZoom) {
458 GuiHelper.assertCallFromEdt();
459 LayerAddEvent e = new LayerAddEvent(this, layer, initialZoom);
460 for (LayerChangeListener l : layerChangeListeners) {
461 try {
462 l.layerAdded(e);
463 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException t) {
464 throw BugReport.intercept(t).put(LISTENER, l).put(EVENT, e);
465 }
466 }
467 }
468
469 /**
470 * Fire the layer remove event
471 * @param layer The layer that was removed
472 * @return A list of layers that should be removed afterwards.
473 */
474 private Collection<Layer> fireLayerRemoving(Layer layer) {
475 GuiHelper.assertCallFromEdt();
476 LayerRemoveEvent e = new LayerRemoveEvent(this, layer);
477 for (LayerChangeListener l : layerChangeListeners) {
478 try {
479 l.layerRemoving(e);
480 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException t) {
481 throw BugReport.intercept(t).put(LISTENER, l).put(EVENT, e).put("layer", layer);
482 }
483 }
484 if (layer instanceof OsmDataLayer) {
485 ((OsmDataLayer) layer).clear();
486 }
487 return e.scheduleForRemoval;
488 }
489
490 private void fireLayerOrderChanged() {
491 GuiHelper.assertCallFromEdt();
492 LayerOrderChangeEvent e = new LayerOrderChangeEvent(this);
493 for (LayerChangeListener l : layerChangeListeners) {
494 try {
495 l.layerOrderChanged(e);
496 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException t) {
497 throw BugReport.intercept(t).put(LISTENER, l).put(EVENT, e);
498 }
499 }
500 }
501
502 /**
503 * Reset all layer manager state. This includes removing all layers and then unregistering all listeners
504 * @since 10432
505 */
506 public void resetState() {
507 // we force this on to the EDT Thread to have a clean synchronization
508 // The synchronization lock needs to be held by the EDT.
509 GuiHelper.runInEDTAndWaitWithException(this::realResetState);
510 }
511
512 /**
513 * Reset all layer manager state (implementation).
514 */
515 protected synchronized void realResetState() {
516 // The listeners trigger the removal of other layers
517 while (!getLayers().isEmpty()) {
518 removeLayer(getLayers().get(0));
519 }
520
521 layerChangeListeners.clear();
522 }
523}
Note: See TracBrowser for help on using the repository browser.