001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm.event; 003 004import java.util.Collections; 005import java.util.HashSet; 006import java.util.LinkedHashSet; 007import java.util.Set; 008import java.util.stream.Collectors; 009import java.util.stream.Stream; 010 011import org.openstreetmap.josm.data.osm.DataSelectionListener; 012import org.openstreetmap.josm.data.osm.INode; 013import org.openstreetmap.josm.data.osm.IPrimitive; 014import org.openstreetmap.josm.data.osm.IRelation; 015import org.openstreetmap.josm.data.osm.IWay; 016import org.openstreetmap.josm.data.osm.OsmData; 017import org.openstreetmap.josm.data.osm.OsmPrimitive; 018import org.openstreetmap.josm.tools.CheckParameterUtil; 019 020/** 021 * This interface is the same as {@link DataSelectionListener}, except it isn't {@link OsmPrimitive} specific. 022 * @author Taylor Smock, Michael Zangl (original code) 023 * @param <O> the base type of OSM primitives 024 * @param <N> type representing OSM nodes 025 * @param <W> type representing OSM ways 026 * @param <R> type representing OSM relations 027 * @param <D> The dataset type 028 * @since 17862 029 */ 030@FunctionalInterface 031public interface IDataSelectionListener<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, 032 D extends OsmData<O, N, W, R>> { 033 /** 034 * Called whenever the selection is changed. 035 * 036 * You get notified about the new selection, the elements that were added and removed and the layer that triggered the event. 037 * @param event The selection change event. 038 * @see SelectionChangeEvent 039 */ 040 void selectionChanged(SelectionChangeEvent<O, N, W, R, D> event); 041 042 /** 043 * The event that is fired when the selection changed. 044 * @author Michael Zangl 045 * @param <O> the base type of OSM primitives 046 * @param <N> type representing OSM nodes 047 * @param <W> type representing OSM ways 048 * @param <R> type representing OSM relations 049 * @param <D> The dataset type 050 * @since 17862 (generics) 051 */ 052 interface SelectionChangeEvent<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, 053 D extends OsmData<O, N, W, R>> { 054 /** 055 * Gets the previous selection 056 * <p> 057 * This collection cannot be modified and will not change. 058 * @return The old selection 059 */ 060 Set<O> getOldSelection(); 061 062 /** 063 * Gets the new selection. New elements are added to the end of the collection. 064 * <p> 065 * This collection cannot be modified and will not change. 066 * @return The new selection 067 */ 068 Set<O> getSelection(); 069 070 /** 071 * Gets the primitives that have been removed from the selection. 072 * <p> 073 * Those are the primitives contained in {@link #getOldSelection()} but not in {@link #getSelection()} 074 * <p> 075 * This collection cannot be modified and will not change. 076 * @return The primitives that were removed 077 */ 078 Set<O> getRemoved(); 079 080 /** 081 * Gets the primitives that have been added to the selection. 082 * <p> 083 * Those are the primitives contained in {@link #getSelection()} but not in {@link #getOldSelection()} 084 * <p> 085 * This collection cannot be modified and will not change. 086 * @return The primitives that were added 087 */ 088 Set<O> getAdded(); 089 090 /** 091 * Gets the data set that triggered this selection event. 092 * @return The data set. 093 */ 094 D getSource(); 095 096 /** 097 * Test if this event did not change anything. 098 * <p> 099 * This will return <code>false</code> for all events that are sent to listeners, so you don't need to test it. 100 * @return <code>true</code> if this did not change the selection. 101 */ 102 default boolean isNop() { 103 return getAdded().isEmpty() && getRemoved().isEmpty(); 104 } 105 } 106 107 /** 108 * The base class for selection events 109 * @author Michael Zangl 110 * @param <O> the base type of OSM primitives 111 * @param <N> type representing OSM nodes 112 * @param <W> type representing OSM ways 113 * @param <R> type representing OSM relations 114 * @param <D> The dataset type 115 * @since 12048, 17862 (generics) 116 */ 117 abstract class AbstractSelectionEvent<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, 118 D extends OsmData<O, N, W, R>> implements SelectionChangeEvent<O, N, W, R, D> { 119 private final D source; 120 private final Set<O> old; 121 122 protected AbstractSelectionEvent(D source, Set<O> old) { 123 CheckParameterUtil.ensureParameterNotNull(source, "source"); 124 CheckParameterUtil.ensureParameterNotNull(old, "old"); 125 this.source = source; 126 this.old = Collections.unmodifiableSet(old); 127 } 128 129 @Override 130 public Set<O> getOldSelection() { 131 return old; 132 } 133 134 @Override 135 public D getSource() { 136 return source; 137 } 138 } 139 140 /** 141 * The selection is replaced by a new selection 142 * @author Michael Zangl 143 * @param <O> the base type of OSM primitives 144 * @param <N> type representing OSM nodes 145 * @param <W> type representing OSM ways 146 * @param <R> type representing OSM relations 147 * @param <D> The dataset type 148 * @since 17862 (generics) 149 */ 150 class SelectionReplaceEvent<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, D extends OsmData<O, N, W, R>> 151 extends AbstractSelectionEvent<O, N, W, R, D> { 152 private final Set<O> current; 153 private Set<O> removed; 154 private Set<O> added; 155 156 /** 157 * Create a {@link SelectionReplaceEvent} 158 * @param source The source dataset 159 * @param old The old primitives that were previously selected. The caller needs to ensure that this set is not modified. 160 * @param newSelection The primitives of the new selection. 161 */ 162 public SelectionReplaceEvent(D source, Set<O> old, Stream<O> newSelection) { 163 super(source, old); 164 this.current = newSelection.collect(Collectors.toCollection(LinkedHashSet::new)); 165 } 166 167 @Override 168 public Set<O> getSelection() { 169 return current; 170 } 171 172 @Override 173 public synchronized Set<O> getRemoved() { 174 if (removed == null) { 175 removed = getOldSelection().stream() 176 .filter(p -> !current.contains(p)) 177 .collect(Collectors.toCollection(LinkedHashSet::new)); 178 } 179 return removed; 180 } 181 182 @Override 183 public synchronized Set<O> getAdded() { 184 if (added == null) { 185 added = current.stream() 186 .filter(p -> !getOldSelection().contains(p)).collect(Collectors.toCollection(LinkedHashSet::new)); 187 } 188 return added; 189 } 190 191 @Override 192 public String toString() { 193 return "SelectionReplaceEvent [current=" + current + ", removed=" + removed + ", added=" + added + ']'; 194 } 195 } 196 197 /** 198 * Primitives are added to the selection 199 * @author Michael Zangl 200 * @param <O> the base type of OSM primitives 201 * @param <N> type representing OSM nodes 202 * @param <W> type representing OSM ways 203 * @param <R> type representing OSM relations 204 * @param <D> The dataset type 205 * @since 17862 (generics) 206 */ 207 class SelectionAddEvent<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, D extends OsmData<O, N, W, R>> 208 extends AbstractSelectionEvent<O, N, W, R, D> { 209 private final Set<O> add; 210 private final Set<O> current; 211 212 /** 213 * Create a {@link SelectionAddEvent} 214 * @param source The source dataset 215 * @param old The old primitives that were previously selected. The caller needs to ensure that this set is not modified. 216 * @param toAdd The primitives to add. 217 */ 218 public SelectionAddEvent(D source, Set<O> old, Stream<O> toAdd) { 219 super(source, old); 220 this.add = toAdd 221 .filter(p -> !old.contains(p)) 222 .collect(Collectors.toCollection(LinkedHashSet::new)); 223 if (this.add.isEmpty()) { 224 this.current = this.getOldSelection(); 225 } else { 226 this.current = new LinkedHashSet<>(old); 227 this.current.addAll(add); 228 } 229 } 230 231 @Override 232 public Set<O> getSelection() { 233 return Collections.unmodifiableSet(current); 234 } 235 236 @Override 237 public Set<O> getRemoved() { 238 return Collections.emptySet(); 239 } 240 241 @Override 242 public Set<O> getAdded() { 243 return Collections.unmodifiableSet(add); 244 } 245 246 @Override 247 public String toString() { 248 return "SelectionAddEvent [add=" + add + ", current=" + current + ']'; 249 } 250 } 251 252 /** 253 * Primitives are removed from the selection 254 * @author Michael Zangl 255 * @param <O> the base type of OSM primitives 256 * @param <N> type representing OSM nodes 257 * @param <W> type representing OSM ways 258 * @param <R> type representing OSM relations 259 * @param <D> The dataset type 260 * @since 12048, 17862 (generics) 261 */ 262 class SelectionRemoveEvent<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, D extends OsmData<O, N, W, R>> 263 extends AbstractSelectionEvent<O, N, W, R, D> { 264 private final Set<O> remove; 265 private final Set<O> current; 266 267 /** 268 * Create a {@code SelectionRemoveEvent} 269 * @param source The source dataset 270 * @param old The old primitives that were previously selected. The caller needs to ensure that this set is not modified. 271 * @param toRemove The primitives to remove. 272 */ 273 public SelectionRemoveEvent(D source, Set<O> old, Stream<O> toRemove) { 274 super(source, old); 275 this.remove = toRemove 276 .filter(old::contains) 277 .collect(Collectors.toCollection(LinkedHashSet::new)); 278 if (this.remove.isEmpty()) { 279 this.current = this.getOldSelection(); 280 } else { 281 HashSet<O> currentSet = new LinkedHashSet<>(old); 282 currentSet.removeAll(remove); 283 current = currentSet; 284 } 285 } 286 287 @Override 288 public Set<O> getSelection() { 289 return Collections.unmodifiableSet(current); 290 } 291 292 @Override 293 public Set<O> getRemoved() { 294 return Collections.unmodifiableSet(remove); 295 } 296 297 @Override 298 public Set<O> getAdded() { 299 return Collections.emptySet(); 300 } 301 302 @Override 303 public String toString() { 304 return "SelectionRemoveEvent [remove=" + remove + ", current=" + current + ']'; 305 } 306 } 307 308 /** 309 * Toggle the selected state of a primitive 310 * @author Michael Zangl 311 * @param <O> the base type of OSM primitives 312 * @param <N> type representing OSM nodes 313 * @param <W> type representing OSM ways 314 * @param <R> type representing OSM relations 315 * @param <D> The dataset type 316 * @since 17862 (generics) 317 */ 318 class SelectionToggleEvent<O extends IPrimitive, N extends INode, W extends IWay<N>, R extends IRelation<?>, D extends OsmData<O, N, W, R>> 319 extends AbstractSelectionEvent<O, N, W, R, D> { 320 private final Set<O> current; 321 private final Set<O> remove; 322 private final Set<O> add; 323 324 /** 325 * Create a {@link SelectionToggleEvent} 326 * @param source The source dataset 327 * @param old The old primitives that were previously selected. The caller needs to ensure that this set is not modified. 328 * @param toToggle The primitives to toggle. 329 */ 330 public SelectionToggleEvent(D source, Set<O> old, Stream<O> toToggle) { 331 super(source, old); 332 HashSet<O> currentSet = new LinkedHashSet<>(old); 333 HashSet<O> removeSet = new LinkedHashSet<>(); 334 HashSet<O> addSet = new LinkedHashSet<>(); 335 toToggle.forEach(p -> { 336 if (currentSet.remove(p)) { 337 removeSet.add(p); 338 } else { 339 addSet.add(p); 340 currentSet.add(p); 341 } 342 }); 343 this.current = Collections.unmodifiableSet(currentSet); 344 this.remove = Collections.unmodifiableSet(removeSet); 345 this.add = Collections.unmodifiableSet(addSet); 346 } 347 348 @Override 349 public Set<O> getSelection() { 350 return current; 351 } 352 353 @Override 354 public Set<O> getRemoved() { 355 return remove; 356 } 357 358 @Override 359 public Set<O> getAdded() { 360 return add; 361 } 362 363 @Override 364 public String toString() { 365 return "SelectionToggleEvent [current=" + current + ", remove=" + remove + ", add=" + add + ']'; 366 } 367 } 368}