001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.geom.Area;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.LinkedHashSet;
013import java.util.LinkedList;
014import java.util.List;
015import java.util.Map;
016import java.util.Objects;
017import java.util.Set;
018import java.util.concurrent.CopyOnWriteArrayList;
019import java.util.concurrent.atomic.AtomicBoolean;
020import java.util.concurrent.locks.Lock;
021import java.util.concurrent.locks.ReadWriteLock;
022import java.util.concurrent.locks.ReentrantReadWriteLock;
023import java.util.function.Function;
024import java.util.function.Predicate;
025import java.util.function.Supplier;
026import java.util.stream.Collectors;
027import java.util.stream.Stream;
028
029import org.openstreetmap.josm.data.APIDataSet.APIOperation;
030import org.openstreetmap.josm.data.Bounds;
031import org.openstreetmap.josm.data.DataSource;
032import org.openstreetmap.josm.data.ProjectionBounds;
033import org.openstreetmap.josm.data.conflict.ConflictCollection;
034import org.openstreetmap.josm.data.coor.EastNorth;
035import org.openstreetmap.josm.data.coor.LatLon;
036import org.openstreetmap.josm.data.gpx.GpxData.XMLNamespace;
037import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent;
038import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent;
039import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent;
040import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent;
041import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent;
042import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
043import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
044import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
045import org.openstreetmap.josm.data.osm.event.DataSetListener;
046import org.openstreetmap.josm.data.osm.event.DataSourceAddedEvent;
047import org.openstreetmap.josm.data.osm.event.DataSourceRemovedEvent;
048import org.openstreetmap.josm.data.osm.event.FilterChangedEvent;
049import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
050import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent;
051import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
052import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
053import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
054import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
055import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
056import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
057import org.openstreetmap.josm.data.projection.Projection;
058import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
059import org.openstreetmap.josm.data.projection.ProjectionRegistry;
060import org.openstreetmap.josm.gui.progress.ProgressMonitor;
061import org.openstreetmap.josm.spi.preferences.Config;
062import org.openstreetmap.josm.tools.ListenerList;
063import org.openstreetmap.josm.tools.Logging;
064import org.openstreetmap.josm.tools.SubclassFilteredCollection;
065
066/**
067 * DataSet is the data behind the application. It can consists of only a few points up to the whole
068 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
069 *
070 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
071 * store some information.
072 *
073 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
074 * lead to data corruption or ConcurrentModificationException. However when for example one thread
075 * removes primitive and other thread try to add another primitive referring to the removed primitive,
076 * DataIntegrityException will occur.
077 *
078 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
079 * Dataset will not change. Sample usage:
080 * <code>
081 *   ds.getReadLock().lock();
082 *   try {
083 *     // .. do something with dataset
084 *   } finally {
085 *     ds.getReadLock().unlock();
086 *   }
087 * </code>
088 *
089 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
090 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
091 * reasons - GUI can be updated after all changes are done.
092 * Sample usage:
093 * <code>
094 * ds.beginUpdate()
095 * try {
096 *   // .. do modifications
097 * } finally {
098 *  ds.endUpdate();
099 * }
100 * </code>
101 *
102 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
103 * automatically.
104 *
105 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
106 * sample ticket
107 *
108 * @author imi
109 */
110public final class DataSet implements OsmData<OsmPrimitive, Node, Way, Relation>, ProjectionChangeListener {
111
112    /**
113     * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
114     */
115    private static final int MAX_SINGLE_EVENTS = 30;
116
117    /**
118     * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
119     */
120    private static final int MAX_EVENTS = 1000;
121
122    private final QuadBucketPrimitiveStore<Node, Way, Relation> store = new QuadBucketPrimitiveStore<>();
123
124    private final Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true);
125    private final Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives
126            .foreignKey(new Storage.PrimitiveIdHash());
127    private final CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>();
128
129    // provide means to highlight map elements that are not osm primitives
130    private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>();
131    private Collection<WaySegment> highlightedWaySegments = new LinkedList<>();
132    private final ListenerList<HighlightUpdateListener> highlightUpdateListeners = ListenerList.create();
133
134    // Number of open calls to beginUpdate
135    private int updateCount;
136    // Events that occurred while dataset was locked but should be fired after write lock is released
137    private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>();
138
139    private String name;
140    private DownloadPolicy downloadPolicy = DownloadPolicy.NORMAL;
141    private UploadPolicy uploadPolicy = UploadPolicy.NORMAL;
142    /** Flag used to know if the dataset should not be editable */
143    private final AtomicBoolean isReadOnly = new AtomicBoolean(false);
144
145    private final ReadWriteLock lock = new ReentrantReadWriteLock();
146
147    /**
148     * The mutex lock that is used to synchronize selection changes.
149     */
150    private final Object selectionLock = new Object();
151    /**
152     * The current selected primitives. This is always a unmodifiable set.
153     *
154     * The set should be ordered in the order in which the primitives have been added to the selection.
155     */
156    private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet();
157
158    /**
159     * A list of listeners that listen to selection changes on this layer.
160     */
161    private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
162
163    private Area cachedDataSourceArea;
164    private List<Bounds> cachedDataSourceBounds;
165
166    /**
167     * All data sources of this DataSet.
168     */
169    private final Collection<DataSource> dataSources = new LinkedList<>();
170
171    /**
172     * A list of listeners that listen to DataSource changes on this layer
173     */
174    private final ListenerList<DataSourceListener> dataSourceListeners = ListenerList.create();
175
176    private final ConflictCollection conflicts = new ConflictCollection();
177
178    private short mappaintCacheIdx = 1;
179    private String remark;
180
181    /**
182     * Used to temporarily store namespaces from the GPX file in case the user converts back and forth.
183     * Will not be saved to .osm files, but that's not necessary because GPX files won't automatically be overridden after that.
184     */
185    private List<XMLNamespace> gpxNamespaces;
186
187    /**
188     * Constructs a new {@code DataSet}.
189     */
190    public DataSet() {
191        // Transparently register as projection change listener. No need to explicitly remove
192        // the listener, projection change listeners are managed as WeakReferences.
193        ProjectionRegistry.addProjectionChangeListener(this);
194    }
195
196    /**
197     * Creates a new {@link DataSet}.
198     * @param copyFrom An other {@link DataSet} to copy the contents of this dataset from.
199     * @since 10346
200     */
201    public DataSet(DataSet copyFrom) {
202        this();
203        copyFrom.getReadLock().lock();
204        try {
205            clonePrimitives(copyFrom.getNodes(), copyFrom.getWays(), copyFrom.getRelations());
206            DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(this,
207                    new LinkedHashSet<>(dataSources), copyFrom.dataSources.stream());
208            for (DataSource source : copyFrom.dataSources) {
209                dataSources.add(new DataSource(source));
210            }
211            dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
212            version = copyFrom.version;
213            uploadPolicy = copyFrom.uploadPolicy;
214            downloadPolicy = copyFrom.downloadPolicy;
215            isReadOnly.set(copyFrom.isReadOnly.get());
216        } finally {
217            copyFrom.getReadLock().unlock();
218        }
219    }
220
221    /**
222     * Constructs a new {@code DataSet} initially filled with the given primitives.
223     * @param osmPrimitives primitives to add to this data set
224     * @since 12726
225     */
226    public DataSet(OsmPrimitive... osmPrimitives) {
227        this();
228        update(() -> {
229            for (OsmPrimitive o : osmPrimitives) {
230                addPrimitive(o);
231            }
232        });
233    }
234
235    /**
236     * Clones the specified primitives into this data set.
237     * @param nodes nodes to clone
238     * @param ways ways to clone
239     * @param relations relations to clone
240     * @return the map of cloned primitives indexed by their original version
241     * @since 18001
242     */
243    public Map<OsmPrimitive, OsmPrimitive> clonePrimitives(Iterable<Node> nodes, Iterable<Way> ways, Iterable<Relation> relations) {
244        Map<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>();
245        for (Node n : nodes) {
246            Node newNode = new Node(n);
247            primMap.put(n, newNode);
248            addPrimitive(newNode);
249        }
250        for (Way w : ways) {
251            Way newWay = new Way(w, false, false);
252            primMap.put(w, newWay);
253            List<Node> newNodes = w.getNodes().stream()
254                    .map(n -> (Node) primMap.get(n))
255                    .collect(Collectors.toList());
256            newWay.setNodes(newNodes);
257            addPrimitive(newWay);
258        }
259        // Because relations can have other relations as members we first clone all relations
260        // and then get the cloned members
261        for (Relation r : relations) {
262            Relation newRelation = new Relation(r, false, false);
263            primMap.put(r, newRelation);
264            addPrimitive(newRelation);
265        }
266        for (Relation r : relations) {
267            ((Relation) primMap.get(r)).setMembers(r.getMembers().stream()
268                    .map(rm -> new RelationMember(rm.getRole(), primMap.get(rm.getMember())))
269                    .collect(Collectors.toList()));
270        }
271        return primMap;
272    }
273
274    /**
275     * Adds a new data source.
276     * @param source data source to add
277     * @return {@code true} if the collection changed as a result of the call
278     * @since 11626
279     */
280    public synchronized boolean addDataSource(DataSource source) {
281        return addDataSources(Collections.singleton(source));
282    }
283
284    /**
285     * Adds new data sources.
286     * @param sources data sources to add
287     * @return {@code true} if the collection changed as a result of the call
288     * @since 11626
289     */
290    public synchronized boolean addDataSources(Collection<DataSource> sources) {
291        DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(this,
292                new LinkedHashSet<>(dataSources), sources.stream());
293        boolean changed = dataSources.addAll(sources);
294        if (changed) {
295            cachedDataSourceArea = null;
296            cachedDataSourceBounds = null;
297        }
298        dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
299        return changed;
300    }
301
302    @Override
303    public Lock getReadLock() {
304        return lock.readLock();
305    }
306
307    /**
308     * History of selections - shared by plugins and SelectionListDialog
309     */
310    private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>();
311
312    /**
313     * Replies the history of JOSM selections
314     *
315     * @return list of history entries
316     */
317    public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() {
318        return selectionHistory;
319    }
320
321    /**
322     * Clears selection history list
323     */
324    public void clearSelectionHistory() {
325        selectionHistory.clear();
326    }
327
328    /**
329     * The API version that created this data set, if any.
330     */
331    private String version;
332
333    @Override
334    public String getVersion() {
335        return version;
336    }
337
338    /**
339     * Sets the API version this dataset was created from.
340     *
341     * @param version the API version, i.e. "0.6"
342     * @throws IllegalStateException if the dataset is read-only
343     */
344    public void setVersion(String version) {
345        checkModifiable();
346        this.version = version;
347    }
348
349    @Override
350    public DownloadPolicy getDownloadPolicy() {
351        return this.downloadPolicy;
352    }
353
354    @Override
355    public void setDownloadPolicy(DownloadPolicy downloadPolicy) {
356        this.downloadPolicy = Objects.requireNonNull(downloadPolicy);
357    }
358
359    @Override
360    public UploadPolicy getUploadPolicy() {
361        return this.uploadPolicy;
362    }
363
364    @Override
365    public void setUploadPolicy(UploadPolicy uploadPolicy) {
366        this.uploadPolicy = Objects.requireNonNull(uploadPolicy);
367    }
368
369    /**
370     * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
371     */
372    private final Map<String, String> changeSetTags = new HashMap<>();
373
374    /**
375     * Replies the set of changeset tags to be applied when or if this is ever uploaded.
376     * @return the set of changeset tags
377     * @see #addChangeSetTag
378     */
379    public Map<String, String> getChangeSetTags() {
380        return changeSetTags;
381    }
382
383    /**
384     * Adds a new changeset tag.
385     * @param k Key
386     * @param v Value
387     * @see #getChangeSetTags
388     */
389    public void addChangeSetTag(String k, String v) {
390        if (v != null) {
391            this.changeSetTags.put(k, v);
392        } else {
393            this.changeSetTags.remove(k);
394        }
395    }
396
397    @Override
398    public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) {
399        return new SubclassFilteredCollection<>(allPrimitives, predicate);
400    }
401
402    @Override
403    public Collection<Node> getNodes() {
404        return getPrimitives(Node.class::isInstance);
405    }
406
407    @Override
408    public List<Node> searchNodes(BBox bbox) {
409        lock.readLock().lock();
410        try {
411            return store.searchNodes(bbox);
412        } finally {
413            lock.readLock().unlock();
414        }
415    }
416
417    @Override
418    public Collection<Way> getWays() {
419        return getPrimitives(Way.class::isInstance);
420    }
421
422    @Override
423    public List<Way> searchWays(BBox bbox) {
424        lock.readLock().lock();
425        try {
426            return store.searchWays(bbox);
427        } finally {
428            lock.readLock().unlock();
429        }
430    }
431
432    @Override
433    public List<Relation> searchRelations(BBox bbox) {
434        lock.readLock().lock();
435        try {
436            return store.searchRelations(bbox);
437        } finally {
438            lock.readLock().unlock();
439        }
440    }
441
442    /**
443     * Searches for all primitives in the given bounding box
444     *
445     * @param bbox the bounding box
446     * @return List of primitives in the given bbox. Can be empty but not null
447     * @since 15891
448     */
449    public List<OsmPrimitive> searchPrimitives(BBox bbox) {
450        List<OsmPrimitive> primitiveList = new ArrayList<>();
451        primitiveList.addAll(searchNodes(bbox));
452        primitiveList.addAll(searchWays(bbox));
453        primitiveList.addAll(searchRelations(bbox));
454        return primitiveList;
455    }
456
457    @Override
458    public Collection<Relation> getRelations() {
459        return getPrimitives(Relation.class::isInstance);
460    }
461
462    /**
463     * Determines if the given node can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
464     * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
465     *
466     * @param n The node to search
467     * @return {@code true} if {@code n} can be retrieved in this data set, {@code false} otherwise
468     * @since 7501
469     */
470    @Override
471    public boolean containsNode(Node n) {
472        return store.containsNode(n);
473    }
474
475    /**
476     * Determines if the given way can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
477     * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
478     *
479     * @param w The way to search
480     * @return {@code true} if {@code w} can be retrieved in this data set, {@code false} otherwise
481     * @since 7501
482     */
483    @Override
484    public boolean containsWay(Way w) {
485        return store.containsWay(w);
486    }
487
488    /**
489     * Determines if the given relation can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
490     * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
491     *
492     * @param r The relation to search
493     * @return {@code true} if {@code r} can be retrieved in this data set, {@code false} otherwise
494     * @since 7501
495     */
496    @Override
497    public boolean containsRelation(Relation r) {
498        return store.containsRelation(r);
499    }
500
501    /**
502     * Adds a primitive to the dataset.
503     *
504     * @param primitive the primitive.
505     * @throws IllegalStateException if the dataset is read-only
506     */
507    @Override
508    public void addPrimitive(OsmPrimitive primitive) {
509        Objects.requireNonNull(primitive, "primitive");
510        checkModifiable();
511        update(() -> {
512            if (getPrimitiveById(primitive) != null)
513                throw new DataIntegrityProblemException(
514                        tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()),
515                        null, primitive);
516
517            allPrimitives.add(primitive);
518            primitive.setDataset(this);
519            primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly)
520            store.addPrimitive(primitive);
521            firePrimitivesAdded(Collections.singletonList(primitive), false);
522        });
523    }
524
525    /**
526     * Adds recursively a primitive, and all its children, to the dataset.
527     *
528     * @param primitive the primitive.
529     * @throws IllegalStateException if the dataset is read-only
530     * @since 17981
531     */
532    public void addPrimitiveRecursive(OsmPrimitive primitive) {
533        if (primitive instanceof Way) {
534            ((Way) primitive).getNodes().forEach(n -> addPrimitiveRecursive(n));
535        } else if (primitive instanceof Relation) {
536            ((Relation) primitive).getMembers().forEach(m -> addPrimitiveRecursive(m.getMember()));
537        }
538        addPrimitive(primitive);
539    }
540
541    /**
542     * Removes a primitive from the dataset. This method only removes the
543     * primitive form the respective collection of primitives managed
544     * by this dataset, i.e. from {@code store.nodes}, {@code store.ways}, or
545     * {@code store.relations}. References from other primitives to this
546     * primitive are left unchanged.
547     *
548     * @param primitiveId the id of the primitive
549     * @throws IllegalStateException if the dataset is read-only
550     */
551    public void removePrimitive(PrimitiveId primitiveId) {
552        checkModifiable();
553        update(() -> {
554            OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
555            if (primitive == null)
556                return;
557            removePrimitiveImpl(primitive);
558            firePrimitivesRemoved(Collections.singletonList(primitive), false);
559        });
560    }
561
562    private void removePrimitiveImpl(OsmPrimitive primitive) {
563        clearSelection(primitive.getPrimitiveId());
564        if (primitive.isSelected()) {
565            throw new DataIntegrityProblemException("Primitive was re-selected by a selection listener: " + primitive);
566        }
567        store.removePrimitive(primitive);
568        allPrimitives.remove(primitive);
569        primitive.setDataset(null);
570    }
571
572    void removePrimitive(OsmPrimitive primitive) {
573        checkModifiable();
574        update(() -> {
575            removePrimitiveImpl(primitive);
576            firePrimitivesRemoved(Collections.singletonList(primitive), false);
577        });
578    }
579
580    /*---------------------------------------------------
581     *   SELECTION HANDLING
582     *---------------------------------------------------*/
583
584    @Override
585    public void addSelectionListener(DataSelectionListener listener) {
586        selectionListeners.addListener(listener);
587    }
588
589    @Override
590    public void removeSelectionListener(DataSelectionListener listener) {
591        selectionListeners.removeListener(listener);
592    }
593
594    /**
595     * Returns selected nodes and ways.
596     * @return selected nodes and ways
597     */
598    public Collection<OsmPrimitive> getSelectedNodesAndWays() {
599        return new SubclassFilteredCollection<>(getSelected(),
600                primitive -> primitive instanceof Node || primitive instanceof Way);
601    }
602
603    @Override
604    public Collection<WaySegment> getHighlightedVirtualNodes() {
605        return Collections.unmodifiableCollection(highlightedVirtualNodes);
606    }
607
608    @Override
609    public Collection<WaySegment> getHighlightedWaySegments() {
610        return Collections.unmodifiableCollection(highlightedWaySegments);
611    }
612
613    @Override
614    public void addHighlightUpdateListener(HighlightUpdateListener listener) {
615        highlightUpdateListeners.addListener(listener);
616    }
617
618    @Override
619    public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
620        highlightUpdateListeners.removeListener(listener);
621    }
622
623    /**
624     * Adds a listener that gets notified whenever the data sources change
625     *
626     * @param listener The listener
627     * @see #removeDataSourceListener
628     * @see #getDataSources
629     * @since 15609
630     */
631    public void addDataSourceListener(DataSourceListener listener) {
632        dataSourceListeners.addListener(listener);
633    }
634
635    /**
636     * Removes a listener that gets notified whenever the data sources change
637     *
638     * @param listener The listener
639     * @see #addDataSourceListener
640     * @see #getDataSources
641     * @since 15609
642     */
643    public void removeDataSourceListener(DataSourceListener listener) {
644        dataSourceListeners.removeListener(listener);
645    }
646
647    @Override
648    public Collection<OsmPrimitive> getAllSelected() {
649        return currentSelectedPrimitives;
650    }
651
652    @Override
653    public boolean selectionEmpty() {
654        return currentSelectedPrimitives.isEmpty();
655    }
656
657    @Override
658    public boolean isSelected(OsmPrimitive osm) {
659        return currentSelectedPrimitives.contains(osm);
660    }
661
662    @Override
663    public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
664        if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
665            return;
666
667        highlightedVirtualNodes = waySegments;
668        fireHighlightingChanged();
669    }
670
671    @Override
672    public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
673        if (highlightedWaySegments.isEmpty() && waySegments.isEmpty())
674            return;
675
676        highlightedWaySegments = waySegments;
677        fireHighlightingChanged();
678    }
679
680    @Override
681    public void setSelected(Collection<? extends PrimitiveId> selection) {
682        setSelected(selection.stream());
683    }
684
685    @Override
686    public void setSelected(PrimitiveId... osm) {
687        setSelected(Stream.of(osm).filter(Objects::nonNull));
688    }
689
690    private void setSelected(Stream<? extends PrimitiveId> stream) {
691        doSelectionChange(old -> new SelectionReplaceEvent(this, old,
692                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
693    }
694
695    @Override
696    public void addSelected(Collection<? extends PrimitiveId> selection) {
697        addSelected(selection.stream());
698    }
699
700    @Override
701    public void addSelected(PrimitiveId... osm) {
702        addSelected(Stream.of(osm));
703    }
704
705    private void addSelected(Stream<? extends PrimitiveId> stream) {
706        doSelectionChange(old -> new SelectionAddEvent(this, old,
707                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
708    }
709
710    @Override
711    public void clearSelection(PrimitiveId... osm) {
712        clearSelection(Stream.of(osm));
713    }
714
715    @Override
716    public void clearSelection(Collection<? extends PrimitiveId> list) {
717        clearSelection(list.stream());
718    }
719
720    @Override
721    public void clearSelection() {
722        setSelected(Stream.empty());
723    }
724
725    private void clearSelection(Stream<? extends PrimitiveId> stream) {
726        doSelectionChange(old -> new SelectionRemoveEvent(this, old,
727                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
728    }
729
730    @Override
731    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
732        toggleSelected(osm.stream());
733    }
734
735    @Override
736    public void toggleSelected(PrimitiveId... osm) {
737        toggleSelected(Stream.of(osm));
738    }
739
740    private void toggleSelected(Stream<? extends PrimitiveId> stream) {
741        doSelectionChange(old -> new SelectionToggleEvent(this, old,
742                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
743    }
744
745    /**
746     * Do a selection change.
747     * <p>
748     * This is the only method that changes the current selection state.
749     * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
750     * @return true iff the command did change the selection.
751     * @since 12048
752     */
753    private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
754        synchronized (selectionLock) {
755            SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
756            if (event.isNop()) {
757                return false;
758            }
759            currentSelectedPrimitives = event.getSelection();
760            selectionListeners.fireEvent(l -> l.selectionChanged(event));
761            return true;
762        }
763    }
764
765    @Override
766    public synchronized Area getDataSourceArea() {
767        if (cachedDataSourceArea == null) {
768            cachedDataSourceArea = OsmData.super.getDataSourceArea();
769        }
770        return cachedDataSourceArea;
771    }
772
773    @Override
774    public synchronized List<Bounds> getDataSourceBounds() {
775        if (cachedDataSourceBounds == null) {
776            cachedDataSourceBounds = OsmData.super.getDataSourceBounds();
777        }
778        return Collections.unmodifiableList(cachedDataSourceBounds);
779    }
780
781    @Override
782    public synchronized Collection<DataSource> getDataSources() {
783        return Collections.unmodifiableCollection(dataSources);
784    }
785
786    @Override
787    public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
788        return primitiveId != null ? primitivesMap.get(primitiveId) : null;
789    }
790
791    /**
792     * Show message and stack trace in log in case primitive is not found
793     * @param primitiveId primitive id to look for
794     * @return Primitive by id.
795     */
796    private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
797        OsmPrimitive result = getPrimitiveById(primitiveId);
798        if (result == null && primitiveId != null) {
799            Logging.error(new IllegalStateException(tr(
800                    "JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
801                            + "at {2}. This is not a critical error, it should be safe to continue in your work.",
802                    primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Config.getUrls().getJOSMWebsite())));
803        }
804
805        return result;
806    }
807
808    private static void deleteWay(Way way) {
809        way.setNodes(null);
810        way.setDeleted(true);
811    }
812
813    /**
814     * Removes all references from ways in this dataset to a particular node.
815     *
816     * @param node the node
817     * @return The set of ways that have been modified
818     * @throws IllegalStateException if the dataset is read-only
819     */
820    public Set<Way> unlinkNodeFromWays(Node node) {
821        checkModifiable();
822        return update(() -> {
823            Set<Way> result = new HashSet<>();
824            for (Way way : node.getParentWays()) {
825                List<Node> wayNodes;
826                if (!way.isIncomplete()) {
827                    wayNodes = way.calculateRemoveNodes(Collections.singleton(node));
828                } else {
829                    wayNodes = way.getNodes();
830                    wayNodes.removeIf(node::equals);
831                }
832                if (wayNodes.size() < way.getNodesCount()) {
833                    if (wayNodes.size() < 2) {
834                        deleteWay(way);
835                    } else {
836                        way.setNodes(wayNodes);
837                    }
838                    result.add(way);
839                }
840            }
841            return result;
842        });
843    }
844
845    /**
846     * removes all references from relations in this dataset  to this primitive
847     *
848     * @param primitive the primitive
849     * @return The set of relations that have been modified
850     * @throws IllegalStateException if the dataset is read-only
851     */
852    public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
853        checkModifiable();
854        return update(() -> {
855            Set<Relation> result = new HashSet<>();
856            for (Relation relation : getRelations()) {
857                List<RelationMember> members = relation.getMembers();
858                boolean removed = members.removeIf(member -> member.getMember().equals(primitive));
859                if (removed) {
860                    relation.setMembers(members);
861                    result.add(relation);
862                }
863            }
864            return result;
865        });
866    }
867
868    /**
869     * Removes all references from other primitives to the referenced primitive.
870     *
871     * @param referencedPrimitive the referenced primitive
872     * @return The set of primitives that have been modified
873     * @throws IllegalStateException if the dataset is read-only
874     */
875    public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
876        checkModifiable();
877        return update(() -> {
878            Set<OsmPrimitive> result = new HashSet<>();
879            if (referencedPrimitive instanceof Node) {
880                result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
881            }
882            result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
883            return result;
884        });
885    }
886
887    @Override
888    public boolean isModified() {
889        return allPrimitives.parallelStream().anyMatch(OsmPrimitive::isModified);
890    }
891
892    /**
893     * Replies true if there is at least one primitive in this dataset which requires to be uploaded to server.
894     * @return true if there is at least one primitive in this dataset which requires to be uploaded to server
895     * @since 13161
896     */
897    public boolean requiresUploadToServer() {
898        return allPrimitives.parallelStream().anyMatch(p -> APIOperation.of(p) != null);
899    }
900
901    /**
902     * Adds a new data set listener.
903     * @param dsl The data set listener to add
904     */
905    public void addDataSetListener(DataSetListener dsl) {
906        listeners.addIfAbsent(dsl);
907    }
908
909    /**
910     * Removes a data set listener.
911     * @param dsl The data set listener to remove
912     */
913    public void removeDataSetListener(DataSetListener dsl) {
914        listeners.remove(dsl);
915    }
916
917    /**
918     * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
919     * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
920     * <br>
921     * Typical use case should look like this:
922     * <pre>
923     * ds.beginUpdate();
924     * try {
925     *   ...
926     * } finally {
927     *   ds.endUpdate();
928     * }
929     * </pre>
930     * @see #endUpdate()
931     */
932    public void beginUpdate() {
933        lock.writeLock().lock();
934        updateCount++;
935    }
936
937    /**
938     * Must be called after a previous call to {@link #beginUpdate()} to fire change events.
939     * <br>
940     * Typical use case should look like this:
941     * <pre>
942     * ds.beginUpdate();
943     * try {
944     *   ...
945     * } finally {
946     *   ds.endUpdate();
947     * }
948     * </pre>
949     * @see DataSet#beginUpdate()
950     */
951    public void endUpdate() {
952        if (updateCount > 0) {
953            updateCount--;
954            List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList();
955            if (updateCount == 0) {
956                eventsToFire = new ArrayList<>(cachedEvents);
957                cachedEvents.clear();
958            }
959
960            if (!eventsToFire.isEmpty()) {
961                lock.readLock().lock();
962                try {
963                    lock.writeLock().unlock();
964                    if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
965                        for (AbstractDatasetChangedEvent event : eventsToFire) {
966                            fireEventToListeners(event);
967                        }
968                    } else if (eventsToFire.size() == MAX_EVENTS) {
969                        fireEventToListeners(new DataChangedEvent(this));
970                    } else {
971                        fireEventToListeners(new DataChangedEvent(this, eventsToFire));
972                    }
973                } finally {
974                    lock.readLock().unlock();
975                }
976            } else {
977                lock.writeLock().unlock();
978            }
979
980        } else
981            throw new AssertionError("endUpdate called without beginUpdate");
982    }
983
984    /**
985     * Performs the update runnable between {@link #beginUpdate()} / {@link #endUpdate()} calls.
986     * @param runnable update action
987     * @since 16187
988     */
989    public void update(Runnable runnable) {
990        beginUpdate();
991        try {
992            runnable.run();
993        } finally {
994            endUpdate();
995        }
996    }
997
998    /**
999     * Performs the update function between {@link #beginUpdate()} / {@link #endUpdate()} calls.
1000     * @param function update function
1001     * @param t function argument
1002     * @param <T> argument type
1003     * @param <R> result type
1004     * @return function result
1005     * @since 16187
1006     */
1007    public <T, R> R update(Function<T, R> function, T t) {
1008        beginUpdate();
1009        try {
1010            return function.apply(t);
1011        } finally {
1012            endUpdate();
1013        }
1014    }
1015
1016    /**
1017     * Performs the update supplier between {@link #beginUpdate()} / {@link #endUpdate()} calls.
1018     * @param supplier update supplier
1019     * @param <R> result type
1020     * @return supplier result
1021     * @since 16187
1022     */
1023    public <R> R update(Supplier<R> supplier) {
1024        beginUpdate();
1025        try {
1026            return supplier.get();
1027        } finally {
1028            endUpdate();
1029        }
1030    }
1031
1032    private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1033        for (DataSetListener listener : listeners) {
1034            Logging.trace("Firing {0} to {1} (dataset)", event, listener);
1035            event.fire(listener);
1036        }
1037    }
1038
1039    private void fireEvent(AbstractDatasetChangedEvent event) {
1040        if (updateCount == 0)
1041            throw new AssertionError("dataset events can be fired only when dataset is locked");
1042        if (cachedEvents.size() < MAX_EVENTS) {
1043            cachedEvents.add(event);
1044        }
1045    }
1046
1047    void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1048        fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1049    }
1050
1051    void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1052        fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1053    }
1054
1055    void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1056        fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1057    }
1058
1059    void fireRelationMembersChanged(Relation r) {
1060        store.reindexRelation(r, Relation::updatePosition);
1061        fireEvent(new RelationMembersChangedEvent(this, r));
1062    }
1063
1064    void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1065        store.reindexNode(node, n -> n.setCoorInternal(newCoor, eastNorth), Way::updatePosition, Relation::updatePosition);
1066        fireEvent(new NodeMovedEvent(this, node));
1067    }
1068
1069    void fireWayNodesChanged(Way way) {
1070        if (!way.isEmpty()) {
1071            store.reindexWay(way, Way::updatePosition, Relation::updatePosition);
1072        }
1073        fireEvent(new WayNodesChangedEvent(this, way));
1074    }
1075
1076    void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1077        fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId,
1078                newChangesetId));
1079    }
1080
1081    void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1082        fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1083    }
1084
1085    void fireFilterChanged() {
1086        fireEvent(new FilterChangedEvent(this));
1087    }
1088
1089    void fireHighlightingChanged() {
1090        HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this);
1091        highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e));
1092    }
1093
1094    /**
1095     * Invalidates the internal cache of projected east/north coordinates.
1096     *
1097     * This method can be invoked after the globally configured projection method changed.
1098     */
1099    public void invalidateEastNorthCache() {
1100        if (ProjectionRegistry.getProjection() == null)
1101            return; // sanity check
1102        update(() -> getNodes().forEach(Node::invalidateEastNorthCache));
1103    }
1104
1105    /**
1106     * Cleanups all deleted primitives (really delete them from the dataset).
1107     */
1108    public void cleanupDeletedPrimitives() {
1109        update(() -> {
1110            Collection<OsmPrimitive> toCleanUp = getPrimitives(
1111                    primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()));
1112            if (!toCleanUp.isEmpty()) {
1113                // We unselect them in advance to not fire a selection change for every primitive
1114                clearSelection(toCleanUp.stream().map(OsmPrimitive::getPrimitiveId));
1115                for (OsmPrimitive primitive : toCleanUp) {
1116                    removePrimitiveImpl(primitive);
1117                }
1118                firePrimitivesRemoved(toCleanUp, false);
1119            }
1120        });
1121    }
1122
1123    /**
1124     * Removes all primitives from the dataset and resets the currently selected primitives
1125     * to the empty collection. Also notifies selection change listeners if necessary.
1126     * @throws IllegalStateException if the dataset is read-only
1127     */
1128    @Override
1129    public void clear() {
1130        //TODO: Why can't we clear a dataset that is locked?
1131        //TODO: Report listeners that are still active (should be none)
1132        checkModifiable();
1133        update(() -> {
1134            clearSelection();
1135            clearSelectionHistory();
1136            for (OsmPrimitive primitive : allPrimitives) {
1137                primitive.setDataset(null);
1138            }
1139            store.clear();
1140            allPrimitives.clear();
1141            conflicts.get().clear();
1142        });
1143    }
1144
1145    /**
1146     * Marks all "invisible" objects as deleted. These objects should be always marked as
1147     * deleted when downloaded from the server. They can be undeleted later if necessary.
1148     * @throws IllegalStateException if the dataset is read-only
1149     */
1150    public void deleteInvisible() {
1151        checkModifiable();
1152        for (OsmPrimitive primitive : allPrimitives) {
1153            if (!primitive.isVisible()) {
1154                primitive.setDeleted(true);
1155            }
1156        }
1157    }
1158
1159    /**
1160     * Moves all primitives and datasources from DataSet "from" to this DataSet.
1161     * @param from The source DataSet
1162     */
1163    public void mergeFrom(DataSet from) {
1164        mergeFrom(from, null);
1165    }
1166
1167    /**
1168     * Moves all primitives and datasources from DataSet "from" to this DataSet.
1169     * @param from The source DataSet
1170     * @param progressMonitor The progress monitor
1171     * @throws IllegalStateException if the dataset is read-only
1172     */
1173    public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1174        if (from != null) {
1175            checkModifiable();
1176            new DataSetMerger(this, from).merge(progressMonitor);
1177            synchronized (from) {
1178                if (!from.dataSources.isEmpty()) {
1179                    DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(
1180                            this, new LinkedHashSet<>(dataSources), from.dataSources.stream());
1181                    DataSourceRemovedEvent clearEvent = new DataSourceRemovedEvent(
1182                            this, new LinkedHashSet<>(from.dataSources), from.dataSources.stream());
1183                    if (from.dataSources.stream().filter(dataSource -> !dataSources.contains(dataSource))
1184                            .anyMatch(dataSources::add)) {
1185                        cachedDataSourceArea = null;
1186                        cachedDataSourceBounds = null;
1187                    }
1188                    from.dataSources.clear();
1189                    from.cachedDataSourceArea = null;
1190                    from.cachedDataSourceBounds = null;
1191                    dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
1192                    from.dataSourceListeners.fireEvent(d -> d.dataSourceChange(clearEvent));
1193                }
1194            }
1195        }
1196    }
1197
1198    /**
1199     * Replies the set of conflicts currently managed in this layer.
1200     *
1201     * @return the set of conflicts currently managed in this layer
1202     * @since 12672
1203     */
1204    public ConflictCollection getConflicts() {
1205        return conflicts;
1206    }
1207
1208    @Override
1209    public String getName() {
1210        return name;
1211    }
1212
1213    @Override
1214    public void setName(String name) {
1215        this.name = name;
1216    }
1217
1218    /* --------------------------------------------------------------------------------- */
1219    /* interface ProjectionChangeListener                                                */
1220    /* --------------------------------------------------------------------------------- */
1221    @Override
1222    public void projectionChanged(Projection oldValue, Projection newValue) {
1223        invalidateEastNorthCache();
1224    }
1225
1226    @Override
1227    public synchronized ProjectionBounds getDataSourceBoundingBox() {
1228        BoundingXYVisitor bbox = new BoundingXYVisitor();
1229        for (DataSource source : dataSources) {
1230            bbox.visit(source.bounds);
1231        }
1232        if (bbox.hasExtend()) {
1233            return bbox.getBounds();
1234        }
1235        return null;
1236    }
1237
1238    /**
1239     * Returns mappaint cache index for this DataSet.
1240     *
1241     * If the {@link OsmPrimitive#mappaintCacheIdx} is not equal to the DataSet mappaint
1242     * cache index, this means the cache for that primitive is out of date.
1243     * @return mappaint cache index
1244     * @since 13420
1245     */
1246    public short getMappaintCacheIndex() {
1247        return mappaintCacheIdx;
1248    }
1249
1250    @Override
1251    public void clearMappaintCache() {
1252        mappaintCacheIdx++;
1253    }
1254
1255    @Override
1256    public void lock() {
1257        if (!isReadOnly.compareAndSet(false, true)) {
1258            Logging.warn("Trying to set readOnly flag on a readOnly dataset ", getName());
1259        }
1260    }
1261
1262    @Override
1263    public void unlock() {
1264        if (!isReadOnly.compareAndSet(true, false)) {
1265            Logging.warn("Trying to unset readOnly flag on a non-readOnly dataset ", getName());
1266        }
1267    }
1268
1269    @Override
1270    public boolean isLocked() {
1271        return isReadOnly.get();
1272    }
1273
1274    /**
1275     * Checks the dataset is modifiable (not read-only).
1276     * @throws IllegalStateException if the dataset is read-only
1277     */
1278    private void checkModifiable() {
1279        if (isLocked()) {
1280            throw new IllegalStateException("DataSet is read-only");
1281        }
1282    }
1283
1284    /**
1285     * Returns an optional remark about this data set (used by Overpass API).
1286     * @return a remark about this data set, or {@code null}
1287     * @since 14219
1288     */
1289    public String getRemark() {
1290        return remark;
1291    }
1292
1293    /**
1294     * Sets an optional remark about this data set (used by Overpass API).
1295     * @param remark a remark about this data set, or {@code null}
1296     * @since 14219
1297     */
1298    public void setRemark(String remark) {
1299        this.remark = remark;
1300    }
1301
1302    /**
1303     * Gets the GPX (XML) namespaces if this DataSet was created from a GPX file
1304     * @return the GPXNamespaces or <code>null</code>
1305     */
1306    public List<XMLNamespace> getGPXNamespaces() {
1307        return gpxNamespaces;
1308    }
1309
1310    /**
1311     * Sets the GPX (XML) namespaces
1312     * @param gpxNamespaces the GPXNamespaces to set
1313     */
1314    public void setGPXNamespaces(List<XMLNamespace> gpxNamespaces) {
1315        this.gpxNamespaces = gpxNamespaces;
1316    }
1317
1318    /**
1319     * Determines if this Dataset contains no primitives.
1320     * @return true if this Dataset contains no primitives
1321     * @since 14835
1322     */
1323    public boolean isEmpty() {
1324        return allPrimitives.isEmpty();
1325    }
1326}