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