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