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

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

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

File size: 14.1 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 final 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(() -> realAddLayer(layer));
181 }
182
183 protected synchronized void realAddLayer(Layer layer) {
184 if (containsLayer(layer)) {
185 throw new IllegalArgumentException("Cannot add a layer twice.");
186 }
187 LayerPositionStrategy positionStrategy = layer.getDefaultLayerPosition();
188 int position = positionStrategy.getPosition(this);
189 checkPosition(position);
190 insertLayerAt(layer, position);
191 fireLayerAdded(layer);
192 if (Main.map != null) {
193 layer.hookUpMapView(); // needs to be after fireLayerAdded
194 }
195 }
196
197 /**
198 * Remove the layer from the mapview. If the layer was in the list before,
199 * an LayerChange event is fired.
200 * @param layer The layer to remove
201 */
202 public void removeLayer(final Layer layer) {
203 // we force this on to the EDT Thread to make events fire from there.
204 // The synchronization lock needs to be held by the EDT.
205 GuiHelper.runInEDTAndWaitWithException(() -> realRemoveLayer(layer));
206 }
207
208 protected synchronized void realRemoveLayer(Layer layer) {
209 GuiHelper.assertCallFromEdt();
210 Set<Layer> toRemove = Collections.newSetFromMap(new IdentityHashMap<Layer, Boolean>());
211 toRemove.add(layer);
212
213 while (!toRemove.isEmpty()) {
214 Iterator<Layer> iterator = toRemove.iterator();
215 Layer layerToRemove = iterator.next();
216 iterator.remove();
217 checkContainsLayer(layerToRemove);
218
219 Collection<Layer> newToRemove = realRemoveSingleLayer(layerToRemove);
220 toRemove.addAll(newToRemove);
221 }
222 }
223
224 protected Collection<Layer> realRemoveSingleLayer(Layer layerToRemove) {
225 Collection<Layer> newToRemove = fireLayerRemoving(layerToRemove);
226 layers.remove(layerToRemove);
227 return newToRemove;
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 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 <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 to remove
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 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.