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