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