001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer; 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.IdentityHashMap; 008import java.util.Iterator; 009import java.util.List; 010import java.util.Set; 011import java.util.concurrent.CopyOnWriteArrayList; 012import java.util.function.Consumer; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.gui.util.GuiHelper; 016import org.openstreetmap.josm.tools.Utils; 017import org.openstreetmap.josm.tools.bugreport.BugReport; 018 019/** 020 * This class handles the layer management. 021 * <p> 022 * This manager handles a list of layers with the first layer being the front layer. 023 * <h1>Threading</h1> 024 * Synchronization of the layer manager is done by synchronizing all read/write access. All changes are internally done in the EDT thread. 025 * 026 * Methods of this manager may be called from any thread in any order. 027 * Listeners are called while this layer manager is locked, so they should not block on other threads. 028 * 029 * @author Michael Zangl 030 * @since 10273 031 */ 032public class LayerManager { 033 /** 034 * Interface to notify listeners of a layer change. 035 */ 036 public interface LayerChangeListener { 037 /** 038 * Notifies this listener that a layer has been added. 039 * <p> 040 * Listeners are called in the EDT thread. You should not do blocking or long-running tasks in this method. 041 * @param e The new added layer event 042 */ 043 void layerAdded(LayerAddEvent e); 044 045 /** 046 * Notifies this listener that a alayer was just removed. 047 * <p> 048 * Listeners are called in the EDT thread after the layer was removed. 049 * Use {@link LayerRemoveEvent#scheduleRemoval(Collection)} to remove more layers. 050 * You should not do blocking or long-running tasks in this method. 051 * @param e The layer to be removed (as event) 052 */ 053 void layerRemoving(LayerRemoveEvent e); 054 055 /** 056 * Notifies this listener that the order of layers was changed. 057 * <p> 058 * Listeners are called in the EDT thread. 059 * You should not do blocking or long-running tasks in this method. 060 * @param e The order change event. 061 */ 062 void layerOrderChanged(LayerOrderChangeEvent e); 063 } 064 065 protected static class LayerManagerEvent { 066 private final LayerManager source; 067 068 LayerManagerEvent(LayerManager source) { 069 this.source = source; 070 } 071 072 public LayerManager getSource() { 073 return source; 074 } 075 } 076 077 /** 078 * The event that is fired whenever a layer was added. 079 * @author Michael Zangl 080 */ 081 public static class LayerAddEvent extends LayerManagerEvent { 082 private final Layer addedLayer; 083 084 LayerAddEvent(LayerManager source, Layer addedLayer) { 085 super(source); 086 this.addedLayer = addedLayer; 087 } 088 089 /** 090 * Gets the layer that was added. 091 * @return The added layer. 092 */ 093 public Layer getAddedLayer() { 094 return addedLayer; 095 } 096 097 @Override 098 public String toString() { 099 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<WMSLayer> 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}