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}