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