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