001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.geom.Area;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.Collections;
011import java.util.HashMap;
012import java.util.HashSet;
013import java.util.Iterator;
014import java.util.LinkedHashSet;
015import java.util.LinkedList;
016import java.util.List;
017import java.util.Map;
018import java.util.Set;
019import java.util.concurrent.CopyOnWriteArrayList;
020import java.util.concurrent.locks.Lock;
021import java.util.concurrent.locks.ReadWriteLock;
022import java.util.concurrent.locks.ReentrantReadWriteLock;
023
024import org.openstreetmap.josm.Main;
025import org.openstreetmap.josm.data.Bounds;
026import org.openstreetmap.josm.data.Data;
027import org.openstreetmap.josm.data.DataSource;
028import org.openstreetmap.josm.data.ProjectionBounds;
029import org.openstreetmap.josm.data.SelectionChangedListener;
030import org.openstreetmap.josm.data.coor.EastNorth;
031import org.openstreetmap.josm.data.coor.LatLon;
032import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
033import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
034import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
035import org.openstreetmap.josm.data.osm.event.DataSetListener;
036import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
037import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent;
038import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
039import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
040import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
041import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
042import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
043import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
044import org.openstreetmap.josm.data.projection.Projection;
045import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
046import org.openstreetmap.josm.gui.progress.ProgressMonitor;
047import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
048import org.openstreetmap.josm.tools.FilteredCollection;
049import org.openstreetmap.josm.tools.Predicate;
050import org.openstreetmap.josm.tools.Predicates;
051import org.openstreetmap.josm.tools.SubclassFilteredCollection;
052import org.openstreetmap.josm.tools.Utils;
053
054/**
055 * DataSet is the data behind the application. It can consists of only a few points up to the whole
056 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
057 *
058 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
059 * store some information.
060 *
061 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
062 * lead to data corruption or ConccurentModificationException. However when for example one thread
063 * removes primitive and other thread try to add another primitive referring to the removed primitive,
064 * DataIntegrityException will occur.
065 *
066 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
067 * Dataset will not change. Sample usage:
068 * <code>
069 *   ds.getReadLock().lock();
070 *   try {
071 *     // .. do something with dataset
072 *   } finally {
073 *     ds.getReadLock().unlock();
074 *   }
075 * </code>
076 *
077 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
078 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
079 * reasons - GUI can be updated after all changes are done.
080 * Sample usage:
081 * <code>
082 * ds.beginUpdate()
083 * try {
084 *   // .. do modifications
085 * } finally {
086 *  ds.endUpdate();
087 * }
088 * </code>
089 *
090 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
091 * automatically.
092 *
093 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
094 * sample ticket
095 *
096 * @author imi
097 */
098public final class DataSet implements Data, Cloneable, ProjectionChangeListener {
099
100    /**
101     * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
102     */
103    private static final int MAX_SINGLE_EVENTS = 30;
104
105    /**
106     * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
107     */
108    private static final int MAX_EVENTS = 1000;
109
110    private final Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true);
111    private final Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new Storage.PrimitiveIdHash());
112    private final CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>();
113
114    // provide means to highlight map elements that are not osm primitives
115    private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>();
116    private Collection<WaySegment> highlightedWaySegments = new LinkedList<>();
117
118    // Number of open calls to beginUpdate
119    private int updateCount;
120    // Events that occurred while dataset was locked but should be fired after write lock is released
121    private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>();
122
123    private int highlightUpdateCount;
124
125    private boolean uploadDiscouraged;
126
127    private final ReadWriteLock lock = new ReentrantReadWriteLock();
128    private final Object selectionLock = new Object();
129
130    /**
131     * Constructs a new {@code DataSet}.
132     */
133    public DataSet() {
134        // Transparently register as projection change listener. No need to explicitly remove
135        // the listener, projection change listeners are managed as WeakReferences.
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 final 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<? super 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(Predicates.alwaysTrue());
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 = (DataSet) super.clone();
880            Main.addProjectionChangeListener(ds);
881            Map<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>();
882            for (Node n : nodes) {
883                Node newNode = new Node(n);
884                primMap.put(n, newNode);
885                ds.addPrimitive(newNode);
886            }
887            for (Way w : ways) {
888                Way newWay = new Way(w);
889                primMap.put(w, newWay);
890                List<Node> newNodes = new ArrayList<>();
891                for (Node n: w.getNodes()) {
892                    newNodes.add((Node) primMap.get(n));
893                }
894                newWay.setNodes(newNodes);
895                ds.addPrimitive(newWay);
896            }
897            // Because relations can have other relations as members we first clone all relations
898            // and then get the cloned members
899            for (Relation r : relations) {
900                Relation newRelation = new Relation(r, r.isNew());
901                newRelation.setMembers(null);
902                primMap.put(r, newRelation);
903                ds.addPrimitive(newRelation);
904            }
905            for (Relation r : relations) {
906                Relation newRelation = (Relation) primMap.get(r);
907                List<RelationMember> newMembers = new ArrayList<>();
908                for (RelationMember rm: r.getMembers()) {
909                    newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
910                }
911                newRelation.setMembers(newMembers);
912            }
913            for (DataSource source : dataSources) {
914                ds.dataSources.add(new DataSource(source.bounds, source.origin));
915            }
916            ds.version = version;
917            return ds;
918        } catch (CloneNotSupportedException e) {
919            throw new IllegalStateException(e);
920        } finally {
921            getReadLock().unlock();
922        }
923    }
924
925    @Override
926    public Collection<DataSource> getDataSources() {
927        return dataSources;
928    }
929
930    @Override
931    public Area getDataSourceArea() {
932        return DataSource.getDataSourceArea(dataSources);
933    }
934
935    /**
936     * Returns a primitive with a given id from the data set. null, if no such primitive exists
937     *
938     * @param id  uniqueId of the primitive. Might be &lt; 0 for newly created primitives
939     * @param type the type of  the primitive. Must not be null.
940     * @return the primitive
941     * @throws NullPointerException if type is null
942     */
943    public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
944        return getPrimitiveById(new SimplePrimitiveId(id, type));
945    }
946
947    /**
948     * Returns a primitive with a given id from the data set. null, if no such primitive exists
949     *
950     * @param primitiveId type and uniqueId of the primitive. Might be &lt; 0 for newly created primitives
951     * @return the primitive
952     */
953    public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
954        return primitiveId != null ? primitivesMap.get(primitiveId) : null;
955    }
956
957    /**
958     * Show message and stack trace in log in case primitive is not found
959     * @param primitiveId primitive id to look for
960     * @return Primitive by id.
961     */
962    private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
963        OsmPrimitive result = getPrimitiveById(primitiveId);
964        if (result == null && primitiveId != null) {
965            Main.warn(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
966                    + "at {2}. This is not a critical error, it should be safe to continue in your work.",
967                    primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite()));
968            Main.error(new Exception());
969        }
970
971        return result;
972    }
973
974    private static void deleteWay(Way way) {
975        way.setNodes(null);
976        way.setDeleted(true);
977    }
978
979    /**
980     * Removes all references from ways in this dataset to a particular node.
981     *
982     * @param node the node
983     * @return The set of ways that have been modified
984     */
985    public Set<Way> unlinkNodeFromWays(Node node) {
986        Set<Way> result = new HashSet<>();
987        beginUpdate();
988        try {
989            for (Way way: ways) {
990                List<Node> wayNodes = way.getNodes();
991                if (wayNodes.remove(node)) {
992                    if (wayNodes.size() < 2) {
993                        deleteWay(way);
994                    } else {
995                        way.setNodes(wayNodes);
996                    }
997                    result.add(way);
998                }
999            }
1000        } finally {
1001            endUpdate();
1002        }
1003        return result;
1004    }
1005
1006    /**
1007     * removes all references from relations in this dataset  to this primitive
1008     *
1009     * @param primitive the primitive
1010     * @return The set of relations that have been modified
1011     */
1012    public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
1013        Set<Relation> result = new HashSet<>();
1014        beginUpdate();
1015        try {
1016            for (Relation relation : relations) {
1017                List<RelationMember> members = relation.getMembers();
1018
1019                Iterator<RelationMember> it = members.iterator();
1020                boolean removed = false;
1021                while (it.hasNext()) {
1022                    RelationMember member = it.next();
1023                    if (member.getMember().equals(primitive)) {
1024                        it.remove();
1025                        removed = true;
1026                    }
1027                }
1028
1029                if (removed) {
1030                    relation.setMembers(members);
1031                    result.add(relation);
1032                }
1033            }
1034        } finally {
1035            endUpdate();
1036        }
1037        return result;
1038    }
1039
1040    /**
1041     * Removes all references from other primitives to the referenced primitive.
1042     *
1043     * @param referencedPrimitive the referenced primitive
1044     * @return The set of primitives that have been modified
1045     */
1046    public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
1047        Set<OsmPrimitive> result = new HashSet<>();
1048        beginUpdate();
1049        try {
1050            if (referencedPrimitive instanceof Node) {
1051                result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
1052            }
1053            result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
1054        } finally {
1055            endUpdate();
1056        }
1057        return result;
1058    }
1059
1060    /**
1061     * Replies true if there is at least one primitive in this dataset with
1062     * {@link OsmPrimitive#isModified()} == <code>true</code>.
1063     *
1064     * @return true if there is at least one primitive in this dataset with
1065     * {@link OsmPrimitive#isModified()} == <code>true</code>.
1066     */
1067    public boolean isModified() {
1068        for (OsmPrimitive p: allPrimitives) {
1069            if (p.isModified())
1070                return true;
1071        }
1072        return false;
1073    }
1074
1075    private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
1076        if (!nodes.remove(node))
1077            throw new RuntimeException("Reindexing node failed to remove");
1078        node.setCoorInternal(newCoor, eastNorth);
1079        if (!nodes.add(node))
1080            throw new RuntimeException("Reindexing node failed to add");
1081        for (OsmPrimitive primitive: node.getReferrers()) {
1082            if (primitive instanceof Way) {
1083                reindexWay((Way) primitive);
1084            } else {
1085                reindexRelation((Relation) primitive);
1086            }
1087        }
1088    }
1089
1090    private void reindexWay(Way way) {
1091        BBox before = way.getBBox();
1092        if (!ways.remove(way))
1093            throw new RuntimeException("Reindexing way failed to remove");
1094        way.updatePosition();
1095        if (!ways.add(way))
1096            throw new RuntimeException("Reindexing way failed to add");
1097        if (!way.getBBox().equals(before)) {
1098            for (OsmPrimitive primitive: way.getReferrers()) {
1099                reindexRelation((Relation) primitive);
1100            }
1101        }
1102    }
1103
1104    private static void reindexRelation(Relation relation) {
1105        BBox before = relation.getBBox();
1106        relation.updatePosition();
1107        if (!before.equals(relation.getBBox())) {
1108            for (OsmPrimitive primitive: relation.getReferrers()) {
1109                reindexRelation((Relation) primitive);
1110            }
1111        }
1112    }
1113
1114    /**
1115     * Adds a new data set listener.
1116     * @param dsl The data set listener to add
1117     */
1118    public void addDataSetListener(DataSetListener dsl) {
1119        listeners.addIfAbsent(dsl);
1120    }
1121
1122    /**
1123     * Removes a data set listener.
1124     * @param dsl The data set listener to remove
1125     */
1126    public void removeDataSetListener(DataSetListener dsl) {
1127        listeners.remove(dsl);
1128    }
1129
1130    /**
1131     * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
1132     * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
1133     * <br>
1134     * Typical usecase should look like this:
1135     * <pre>
1136     * ds.beginUpdate();
1137     * try {
1138     *   ...
1139     * } finally {
1140     *   ds.endUpdate();
1141     * }
1142     * </pre>
1143     */
1144    public void beginUpdate() {
1145        lock.writeLock().lock();
1146        updateCount++;
1147    }
1148
1149    /**
1150     * @see DataSet#beginUpdate()
1151     */
1152    public void endUpdate() {
1153        if (updateCount > 0) {
1154            updateCount--;
1155            if (updateCount == 0) {
1156                List<AbstractDatasetChangedEvent> eventsCopy = new ArrayList<>(cachedEvents);
1157                cachedEvents.clear();
1158                lock.writeLock().unlock();
1159
1160                if (!eventsCopy.isEmpty()) {
1161                    lock.readLock().lock();
1162                    try {
1163                        if (eventsCopy.size() < MAX_SINGLE_EVENTS) {
1164                            for (AbstractDatasetChangedEvent event: eventsCopy) {
1165                                fireEventToListeners(event);
1166                            }
1167                        } else if (eventsCopy.size() == MAX_EVENTS) {
1168                            fireEventToListeners(new DataChangedEvent(this));
1169                        } else {
1170                            fireEventToListeners(new DataChangedEvent(this, eventsCopy));
1171                        }
1172                    } finally {
1173                        lock.readLock().unlock();
1174                    }
1175                }
1176            } else {
1177                lock.writeLock().unlock();
1178            }
1179
1180        } else
1181            throw new AssertionError("endUpdate called without beginUpdate");
1182    }
1183
1184    private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1185        for (DataSetListener listener: listeners) {
1186            event.fire(listener);
1187        }
1188    }
1189
1190    private void fireEvent(AbstractDatasetChangedEvent event) {
1191        if (updateCount == 0)
1192            throw new AssertionError("dataset events can be fired only when dataset is locked");
1193        if (cachedEvents.size() < MAX_EVENTS) {
1194            cachedEvents.add(event);
1195        }
1196    }
1197
1198    void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1199        fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1200    }
1201
1202    void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1203        fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1204    }
1205
1206    void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1207        fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1208    }
1209
1210    void fireRelationMembersChanged(Relation r) {
1211        reindexRelation(r);
1212        fireEvent(new RelationMembersChangedEvent(this, r));
1213    }
1214
1215    void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1216        reindexNode(node, newCoor, eastNorth);
1217        fireEvent(new NodeMovedEvent(this, node));
1218    }
1219
1220    void fireWayNodesChanged(Way way) {
1221        reindexWay(way);
1222        fireEvent(new WayNodesChangedEvent(this, way));
1223    }
1224
1225    void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1226        fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
1227    }
1228
1229    void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1230        fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1231    }
1232
1233    void fireHighlightingChanged() {
1234        highlightUpdateCount++;
1235    }
1236
1237    /**
1238     * Invalidates the internal cache of projected east/north coordinates.
1239     *
1240     * This method can be invoked after the globally configured projection method
1241     * changed.
1242     */
1243    public void invalidateEastNorthCache() {
1244        if (Main.getProjection() == null) return; // sanity check
1245        try {
1246            beginUpdate();
1247            for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) {
1248                n.invalidateEastNorthCache();
1249            }
1250        } finally {
1251            endUpdate();
1252        }
1253    }
1254
1255    /**
1256     * Cleanups all deleted primitives (really delete them from the dataset).
1257     */
1258    public void cleanupDeletedPrimitives() {
1259        beginUpdate();
1260        try {
1261            boolean changed = cleanupDeleted(nodes.iterator());
1262            if (cleanupDeleted(ways.iterator())) {
1263                changed = true;
1264            }
1265            if (cleanupDeleted(relations.iterator())) {
1266                changed = true;
1267            }
1268            if (changed) {
1269                fireSelectionChanged();
1270            }
1271        } finally {
1272            endUpdate();
1273        }
1274    }
1275
1276    private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
1277        boolean changed = false;
1278        synchronized (selectionLock) {
1279            while (it.hasNext()) {
1280                OsmPrimitive primitive = it.next();
1281                if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) {
1282                    selectedPrimitives.remove(primitive);
1283                    selectionSnapshot = null;
1284                    allPrimitives.remove(primitive);
1285                    primitive.setDataset(null);
1286                    changed = true;
1287                    it.remove();
1288                }
1289            }
1290            if (changed) {
1291                selectionSnapshot = null;
1292            }
1293        }
1294        return changed;
1295    }
1296
1297    /**
1298     * Removes all primitives from the dataset and resets the currently selected primitives
1299     * to the empty collection. Also notifies selection change listeners if necessary.
1300     *
1301     */
1302    public void clear() {
1303        beginUpdate();
1304        try {
1305            clearSelection();
1306            for (OsmPrimitive primitive:allPrimitives) {
1307                primitive.setDataset(null);
1308            }
1309            nodes.clear();
1310            ways.clear();
1311            relations.clear();
1312            allPrimitives.clear();
1313        } finally {
1314            endUpdate();
1315        }
1316    }
1317
1318    /**
1319     * Marks all "invisible" objects as deleted. These objects should be always marked as
1320     * deleted when downloaded from the server. They can be undeleted later if necessary.
1321     *
1322     */
1323    public void deleteInvisible() {
1324        for (OsmPrimitive primitive:allPrimitives) {
1325            if (!primitive.isVisible()) {
1326                primitive.setDeleted(true);
1327            }
1328        }
1329    }
1330
1331    @Override
1332    public List<Bounds> getDataSourceBounds() {
1333        return DataSource.getDataSourceBounds(dataSources);
1334    }
1335
1336    /**
1337     * Moves all primitives and datasources from DataSet "from" to this DataSet.
1338     * @param from The source DataSet
1339     */
1340    public void mergeFrom(DataSet from) {
1341        mergeFrom(from, null);
1342    }
1343
1344    /**
1345     * Moves all primitives and datasources from DataSet "from" to this DataSet.
1346     * @param from The source DataSet
1347     * @param progressMonitor The progress monitor
1348     */
1349    public void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1350        if (from != null) {
1351            new DataSetMerger(this, from).merge(progressMonitor);
1352            dataSources.addAll(from.dataSources);
1353            from.dataSources.clear();
1354        }
1355    }
1356
1357    /* --------------------------------------------------------------------------------- */
1358    /* interface ProjectionChangeListner                                                 */
1359    /* --------------------------------------------------------------------------------- */
1360    @Override
1361    public void projectionChanged(Projection oldValue, Projection newValue) {
1362        invalidateEastNorthCache();
1363    }
1364
1365    public ProjectionBounds getDataSourceBoundingBox() {
1366        BoundingXYVisitor bbox = new BoundingXYVisitor();
1367        for (DataSource source : dataSources) {
1368            bbox.visit(source.bounds);
1369        }
1370        if (bbox.hasExtend()) {
1371            return bbox.getBounds();
1372        }
1373        return null;
1374    }
1375
1376}