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