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

Last change on this file since 10507 was 10507, checked in by Don-vip, 8 years ago

fix #13095 - Exception on closing layers (patch by michael2402) - gsoc-core

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