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