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

Last change on this file since 11297 was 11297, checked in by simon04, 7 years ago

see #13960 - Enhance exception message

File size: 15.6 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.Main;
15import org.openstreetmap.josm.gui.util.GuiHelper;
16import org.openstreetmap.josm.tools.Utils;
17import org.openstreetmap.josm.tools.bugreport.BugReport;
18
19/**
20 * This class handles the layer management.
21 * <p>
22 * This manager handles a list of layers with the first layer being the front layer.
23 * <h1>Threading</h1>
24 * Synchronization of the layer manager is done by synchronizing all read/write access. All changes are internally done in the EDT thread.
25 *
26 * Methods of this manager may be called from any thread in any order.
27 * Listeners are called while this layer manager is locked, so they should not block on other threads.
28 *
29 * @author Michael Zangl
30 * @since 10273
31 */
32public class LayerManager {
33 /**
34 * Interface to notify listeners of a layer change.
35 */
36 public interface LayerChangeListener {
37 /**
38 * Notifies this listener that a layer has been added.
39 * <p>
40 * Listeners are called in the EDT thread. You should not do blocking or long-running tasks in this method.
41 * @param e The new added layer event
42 */
43 void layerAdded(LayerAddEvent e);
44
45 /**
46 * Notifies this listener that a alayer was just removed.
47 * <p>
48 * Listeners are called in the EDT thread after the layer was removed.
49 * Use {@link LayerRemoveEvent#scheduleRemoval(Collection)} to remove more layers.
50 * You should not do blocking or long-running tasks in this method.
51 * @param e The layer to be removed (as event)
52 */
53 void layerRemoving(LayerRemoveEvent e);
54
55 /**
56 * Notifies this listener that the order of layers was changed.
57 * <p>
58 * Listeners are called in the EDT thread.
59 * You should not do blocking or long-running tasks in this method.
60 * @param e The order change event.
61 */
62 void layerOrderChanged(LayerOrderChangeEvent e);
63 }
64
65 protected static class LayerManagerEvent {
66 private final LayerManager source;
67
68 LayerManagerEvent(LayerManager source) {
69 this.source = source;
70 }
71
72 public LayerManager getSource() {
73 return source;
74 }
75 }
76
77 /**
78 * The event that is fired whenever a layer was added.
79 * @author Michael Zangl
80 */
81 public static class LayerAddEvent extends LayerManagerEvent {
82 private final Layer addedLayer;
83
84 LayerAddEvent(LayerManager source, Layer addedLayer) {
85 super(source);
86 this.addedLayer = addedLayer;
87 }
88
89 /**
90 * Gets the layer that was added.
91 * @return The added layer.
92 */
93 public Layer getAddedLayer() {
94 return addedLayer;
95 }
96
97 @Override
98 public String toString() {
99 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}
Note: See TracBrowser for help on using the repository browser.