source: josm/trunk/src/org/openstreetmap/josm/data/osm/DataSet.java @ 5241

Revision 5122, 40.7 KB checked in by Don-vip, 8 weeks ago (diff)

Add ProgressMonitor to DataSet.mergeFrom()

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.concurrent.CopyOnWriteArrayList;
18import java.util.concurrent.locks.Lock;
19import java.util.concurrent.locks.ReadWriteLock;
20import java.util.concurrent.locks.ReentrantReadWriteLock;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.Bounds;
24import org.openstreetmap.josm.data.SelectionChangedListener;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
28import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
29import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
30import org.openstreetmap.josm.data.osm.event.DataSetListener;
31import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
32import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
33import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
34import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
35import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
36import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
37import org.openstreetmap.josm.data.projection.Projection;
38import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
39import org.openstreetmap.josm.gui.progress.ProgressMonitor;
40import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
41import org.openstreetmap.josm.tools.FilteredCollection;
42import org.openstreetmap.josm.tools.Predicate;
43import org.openstreetmap.josm.tools.SubclassFilteredCollection;
44import org.openstreetmap.josm.tools.Utils;
45
46/**
47 * DataSet is the data behind the application. It can consists of only a few points up to the whole
48 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
49 *
50 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
51 * store some information.
52 *
53 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
54 * lead to data corruption or ConccurentModificationException. However when for example one thread
55 * removes primitive and other thread try to add another primitive reffering to the removed primitive,
56 * DataIntegrityException will occur.
57 *
58 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
59 * Dataset will not change. Sample usage:
60 * <code>
61 *   ds.getReadLock().lock();
62 *   try {
63 *     // .. do something with dataset
64 *   } finally {
65 *     ds.getReadLock().unlock();
66 *   }
67 * </code>
68 *
69 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
70 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
71 * reasons - GUI can be updated after all changes are done.
72 * Sample usage:
73 * <code>
74 * ds.beginUpdate()
75 * try {
76 *   // .. do modifications
77 * } finally {
78 *  ds.endUpdate();
79 * }
80 * </code>
81 *
82 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
83 * automatically.
84 *
85 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
86 * sample ticket
87 *
88 * @author imi
89 */
90public class DataSet implements Cloneable, ProjectionChangeListener {
91
92    /**
93     * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
94     */
95    private static final int MAX_SINGLE_EVENTS = 30;
96
97    /**
98     * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
99     */
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 {@see #nodes}, {@see #ways}, or
382     * {@see #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 {@see SelectionChangedListener} about the current selection in
436     * this dataset.
437     *
438     */
439    public void fireSelectionChanged(){
440        Collection<? extends OsmPrimitive> currentSelection = getSelected();
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. May be empty, but not null.
482     *
483     * @return unmodifiable collection of primitives
484     */
485    public Collection<OsmPrimitive> getSelected() {
486        Collection<OsmPrimitive> currentList;
487        synchronized (selectionLock) {
488            if (selectionSnapshot == null) {
489                selectionSnapshot = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(selectedPrimitives));
490            }
491            currentList = selectionSnapshot;
492        }
493        return currentList;
494    }
495
496    /**
497     * Return selected nodes.
498     */
499    public Collection<Node> getSelectedNodes() {
500        return new SubclassFilteredCollection<OsmPrimitive, Node>(getSelected(), OsmPrimitive.nodePredicate);
501    }
502
503    /**
504     * Return selected ways.
505     */
506    public Collection<Way> getSelectedWays() {
507        return new SubclassFilteredCollection<OsmPrimitive, Way>(getSelected(), OsmPrimitive.wayPredicate);
508    }
509
510    /**
511     * Return selected relations.
512     */
513    public Collection<Relation> getSelectedRelations() {
514        return new SubclassFilteredCollection<OsmPrimitive, Relation>(getSelected(), OsmPrimitive.relationPredicate);
515    }
516
517    /**
518     * @return whether the selection is empty or not
519     */
520    public boolean selectionEmpty() {
521        return selectedPrimitives.isEmpty();
522    }
523
524    public boolean isSelected(OsmPrimitive osm) {
525        return selectedPrimitives.contains(osm);
526    }
527
528    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
529        boolean changed = false;
530        synchronized (selectionLock) {
531            for (PrimitiveId o : osm) {
532                changed = changed | this.__toggleSelected(o);
533            }
534            if (changed) {
535                selectionSnapshot = null;
536            }
537        }
538        if (changed) {
539            fireSelectionChanged();
540        }
541    }
542    public void toggleSelected(PrimitiveId... osm) {
543        toggleSelected(Arrays.asList(osm));
544    }
545    private boolean __toggleSelected(PrimitiveId primitiveId) {
546        OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
547        if (primitive == null)
548            return false;
549        if (!selectedPrimitives.remove(primitive)) {
550            selectedPrimitives.add(primitive);
551        }
552        selectionSnapshot = null;
553        return true;
554    }
555
556    /**
557     * set what virtual nodes should be highlighted. Requires a Collection of
558     * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
559     * otherwise.
560     * @param Collection of waySegments
561     */
562    public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
563        if(highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
564            return;
565
566        highlightedVirtualNodes = waySegments;
567        // can't use fireHighlightingChanged because it requires an OsmPrimitive
568        highlightUpdateCount++;
569    }
570
571    /**
572     * set what virtual ways should be highlighted.
573     * @param Collection of waySegments
574     */
575    public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
576        if(highlightedWaySegments.isEmpty() && waySegments.isEmpty())
577            return;
578
579        highlightedWaySegments = waySegments;
580        // can't use fireHighlightingChanged because it requires an OsmPrimitive
581        highlightUpdateCount++;
582    }
583
584    /**
585     * Sets the current selection to the primitives in <code>selection</code>.
586     * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
587     *
588     * @param selection the selection
589     * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
590     */
591    public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
592        boolean changed;
593        synchronized (selectionLock) {
594            boolean wasEmpty = selectedPrimitives.isEmpty();
595            selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
596            changed = addSelected(selection, false)
597                    || (!wasEmpty && selectedPrimitives.isEmpty());
598            if (changed) {
599                selectionSnapshot = null;
600            }
601        }
602
603        if (changed && fireSelectionChangeEvent) {
604            // If selection is not empty then event was already fired in addSelecteds
605            fireSelectionChanged();
606        }
607    }
608
609    /**
610     * Sets the current selection to the primitives in <code>selection</code>
611     * and notifies all {@see SelectionChangedListener}.
612     *
613     * @param selection the selection
614     */
615    public void setSelected(Collection<? extends PrimitiveId> selection) {
616        setSelected(selection, true /* fire selection change event */);
617    }
618
619    public void setSelected(PrimitiveId... osm) {
620        if (osm.length == 1 && osm[0] == null) {
621            setSelected();
622            return;
623        }
624        List<PrimitiveId> list = Arrays.asList(osm);
625        setSelected(list);
626    }
627
628    /**
629     * Adds   the primitives in <code>selection</code> to the current selection
630     * and notifies all {@see SelectionChangedListener}.
631     *
632     * @param selection the selection
633     */
634    public void addSelected(Collection<? extends PrimitiveId> selection) {
635        addSelected(selection, true /* fire selection change event */);
636    }
637
638    public void addSelected(PrimitiveId... osm) {
639        addSelected(Arrays.asList(osm));
640    }
641
642    /**
643     * Adds the primitives in <code>selection</code> to the current selection.
644     * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
645     *
646     * @param selection the selection
647     * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
648     * @return if the selection was changed in the process
649     */
650    private boolean addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
651        boolean changed = false;
652        synchronized (selectionLock) {
653            for (PrimitiveId id: selection) {
654                OsmPrimitive primitive = getPrimitiveByIdChecked(id);
655                if (primitive != null) {
656                    changed = changed | selectedPrimitives.add(primitive);
657                }
658            }
659            if (changed) {
660                selectionSnapshot = null;
661            }
662        }
663        if (fireSelectionChangeEvent && changed) {
664            fireSelectionChanged();
665        }
666        return changed;
667    }
668
669    /**
670     * clear all highlights of virtual nodes
671     */
672    public void clearHighlightedVirtualNodes() {
673        setHighlightedVirtualNodes(new ArrayList<WaySegment>());
674    }
675
676    /**
677     * clear all highlights of way segments
678     */
679    public void clearHighlightedWaySegments() {
680        setHighlightedWaySegments(new ArrayList<WaySegment>());
681    }
682
683    /**
684     * Remove the selection from every value in the collection.
685     * @param list The collection to remove the selection from.
686     */
687    public void clearSelection(PrimitiveId... osm) {
688        clearSelection(Arrays.asList(osm));
689    }
690    public void clearSelection(Collection<? extends PrimitiveId> list) {
691        boolean changed = false;
692        synchronized (selectionLock) {
693            for (PrimitiveId id:list) {
694                OsmPrimitive primitive = getPrimitiveById(id);
695                if (primitive != null) {
696                    changed = changed | selectedPrimitives.remove(primitive);
697                }
698            }
699            if (changed) {
700                selectionSnapshot = null;
701            }
702        }
703        if (changed) {
704            fireSelectionChanged();
705        }
706    }
707    public void clearSelection() {
708        if (!selectedPrimitives.isEmpty()) {
709            synchronized (selectionLock) {
710                selectedPrimitives.clear();
711                selectionSnapshot = null;
712            }
713            fireSelectionChanged();
714        }
715    }
716
717    @Override public DataSet clone() {
718        getReadLock().lock();
719        try {
720            DataSet ds = new DataSet();
721            HashMap<OsmPrimitive, OsmPrimitive> primMap = new HashMap<OsmPrimitive, OsmPrimitive>();
722            for (Node n : nodes) {
723                Node newNode = new Node(n);
724                primMap.put(n, newNode);
725                ds.addPrimitive(newNode);
726            }
727            for (Way w : ways) {
728                Way newWay = new Way(w);
729                primMap.put(w, newWay);
730                List<Node> newNodes = new ArrayList<Node>();
731                for (Node n: w.getNodes()) {
732                    newNodes.add((Node)primMap.get(n));
733                }
734                newWay.setNodes(newNodes);
735                ds.addPrimitive(newWay);
736            }
737            // Because relations can have other relations as members we first clone all relations
738            // and then get the cloned members
739            for (Relation r : relations) {
740                Relation newRelation = new Relation(r, r.isNew());
741                newRelation.setMembers(null);
742                primMap.put(r, newRelation);
743                ds.addPrimitive(newRelation);
744            }
745            for (Relation r : relations) {
746                Relation newRelation = (Relation)primMap.get(r);
747                List<RelationMember> newMembers = new ArrayList<RelationMember>();
748                for (RelationMember rm: r.getMembers()) {
749                    newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
750                }
751                newRelation.setMembers(newMembers);
752            }
753            for (DataSource source : dataSources) {
754                ds.dataSources.add(new DataSource(source.bounds, source.origin));
755            }
756            ds.version = version;
757            return ds;
758        } finally {
759            getReadLock().unlock();
760        }
761    }
762
763    /**
764     * Returns the total area of downloaded data (the "yellow rectangles").
765     * @return Area object encompassing downloaded data.
766     */
767    public Area getDataSourceArea() {
768        if (dataSources.isEmpty()) return null;
769        Area a = new Area();
770        for (DataSource source : dataSources) {
771            // create area from data bounds
772            a.add(new Area(source.bounds.asRect()));
773        }
774        return a;
775    }
776
777    /**
778     * returns a  primitive with a given id from the data set. null, if no such primitive
779     * exists
780     *
781     * @param id  uniqueId of the primitive. Might be < 0 for newly created primitives
782     * @param type the type of  the primitive. Must not be null.
783     * @return the primitive
784     * @exception NullPointerException thrown, if type is null
785     */
786    public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
787        return getPrimitiveById(new SimplePrimitiveId(id, type));
788    }
789
790    public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
791        return primitivesMap.get(primitiveId);
792    }
793
794
795    /**
796     * Show message and stack trace in log in case primitive is not found
797     * @param primitiveId
798     * @return Primitive by id.
799     */
800    private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
801        OsmPrimitive result = getPrimitiveById(primitiveId);
802        if (result == null) {
803            System.out.println(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
804                    + "at http://josm.openstreetmap.de/. This is not a critical error, it should be safe to continue in your work.",
805                    primitiveId.getType(), Long.toString(primitiveId.getUniqueId())));
806            new Exception().printStackTrace();
807        }
808
809        return result;
810    }
811
812    private void deleteWay(Way way) {
813        way.setNodes(null);
814        way.setDeleted(true);
815    }
816
817    /**
818     * removes all references from ways in this dataset to a particular node
819     *
820     * @param node the node
821     */
822    public void unlinkNodeFromWays(Node node) {
823        beginUpdate();
824        try {
825            for (Way way: ways) {
826                List<Node> wayNodes = way.getNodes();
827                if (wayNodes.remove(node)) {
828                    if (wayNodes.size() < 2) {
829                        deleteWay(way);
830                    } else {
831                        way.setNodes(wayNodes);
832                    }
833                }
834            }
835        } finally {
836            endUpdate();
837        }
838    }
839
840    /**
841     * removes all references from relations in this dataset  to this primitive
842     *
843     * @param primitive the primitive
844     */
845    public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
846        beginUpdate();
847        try {
848            for (Relation relation : relations) {
849                List<RelationMember> members = relation.getMembers();
850
851                Iterator<RelationMember> it = members.iterator();
852                boolean removed = false;
853                while(it.hasNext()) {
854                    RelationMember member = it.next();
855                    if (member.getMember().equals(primitive)) {
856                        it.remove();
857                        removed = true;
858                    }
859                }
860
861                if (removed) {
862                    relation.setMembers(members);
863                }
864            }
865        } finally {
866            endUpdate();
867        }
868    }
869
870    /**
871     * removes all references from other primitives to the
872     * referenced primitive
873     *
874     * @param referencedPrimitive the referenced primitive
875     */
876    public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
877        beginUpdate();
878        try {
879            if (referencedPrimitive instanceof Node) {
880                unlinkNodeFromWays((Node)referencedPrimitive);
881                unlinkPrimitiveFromRelations(referencedPrimitive);
882            } else {
883                unlinkPrimitiveFromRelations(referencedPrimitive);
884            }
885        } finally {
886            endUpdate();
887        }
888    }
889
890    /**
891     * Replies true if there is at least one primitive in this dataset with
892     * {@see OsmPrimitive#isModified()} == <code>true</code>.
893     *
894     * @return true if there is at least one primitive in this dataset with
895     * {@see OsmPrimitive#isModified()} == <code>true</code>.
896     */
897    public boolean isModified() {
898        for (OsmPrimitive p: allPrimitives) {
899            if (p.isModified())
900                return true;
901        }
902        return false;
903    }
904
905    private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
906        if (!nodes.remove(node))
907            throw new RuntimeException("Reindexing node failed to remove");
908        node.setCoorInternal(newCoor, eastNorth);
909        if (!nodes.add(node))
910            throw new RuntimeException("Reindexing node failed to add");
911        for (OsmPrimitive primitive: node.getReferrers()) {
912            if (primitive instanceof Way) {
913                reindexWay((Way)primitive);
914            } else {
915                reindexRelation((Relation) primitive);
916            }
917        }
918    }
919
920    private void reindexWay(Way way) {
921        BBox before = way.getBBox();
922        if (!ways.remove(way))
923            throw new RuntimeException("Reindexing way failed to remove");
924        way.updatePosition();
925        if (!ways.add(way))
926            throw new RuntimeException("Reindexing way failed to add");
927        if (!way.getBBox().equals(before)) {
928            for (OsmPrimitive primitive: way.getReferrers()) {
929                reindexRelation((Relation)primitive);
930            }
931        }
932    }
933
934    private void reindexRelation(Relation relation) {
935        BBox before = relation.getBBox();
936        relation.updatePosition();
937        if (!before.equals(relation.getBBox())) {
938            for (OsmPrimitive primitive: relation.getReferrers()) {
939                reindexRelation((Relation) primitive);
940            }
941        }
942    }
943
944    public void addDataSetListener(DataSetListener dsl) {
945        listeners.addIfAbsent(dsl);
946    }
947
948    public void removeDataSetListener(DataSetListener dsl) {
949        listeners.remove(dsl);
950    }
951
952    /**
953     * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
954     * {@link DataSetListener#dataChanged()} event is triggered after end of changes
955     * <br>
956     * Typical usecase should look like this:
957     * <pre>
958     * ds.beginUpdate();
959     * try {
960     *   ...
961     * } finally {
962     *   ds.endUpdate();
963     * }
964     * </pre>
965     */
966    public void beginUpdate() {
967        lock.writeLock().lock();
968        updateCount++;
969    }
970
971    /**
972     * @see DataSet#beginUpdate()
973     */
974    public void endUpdate() {
975        if (updateCount > 0) {
976            updateCount--;
977            if (updateCount == 0) {
978                List<AbstractDatasetChangedEvent> eventsCopy = new ArrayList<AbstractDatasetChangedEvent>(cachedEvents);
979                cachedEvents.clear();
980                lock.writeLock().unlock();
981
982                if (!eventsCopy.isEmpty()) {
983                    lock.readLock().lock();
984                    try {
985                        if (eventsCopy.size() < MAX_SINGLE_EVENTS) {
986                            for (AbstractDatasetChangedEvent event: eventsCopy) {
987                                fireEventToListeners(event);
988                            }
989                        } else if (eventsCopy.size() == MAX_EVENTS) {
990                            fireEventToListeners(new DataChangedEvent(this));
991                        } else {
992                            fireEventToListeners(new DataChangedEvent(this, eventsCopy));
993                        }
994                    } finally {
995                        lock.readLock().unlock();
996                    }
997                }
998            } else {
999                lock.writeLock().unlock();
1000            }
1001
1002        } else
1003            throw new AssertionError("endUpdate called without beginUpdate");
1004    }
1005
1006    private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1007        for (DataSetListener listener: listeners) {
1008            event.fire(listener);
1009        }
1010    }
1011
1012    private void fireEvent(AbstractDatasetChangedEvent event) {
1013        if (updateCount == 0)
1014            throw new AssertionError("dataset events can be fired only when dataset is locked");
1015        if (cachedEvents.size() < MAX_EVENTS) {
1016            cachedEvents.add(event);
1017        }
1018    }
1019
1020    void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1021        fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1022    }
1023
1024    void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1025        fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1026    }
1027
1028    void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1029        fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1030    }
1031
1032    void fireRelationMembersChanged(Relation r) {
1033        reindexRelation(r);
1034        fireEvent(new RelationMembersChangedEvent(this, r));
1035    }
1036
1037    void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1038        reindexNode(node, newCoor, eastNorth);
1039        fireEvent(new NodeMovedEvent(this, node));
1040    }
1041
1042    void fireWayNodesChanged(Way way) {
1043        reindexWay(way);
1044        fireEvent(new WayNodesChangedEvent(this, way));
1045    }
1046
1047    void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1048        fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
1049    }
1050
1051    void fireHighlightingChanged(OsmPrimitive primitive) {
1052        highlightUpdateCount++;
1053    }
1054
1055    /**
1056     * Invalidates the internal cache of projected east/north coordinates.
1057     *
1058     * This method can be invoked after the globally configured projection method
1059     * changed. In contrast to {@link DataSet#reproject()} it only invalidates the
1060     * cache and doesn't reproject the coordinates.
1061     */
1062    public void invalidateEastNorthCache() {
1063        if (Main.getProjection() == null) return; // sanity check
1064        try {
1065            beginUpdate();
1066            for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) {
1067                n.invalidateEastNorthCache();
1068            }
1069        } finally {
1070            endUpdate();
1071        }
1072    }
1073
1074    public void cleanupDeletedPrimitives() {
1075        beginUpdate();
1076        try {
1077            if (cleanupDeleted(nodes.iterator())
1078                    | cleanupDeleted(ways.iterator())
1079                    | cleanupDeleted(relations.iterator())) {
1080                fireSelectionChanged();
1081            }
1082        } finally {
1083            endUpdate();
1084        }
1085    }
1086
1087    private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
1088        boolean changed = false;
1089        synchronized (selectionLock) {
1090            while (it.hasNext()) {
1091                OsmPrimitive primitive = it.next();
1092                if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) {
1093                    selectedPrimitives.remove(primitive);
1094                    selectionSnapshot = null;
1095                    allPrimitives.remove(primitive);
1096                    primitive.setDataset(null);
1097                    changed = true;
1098                    it.remove();
1099                }
1100            }
1101            if (changed) {
1102                selectionSnapshot = null;
1103            }
1104        }
1105        return changed;
1106    }
1107
1108    /**
1109     * Removes all primitives from the dataset and resets the currently selected primitives
1110     * to the empty collection. Also notifies selection change listeners if necessary.
1111     *
1112     */
1113    public void clear() {
1114        beginUpdate();
1115        try {
1116            clearSelection();
1117            for (OsmPrimitive primitive:allPrimitives) {
1118                primitive.setDataset(null);
1119            }
1120            nodes.clear();
1121            ways.clear();
1122            relations.clear();
1123            allPrimitives.clear();
1124        } finally {
1125            endUpdate();
1126        }
1127    }
1128
1129    /**
1130     * Marks all "invisible" objects as deleted. These objects should be always marked as
1131     * deleted when downloaded from the server. They can be undeleted later if necessary.
1132     *
1133     */
1134    public void deleteInvisible() {
1135        for (OsmPrimitive primitive:allPrimitives) {
1136            if (!primitive.isVisible()) {
1137                primitive.setDeleted(true);
1138            }
1139        }
1140    }
1141
1142    /**
1143     * <p>Replies the list of data source bounds.</p>
1144     *
1145     * <p>Dataset maintains a list of data sources which have been merged into the
1146     * data set. Each of these sources can optionally declare a bounding box of the
1147     * data it supplied to the dataset.</p>
1148     *
1149     * <p>This method replies the list of defined (non {@code null}) bounding boxes.</p>
1150     *
1151     * @return the list of data source bounds. An empty list, if no non-null data source
1152     * bounds are defined.
1153     */
1154    public List<Bounds> getDataSourceBounds() {
1155        List<Bounds> ret = new ArrayList<Bounds>(dataSources.size());
1156        for (DataSource ds : dataSources) {
1157            if (ds.bounds != null) {
1158                ret.add(ds.bounds);
1159            }
1160        }
1161        return ret;
1162    }
1163
1164    /**
1165     * Moves all primitives and datasources from DataSet "from" to this DataSet
1166     * @param from The source DataSet
1167     */
1168    public void mergeFrom(DataSet from) {
1169        mergeFrom(from, null);
1170    }
1171   
1172    /**
1173     * Moves all primitives and datasources from DataSet "from" to this DataSet
1174     * @param from The source DataSet
1175     */
1176    public void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1177        if (from != null) {
1178            new DataSetMerger(this, from).merge(progressMonitor);
1179            dataSources.addAll(from.dataSources);
1180            from.dataSources.clear();
1181        }
1182    }
1183
1184    /* --------------------------------------------------------------------------------- */
1185    /* interface ProjectionChangeListner                                                 */
1186    /* --------------------------------------------------------------------------------- */
1187    @Override
1188    public void projectionChanged(Projection oldValue, Projection newValue) {
1189        invalidateEastNorthCache();
1190    }
1191}
Note: See TracBrowser for help on using the repository browser.