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; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.gui.util.GuiHelper; 015import org.openstreetmap.josm.tools.Utils; 016import org.openstreetmap.josm.tools.bugreport.BugReport; 017 018/** 019 * This class handles the layer management. 020 * <p> 021 * This manager handles a list of layers with the first layer being the front layer. 022 * <h1>Threading</h1> 023 * Methods of this manager may be called from any thread in any order. 024 * Listeners are called while this layer manager is locked, so they should not block. 025 * 026 * @author Michael Zangl 027 * @since 10273 028 */ 029public class LayerManager { 030 /** 031 * Interface to notify listeners of a layer change. 032 */ 033 public interface LayerChangeListener { 034 /** 035 * Notifies this listener that a layer has been added. 036 * <p> 037 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. 038 * @param e The new added layer event 039 */ 040 void layerAdded(LayerAddEvent e); 041 042 /** 043 * Notifies this listener that a layer is about to be removed. 044 * <p> 045 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. 046 * @param e The layer to be removed (as event) 047 */ 048 void layerRemoving(LayerRemoveEvent e); 049 050 /** 051 * Notifies this listener that the order of layers was changed. 052 * <p> 053 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. 054 * @param e The order change event. 055 */ 056 void layerOrderChanged(LayerOrderChangeEvent e); 057 } 058 059 protected static class LayerManagerEvent { 060 private final LayerManager source; 061 062 LayerManagerEvent(LayerManager source) { 063 this.source = source; 064 } 065 066 public LayerManager getSource() { 067 return source; 068 } 069 } 070 071 /** 072 * The event that is fired whenever a layer was added. 073 * @author Michael Zangl 074 */ 075 public static class LayerAddEvent extends LayerManagerEvent { 076 private final Layer addedLayer; 077 078 LayerAddEvent(LayerManager source, Layer addedLayer) { 079 super(source); 080 this.addedLayer = addedLayer; 081 } 082 083 /** 084 * Gets the layer that was added. 085 * @return The added layer. 086 */ 087 public Layer getAddedLayer() { 088 return addedLayer; 089 } 090 091 @Override 092 public String toString() { 093 return "LayerAddEvent [addedLayer=" + addedLayer + ']'; 094 } 095 } 096 097 /** 098 * The event that is fired before removing a layer. 099 * @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<WMSLayer> 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}