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