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

Last change on this file since 12048 was 12048, checked in by michael2402, 18 months ago

Add per-layer selection listeners. Make selection listener code more generic.

  • Property svn:eol-style set to native
File size: 51.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.Collection;
9import java.util.Collections;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Objects;
17import java.util.Set;
18import java.util.concurrent.CopyOnWriteArrayList;
19import java.util.concurrent.locks.Lock;
20import java.util.concurrent.locks.ReadWriteLock;
21import java.util.concurrent.locks.ReentrantReadWriteLock;
22import java.util.function.Function;
23import java.util.function.Predicate;
24import java.util.stream.Collectors;
25import java.util.stream.Stream;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.Bounds;
29import org.openstreetmap.josm.data.Data;
30import org.openstreetmap.josm.data.DataSource;
31import org.openstreetmap.josm.data.ProjectionBounds;
32import org.openstreetmap.josm.data.SelectionChangedListener;
33import org.openstreetmap.josm.data.coor.EastNorth;
34import org.openstreetmap.josm.data.coor.LatLon;
35import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent;
36import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent;
37import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent;
38import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent;
39import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent;
40import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
41import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
42import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
43import org.openstreetmap.josm.data.osm.event.DataSetListener;
44import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
45import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent;
46import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
47import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
48import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
49import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
50import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
51import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
52import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
53import org.openstreetmap.josm.data.projection.Projection;
54import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
55import org.openstreetmap.josm.gui.progress.ProgressMonitor;
56import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
57import org.openstreetmap.josm.tools.JosmRuntimeException;
58import org.openstreetmap.josm.tools.ListenerList;
59import org.openstreetmap.josm.tools.SubclassFilteredCollection;
60import org.openstreetmap.josm.tools.Utils;
61
62/**
63 * DataSet is the data behind the application. It can consists of only a few points up to the whole
64 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
65 *
66 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
67 * store some information.
68 *
69 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
70 * lead to data corruption or ConcurrentModificationException. However when for example one thread
71 * removes primitive and other thread try to add another primitive referring to the removed primitive,
72 * DataIntegrityException will occur.
73 *
74 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
75 * Dataset will not change. Sample usage:
76 * <code>
77 *   ds.getReadLock().lock();
78 *   try {
79 *     // .. do something with dataset
80 *   } finally {
81 *     ds.getReadLock().unlock();
82 *   }
83 * </code>
84 *
85 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
86 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
87 * reasons - GUI can be updated after all changes are done.
88 * Sample usage:
89 * <code>
90 * ds.beginUpdate()
91 * try {
92 *   // .. do modifications
93 * } finally {
94 *  ds.endUpdate();
95 * }
96 * </code>
97 *
98 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
99 * automatically.
100 *
101 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
102 * sample ticket
103 *
104 * @author imi
105 */
106public final class DataSet implements Data, ProjectionChangeListener {
107
108    /**
109     * Upload policy.
110     *
111     * Determines if upload to the OSM server is intended, discouraged, or
112     * disabled / blocked.
113     */
114    public enum UploadPolicy {
115        /**
116         * Normal dataset, upload intended.
117         */
118        NORMAL("true"),
119        /**
120         * Upload discouraged, for example when using or distributing a private dataset.
121         */
122        DISCOURAGED("false"),
123        /**
124         * Upload blocked.
125         * Upload options completely disabled. Intended for special cases
126         * where a warning dialog is not enough, see #12731.
127         *
128         * For the user, it shouldn't be too easy to disable this flag.
129         */
130        BLOCKED("never");
131
132        final String xmlFlag;
133
134        UploadPolicy(String xmlFlag) {
135            this.xmlFlag = xmlFlag;
136        }
137
138        /**
139         * Get the corresponding value of the <code>upload='...'</code> XML-attribute
140         * in the .osm file.
141         * @return value of the <code>upload</code> attribute
142         */
143        public String getXmlFlag() {
144            return xmlFlag;
145        }
146    }
147
148    /**
149     * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
150     */
151    private static final int MAX_SINGLE_EVENTS = 30;
152
153    /**
154     * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
155     */
156    private static final int MAX_EVENTS = 1000;
157
158    private final Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true);
159    private final Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new Storage.PrimitiveIdHash());
160    private final CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>();
161
162    // provide means to highlight map elements that are not osm primitives
163    private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>();
164    private Collection<WaySegment> highlightedWaySegments = new LinkedList<>();
165    private final ListenerList<HighlightUpdateListener> highlightUpdateListeners = ListenerList.create();
166
167    // Number of open calls to beginUpdate
168    private int updateCount;
169    // Events that occurred while dataset was locked but should be fired after write lock is released
170    private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>();
171
172    private UploadPolicy uploadPolicy;
173
174    private final ReadWriteLock lock = new ReentrantReadWriteLock();
175
176    /**
177     * The mutex lock that is used to synchronize selection changes.
178     */
179    private final Object selectionLock = new Object();
180    /**
181     * The current selected primitives. This is always a unmodifiable set.
182     */
183    private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet();
184
185    /**
186     * A list of listeners that listen to selection changes on this layer.
187     */
188    private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
189
190    private Area cachedDataSourceArea;
191    private List<Bounds> cachedDataSourceBounds;
192
193    /**
194     * All data sources of this DataSet.
195     */
196    private final Collection<DataSource> dataSources = new LinkedList<>();
197
198    /**
199     * Constructs a new {@code DataSet}.
200     */
201    public DataSet() {
202        // Transparently register as projection change listener. No need to explicitly remove
203        // the listener, projection change listeners are managed as WeakReferences.
204        Main.addProjectionChangeListener(this);
205        addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection()));
206    }
207
208    /**
209     * Creates a new {@link DataSet}.
210     * @param copyFrom An other {@link DataSet} to copy the contents of this dataset from.
211     * @since 10346
212     */
213    public DataSet(DataSet copyFrom) {
214        this();
215        copyFrom.getReadLock().lock();
216        try {
217            Map<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>();
218            for (Node n : copyFrom.nodes) {
219                Node newNode = new Node(n);
220                primMap.put(n, newNode);
221                addPrimitive(newNode);
222            }
223            for (Way w : copyFrom.ways) {
224                Way newWay = new Way(w);
225                primMap.put(w, newWay);
226                List<Node> newNodes = new ArrayList<>();
227                for (Node n: w.getNodes()) {
228                    newNodes.add((Node) primMap.get(n));
229                }
230                newWay.setNodes(newNodes);
231                addPrimitive(newWay);
232            }
233            // Because relations can have other relations as members we first clone all relations
234            // and then get the cloned members
235            for (Relation r : copyFrom.relations) {
236                Relation newRelation = new Relation(r, r.isNew());
237                newRelation.setMembers(null);
238                primMap.put(r, newRelation);
239                addPrimitive(newRelation);
240            }
241            for (Relation r : copyFrom.relations) {
242                Relation newRelation = (Relation) primMap.get(r);
243                List<RelationMember> newMembers = new ArrayList<>();
244                for (RelationMember rm: r.getMembers()) {
245                    newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
246                }
247                newRelation.setMembers(newMembers);
248            }
249            for (DataSource source : copyFrom.dataSources) {
250                dataSources.add(new DataSource(source));
251            }
252            version = copyFrom.version;
253        } finally {
254            copyFrom.getReadLock().unlock();
255        }
256    }
257
258    /**
259     * Adds a new data source.
260     * @param source data source to add
261     * @return {@code true} if the collection changed as a result of the call
262     * @since 11626
263     */
264    public synchronized boolean addDataSource(DataSource source) {
265        return addDataSources(Collections.singleton(source));
266    }
267
268    /**
269     * Adds new data sources.
270     * @param sources data sources to add
271     * @return {@code true} if the collection changed as a result of the call
272     * @since 11626
273     */
274    public synchronized boolean addDataSources(Collection<DataSource> sources) {
275        boolean changed = dataSources.addAll(sources);
276        if (changed) {
277            cachedDataSourceArea = null;
278            cachedDataSourceBounds = null;
279        }
280        return changed;
281    }
282
283    /**
284     * Returns the lock used for reading.
285     * @return the lock used for reading
286     */
287    public Lock getReadLock() {
288        return lock.readLock();
289    }
290
291    /**
292     * History of selections - shared by plugins and SelectionListDialog
293     */
294    private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>();
295
296    /**
297     * Replies the history of JOSM selections
298     *
299     * @return list of history entries
300     */
301    public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() {
302        return selectionHistory;
303    }
304
305    /**
306     * Clears selection history list
307     */
308    public void clearSelectionHistory() {
309        selectionHistory.clear();
310    }
311
312    /**
313     * Maintains a list of used tags for autocompletion.
314     */
315    private AutoCompletionManager autocomplete;
316
317    /**
318     * Returns the autocompletion manager, which maintains a list of used tags for autocompletion.
319     * @return the autocompletion manager
320     */
321    public AutoCompletionManager getAutoCompletionManager() {
322        if (autocomplete == null) {
323            autocomplete = new AutoCompletionManager(this);
324            addDataSetListener(autocomplete);
325        }
326        return autocomplete;
327    }
328
329    /**
330     * The API version that created this data set, if any.
331     */
332    private String version;
333
334    /**
335     * Replies the API version this dataset was created from. May be null.
336     *
337     * @return the API version this dataset was created from. May be null.
338     */
339    public String getVersion() {
340        return version;
341    }
342
343    /**
344     * Sets the API version this dataset was created from.
345     *
346     * @param version the API version, i.e. "0.6"
347     */
348    public void setVersion(String version) {
349        this.version = version;
350    }
351
352    /**
353     * Determines if upload is being discouraged.
354     * (i.e. this dataset contains private data which should not be uploaded)
355     * @return {@code true} if upload is being discouraged, {@code false} otherwise
356     * @see #setUploadDiscouraged
357     * @deprecated use {@link #getUploadPolicy()}
358     */
359    @Deprecated
360    public boolean isUploadDiscouraged() {
361        return uploadPolicy == UploadPolicy.DISCOURAGED || uploadPolicy == UploadPolicy.BLOCKED;
362    }
363
364    /**
365     * Sets the "upload discouraged" flag.
366     * @param uploadDiscouraged {@code true} if this dataset contains private data which should not be uploaded
367     * @see #isUploadDiscouraged
368     * @deprecated use {@link #setUploadPolicy(UploadPolicy)}
369     */
370    @Deprecated
371    public void setUploadDiscouraged(boolean uploadDiscouraged) {
372        if (uploadPolicy != UploadPolicy.BLOCKED) {
373            this.uploadPolicy = uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL;
374        }
375    }
376
377    /**
378     * Get the upload policy.
379     * @return the upload policy
380     * @see #setUploadPolicy(UploadPolicy)
381     */
382    public UploadPolicy getUploadPolicy() {
383        return this.uploadPolicy;
384    }
385
386    /**
387     * Sets the upload policy.
388     * @param uploadPolicy the upload policy
389     * @see #getUploadPolicy()
390     */
391    public void setUploadPolicy(UploadPolicy uploadPolicy) {
392        this.uploadPolicy = uploadPolicy;
393    }
394
395    /**
396     * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
397     */
398    private final Map<String, String> changeSetTags = new HashMap<>();
399
400    /**
401     * Replies the set of changeset tags to be applied when or if this is ever uploaded.
402     * @return the set of changeset tags
403     * @see #addChangeSetTag
404     */
405    public Map<String, String> getChangeSetTags() {
406        return changeSetTags;
407    }
408
409    /**
410     * Adds a new changeset tag.
411     * @param k Key
412     * @param v Value
413     * @see #getChangeSetTags
414     */
415    public void addChangeSetTag(String k, String v) {
416        this.changeSetTags.put(k, v);
417    }
418
419    /**
420     * All nodes goes here, even when included in other data (ways etc). This enables the instant
421     * conversion of the whole DataSet by iterating over this data structure.
422     */
423    private final QuadBuckets<Node> nodes = new QuadBuckets<>();
424
425    /**
426     * Gets a filtered collection of primitives matching the given predicate.
427     * @param <T> The primitive type.
428     * @param predicate The predicate to match
429     * @return The list of primtives.
430     * @since 10590
431     */
432    public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) {
433        return new SubclassFilteredCollection<>(allPrimitives, predicate);
434    }
435
436    /**
437     * Replies an unmodifiable collection of nodes in this dataset
438     *
439     * @return an unmodifiable collection of nodes in this dataset
440     */
441    public Collection<Node> getNodes() {
442        return getPrimitives(Node.class::isInstance);
443    }
444
445    /**
446     * Searches for nodes in the given bounding box.
447     * @param bbox the bounding box
448     * @return List of nodes in the given bbox. Can be empty but not null
449     */
450    public List<Node> searchNodes(BBox bbox) {
451        lock.readLock().lock();
452        try {
453            return nodes.search(bbox);
454        } finally {
455            lock.readLock().unlock();
456        }
457    }
458
459    /**
460     * Determines if the given node can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
461     * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
462     *
463     * @param n The node to search
464     * @return {@code true} if {@code n} ban be retrieved in this data set, {@code false} otherwise
465     * @since 7501
466     */
467    public boolean containsNode(Node n) {
468        return nodes.contains(n);
469    }
470
471    /**
472     * All ways (Streets etc.) in the DataSet.
473     *
474     * The way nodes are stored only in the way list.
475     */
476    private final QuadBuckets<Way> ways = new QuadBuckets<>();
477
478    /**
479     * Replies an unmodifiable collection of ways in this dataset
480     *
481     * @return an unmodifiable collection of ways in this dataset
482     */
483    public Collection<Way> getWays() {
484        return getPrimitives(Way.class::isInstance);
485    }
486
487    /**
488     * Searches for ways in the given bounding box.
489     * @param bbox the bounding box
490     * @return List of ways in the given bbox. Can be empty but not null
491     */
492    public List<Way> searchWays(BBox bbox) {
493        lock.readLock().lock();
494        try {
495            return ways.search(bbox);
496        } finally {
497            lock.readLock().unlock();
498        }
499    }
500
501    /**
502     * Determines if the given way can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
503     * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
504     *
505     * @param w The way to search
506     * @return {@code true} if {@code w} ban be retrieved in this data set, {@code false} otherwise
507     * @since 7501
508     */
509    public boolean containsWay(Way w) {
510        return ways.contains(w);
511    }
512
513    /**
514     * All relations/relationships
515     */
516    private final Collection<Relation> relations = new ArrayList<>();
517
518    /**
519     * Replies an unmodifiable collection of relations in this dataset
520     *
521     * @return an unmodifiable collection of relations in this dataset
522     */
523    public Collection<Relation> getRelations() {
524        return getPrimitives(Relation.class::isInstance);
525    }
526
527    /**
528     * Searches for relations in the given bounding box.
529     * @param bbox the bounding box
530     * @return List of relations in the given bbox. Can be empty but not null
531     */
532    public List<Relation> searchRelations(BBox bbox) {
533        lock.readLock().lock();
534        try {
535            // QuadBuckets might be useful here (don't forget to do reindexing after some of rm is changed)
536            return relations.stream()
537                    .filter(r -> r.getBBox().intersects(bbox))
538                    .collect(Collectors.toList());
539        } finally {
540            lock.readLock().unlock();
541        }
542    }
543
544    /**
545     * Determines if the given relation can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
546     * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
547     *
548     * @param r The relation to search
549     * @return {@code true} if {@code r} ban be retrieved in this data set, {@code false} otherwise
550     * @since 7501
551     */
552    public boolean containsRelation(Relation r) {
553        return relations.contains(r);
554    }
555
556    /**
557     * Returns a collection containing all primitives of the dataset.
558     * @return A collection containing all primitives of the dataset. Data is not ordered
559     */
560    public Collection<OsmPrimitive> allPrimitives() {
561        return getPrimitives(o -> true);
562    }
563
564    /**
565     * Returns a collection containing all not-deleted primitives.
566     * @return A collection containing all not-deleted primitives.
567     * @see OsmPrimitive#isDeleted
568     */
569    public Collection<OsmPrimitive> allNonDeletedPrimitives() {
570        return getPrimitives(p -> !p.isDeleted());
571    }
572
573    /**
574     * Returns a collection containing all not-deleted complete primitives.
575     * @return A collection containing all not-deleted complete primitives.
576     * @see OsmPrimitive#isDeleted
577     * @see OsmPrimitive#isIncomplete
578     */
579    public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() {
580        return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete());
581    }
582
583    /**
584     * Returns a collection containing all not-deleted complete physical primitives.
585     * @return A collection containing all not-deleted complete physical primitives (nodes and ways).
586     * @see OsmPrimitive#isDeleted
587     * @see OsmPrimitive#isIncomplete
588     */
589    public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() {
590        return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation));
591    }
592
593    /**
594     * Returns a collection containing all modified primitives.
595     * @return A collection containing all modified primitives.
596     * @see OsmPrimitive#isModified
597     */
598    public Collection<OsmPrimitive> allModifiedPrimitives() {
599        return getPrimitives(OsmPrimitive::isModified);
600    }
601
602    /**
603     * Adds a primitive to the dataset.
604     *
605     * @param primitive the primitive.
606     */
607    public void addPrimitive(OsmPrimitive primitive) {
608        Objects.requireNonNull(primitive, "primitive");
609        beginUpdate();
610        try {
611            if (getPrimitiveById(primitive) != null)
612                throw new DataIntegrityProblemException(
613                        tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()));
614
615            allPrimitives.add(primitive);
616            primitive.setDataset(this);
617            primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly)
618            boolean success = false;
619            if (primitive instanceof Node) {
620                success = nodes.add((Node) primitive);
621            } else if (primitive instanceof Way) {
622                success = ways.add((Way) primitive);
623            } else if (primitive instanceof Relation) {
624                success = relations.add((Relation) primitive);
625            }
626            if (!success)
627                throw new JosmRuntimeException("failed to add primitive: "+primitive);
628            firePrimitivesAdded(Collections.singletonList(primitive), false);
629        } finally {
630            endUpdate();
631        }
632    }
633
634    /**
635     * Removes a primitive from the dataset. This method only removes the
636     * primitive form the respective collection of primitives managed
637     * by this dataset, i.e. from {@link #nodes}, {@link #ways}, or
638     * {@link #relations}. References from other primitives to this
639     * primitive are left unchanged.
640     *
641     * @param primitiveId the id of the primitive
642     */
643    public void removePrimitive(PrimitiveId primitiveId) {
644        beginUpdate();
645        try {
646            OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
647            if (primitive == null)
648                return;
649            boolean success = false;
650            if (primitive instanceof Node) {
651                success = nodes.remove(primitive);
652            } else if (primitive instanceof Way) {
653                success = ways.remove(primitive);
654            } else if (primitive instanceof Relation) {
655                success = relations.remove(primitive);
656            }
657            if (!success)
658                throw new JosmRuntimeException("failed to remove primitive: "+primitive);
659            clearSelection(primitiveId);
660            allPrimitives.remove(primitive);
661            primitive.setDataset(null);
662            firePrimitivesRemoved(Collections.singletonList(primitive), false);
663        } finally {
664            endUpdate();
665        }
666    }
667
668    /*---------------------------------------------------
669     *   SELECTION HANDLING
670     *---------------------------------------------------*/
671
672    /**
673     * Add a listener that listens to selection changes in this specific data set.
674     * @param listener The listener.
675     * @see #removeSelectionListener(DataSelectionListener)
676     * @see SelectionEventManager#addSelectionListener(SelectionChangedListener,
677     *      org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
678     *      To add a global listener.
679     */
680    public void addSelectionListener(DataSelectionListener listener) {
681        selectionListeners.addListener(listener);
682    }
683
684    /**
685     * Remove a listener that listens to selection changes in this specific data set.
686     * @param listener The listener.
687     * @see #addSelectionListener(DataSelectionListener)
688     */
689    public void removeSelectionListener(DataSelectionListener listener) {
690        selectionListeners.removeListener(listener);
691    }
692
693    /*---------------------------------------------------
694     *   OLD SELECTION HANDLING
695     *---------------------------------------------------*/
696
697    /**
698     * A list of listeners to selection changed events. The list is static, as listeners register
699     * themselves for any dataset selection changes that occur, regardless of the current active
700     * dataset. (However, the selection does only change in the active layer)
701     */
702    private static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<>();
703
704    /**
705     * Adds a new selection listener.
706     * @param listener The selection listener to add
707     * @see #addSelectionListener(DataSelectionListener)
708     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
709     */
710    public static void addSelectionListener(SelectionChangedListener listener) {
711        ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener);
712    }
713
714    /**
715     * Removes a selection listener.
716     * @param listener The selection listener to remove
717     * @see #removeSelectionListener(DataSelectionListener)
718     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
719     */
720    public static void removeSelectionListener(SelectionChangedListener listener) {
721        selListeners.remove(listener);
722    }
723
724    /**
725     * Notifies all registered {@link SelectionChangedListener} about the current selection in
726     * this dataset.
727     * @deprecated You should never need to do this from the outside.
728     */
729    @Deprecated
730    public void fireSelectionChanged() {
731        fireDreprecatedSelectionChange(getAllSelected());
732    }
733
734    private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
735        for (SelectionChangedListener l : selListeners) {
736            l.selectionChanged(currentSelection);
737        }
738    }
739
740    /**
741     * Returns selected nodes and ways.
742     * @return selected nodes and ways
743     */
744    public Collection<OsmPrimitive> getSelectedNodesAndWays() {
745        return new SubclassFilteredCollection<>(getSelected(), primitive -> primitive instanceof Node || primitive instanceof Way);
746    }
747
748    /**
749     * Returns an unmodifiable collection of *WaySegments* whose virtual
750     * nodes should be highlighted. WaySegments are used to avoid having
751     * to create a VirtualNode class that wouldn't have much purpose otherwise.
752     *
753     * @return unmodifiable collection of WaySegments
754     */
755    public Collection<WaySegment> getHighlightedVirtualNodes() {
756        return Collections.unmodifiableCollection(highlightedVirtualNodes);
757    }
758
759    /**
760     * Returns an unmodifiable collection of WaySegments that should be highlighted.
761     *
762     * @return unmodifiable collection of WaySegments
763     */
764    public Collection<WaySegment> getHighlightedWaySegments() {
765        return Collections.unmodifiableCollection(highlightedWaySegments);
766    }
767
768    /**
769     * Adds a listener that gets notified whenever way segment / virtual nodes highlights change.
770     * @param listener The Listener
771     * @since 12014
772     */
773    public void addHighlightUpdateListener(HighlightUpdateListener listener) {
774        highlightUpdateListeners.addListener(listener);
775    }
776
777    /**
778     * Removes a listener that was added with {@link #addHighlightUpdateListener(HighlightUpdateListener)}
779     * @param listener The Listener
780     * @since 12014
781     */
782    public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
783        highlightUpdateListeners.removeListener(listener);
784    }
785
786    /**
787     * Replies an unmodifiable collection of primitives currently selected
788     * in this dataset, except deleted ones. May be empty, but not null.
789     *
790     * @return unmodifiable collection of primitives
791     */
792    public Collection<OsmPrimitive> getSelected() {
793        return new SubclassFilteredCollection<>(getAllSelected(), p -> !p.isDeleted());
794    }
795
796    /**
797     * Replies an unmodifiable collection of primitives currently selected
798     * in this dataset, including deleted ones. May be empty, but not null.
799     *
800     * @return unmodifiable collection of primitives
801     */
802    public Collection<OsmPrimitive> getAllSelected() {
803        return currentSelectedPrimitives;
804    }
805
806    /**
807     * Returns selected nodes.
808     * @return selected nodes
809     */
810    public Collection<Node> getSelectedNodes() {
811        return new SubclassFilteredCollection<>(getSelected(), Node.class::isInstance);
812    }
813
814    /**
815     * Returns selected ways.
816     * @return selected ways
817     */
818    public Collection<Way> getSelectedWays() {
819        return new SubclassFilteredCollection<>(getSelected(), Way.class::isInstance);
820    }
821
822    /**
823     * Returns selected relations.
824     * @return selected relations
825     */
826    public Collection<Relation> getSelectedRelations() {
827        return new SubclassFilteredCollection<>(getSelected(), Relation.class::isInstance);
828    }
829
830    /**
831     * Determines whether the selection is empty or not
832     * @return whether the selection is empty or not
833     */
834    public boolean selectionEmpty() {
835        return currentSelectedPrimitives.isEmpty();
836    }
837
838    /**
839     * Determines whether the given primitive is selected or not
840     * @param osm the primitive
841     * @return whether {@code osm} is selected or not
842     */
843    public boolean isSelected(OsmPrimitive osm) {
844        return currentSelectedPrimitives.contains(osm);
845    }
846
847    /**
848     * set what virtual nodes should be highlighted. Requires a Collection of
849     * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
850     * otherwise.
851     * @param waySegments Collection of way segments
852     */
853    public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
854        if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
855            return;
856
857        highlightedVirtualNodes = waySegments;
858        fireHighlightingChanged();
859    }
860
861    /**
862     * set what virtual ways should be highlighted.
863     * @param waySegments Collection of way segments
864     */
865    public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
866        if (highlightedWaySegments.isEmpty() && waySegments.isEmpty())
867            return;
868
869        highlightedWaySegments = waySegments;
870        fireHighlightingChanged();
871    }
872
873    /**
874     * Sets the current selection to the primitives in <code>selection</code>.
875     * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
876     *
877     * @param selection the selection
878     * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
879     * @deprecated Use {@link #setSelected(Collection)} instead. To bee removed end of 2017. Does not seem to be used by plugins.
880     */
881    @Deprecated
882    public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
883        setSelected(selection);
884    }
885
886    /**
887     * Sets the current selection to the primitives in <code>selection</code>
888     * and notifies all {@link SelectionChangedListener}.
889     *
890     * @param selection the selection
891     */
892    public void setSelected(Collection<? extends PrimitiveId> selection) {
893        setSelected(selection.stream());
894    }
895
896    /**
897     * Sets the current selection to the primitives in <code>osm</code>
898     * and notifies all {@link SelectionChangedListener}.
899     *
900     * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
901     */
902    public void setSelected(PrimitiveId... osm) {
903        setSelected(Stream.of(osm).filter(Objects::nonNull));
904    }
905
906    private void setSelected(Stream<? extends PrimitiveId> stream) {
907        doSelectionChange(old -> new SelectionReplaceEvent(this, old,
908                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
909    }
910
911    /**
912     * Adds the primitives in <code>selection</code> to the current selection
913     * and notifies all {@link SelectionChangedListener}.
914     *
915     * @param selection the selection
916     */
917    public void addSelected(Collection<? extends PrimitiveId> selection) {
918        addSelected(selection.stream());
919    }
920
921    /**
922     * Adds the primitives in <code>osm</code> to the current selection
923     * and notifies all {@link SelectionChangedListener}.
924     *
925     * @param osm the primitives to add
926     */
927    public void addSelected(PrimitiveId... osm) {
928        addSelected(Stream.of(osm));
929    }
930
931    private void addSelected(Stream<? extends PrimitiveId> stream) {
932        doSelectionChange(old -> new SelectionAddEvent(this, old,
933                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
934    }
935
936    /**
937     * Removes the selection from every value in the collection.
938     * @param osm The collection of ids to remove the selection from.
939     */
940    public void clearSelection(PrimitiveId... osm) {
941        clearSelection(Stream.of(osm));
942    }
943
944    /**
945     * Removes the selection from every value in the collection.
946     * @param list The collection of ids to remove the selection from.
947     */
948    public void clearSelection(Collection<? extends PrimitiveId> list) {
949        clearSelection(list.stream());
950    }
951
952    /**
953     * Clears the current selection.
954     */
955    public void clearSelection() {
956        setSelected(Stream.empty());
957    }
958
959    private void clearSelection(Stream<? extends PrimitiveId> stream) {
960        doSelectionChange(old -> new SelectionRemoveEvent(this, old,
961                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
962    }
963
964    /**
965     * Toggles the selected state of the given collection of primitives.
966     * @param osm The primitives to toggle
967     */
968    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
969        toggleSelected(osm.stream());
970    }
971
972    /**
973     * Toggles the selected state of the given collection of primitives.
974     * @param osm The primitives to toggle
975     */
976    public void toggleSelected(PrimitiveId... osm) {
977        toggleSelected(Stream.of(osm));
978    }
979
980    private void toggleSelected(Stream<? extends PrimitiveId> stream) {
981        doSelectionChange(old -> new SelectionToggleEvent(this, old,
982                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
983    }
984
985    /**
986     * Do a selection change.
987     * <p>
988     * This is the only method that changes the current selection state.
989     * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
990     * @return true iff the command did change the selection.
991     * @since 12048
992     */
993    private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
994        lock.readLock().lock();
995        try {
996            synchronized (selectionLock) {
997                SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
998                if (event.isNop()) {
999                    return false;
1000                }
1001                currentSelectedPrimitives = event.getSelection();
1002                selectionListeners.fireEvent(l -> l.selectionChanged(event));
1003                return true;
1004            }
1005        } finally {
1006            lock.readLock().unlock();
1007        }
1008    }
1009
1010    /**
1011     * clear all highlights of virtual nodes
1012     */
1013    public void clearHighlightedVirtualNodes() {
1014        setHighlightedVirtualNodes(new ArrayList<WaySegment>());
1015    }
1016
1017    /**
1018     * clear all highlights of way segments
1019     */
1020    public void clearHighlightedWaySegments() {
1021        setHighlightedWaySegments(new ArrayList<WaySegment>());
1022    }
1023
1024    @Override
1025    public synchronized Area getDataSourceArea() {
1026        if (cachedDataSourceArea == null) {
1027            cachedDataSourceArea = Data.super.getDataSourceArea();
1028        }
1029        return cachedDataSourceArea;
1030    }
1031
1032    @Override
1033    public synchronized List<Bounds> getDataSourceBounds() {
1034        if (cachedDataSourceBounds == null) {
1035            cachedDataSourceBounds = Data.super.getDataSourceBounds();
1036        }
1037        return Collections.unmodifiableList(cachedDataSourceBounds);
1038    }
1039
1040    @Override
1041    public synchronized Collection<DataSource> getDataSources() {
1042        return Collections.unmodifiableCollection(dataSources);
1043    }
1044
1045    /**
1046     * Returns a primitive with a given id from the data set. null, if no such primitive exists
1047     *
1048     * @param id  uniqueId of the primitive. Might be &lt; 0 for newly created primitives
1049     * @param type the type of  the primitive. Must not be null.
1050     * @return the primitive
1051     * @throws NullPointerException if type is null
1052     */
1053    public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
1054        return getPrimitiveById(new SimplePrimitiveId(id, type));
1055    }
1056
1057    /**
1058     * Returns a primitive with a given id from the data set. null, if no such primitive exists
1059     *
1060     * @param primitiveId type and uniqueId of the primitive. Might be &lt; 0 for newly created primitives
1061     * @return the primitive
1062     */
1063    public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
1064        return primitiveId != null ? primitivesMap.get(primitiveId) : null;
1065    }
1066
1067    /**
1068     * Show message and stack trace in log in case primitive is not found
1069     * @param primitiveId primitive id to look for
1070     * @return Primitive by id.
1071     */
1072    private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
1073        OsmPrimitive result = getPrimitiveById(primitiveId);
1074        if (result == null && primitiveId != null) {
1075            Main.warn(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
1076                    + "at {2}. This is not a critical error, it should be safe to continue in your work.",
1077                    primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite()));
1078            Main.error(new Exception());
1079        }
1080
1081        return result;
1082    }
1083
1084    private static void deleteWay(Way way) {
1085        way.setNodes(null);
1086        way.setDeleted(true);
1087    }
1088
1089    /**
1090     * Removes all references from ways in this dataset to a particular node.
1091     *
1092     * @param node the node
1093     * @return The set of ways that have been modified
1094     */
1095    public Set<Way> unlinkNodeFromWays(Node node) {
1096        Set<Way> result = new HashSet<>();
1097        beginUpdate();
1098        try {
1099            for (Way way : OsmPrimitive.getFilteredList(node.getReferrers(), Way.class)) {
1100                List<Node> wayNodes = way.getNodes();
1101                if (wayNodes.remove(node)) {
1102                    if (wayNodes.size() < 2) {
1103                        deleteWay(way);
1104                    } else {
1105                        way.setNodes(wayNodes);
1106                    }
1107                    result.add(way);
1108                }
1109            }
1110        } finally {
1111            endUpdate();
1112        }
1113        return result;
1114    }
1115
1116    /**
1117     * removes all references from relations in this dataset  to this primitive
1118     *
1119     * @param primitive the primitive
1120     * @return The set of relations that have been modified
1121     */
1122    public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
1123        Set<Relation> result = new HashSet<>();
1124        beginUpdate();
1125        try {
1126            for (Relation relation : relations) {
1127                List<RelationMember> members = relation.getMembers();
1128
1129                Iterator<RelationMember> it = members.iterator();
1130                boolean removed = false;
1131                while (it.hasNext()) {
1132                    RelationMember member = it.next();
1133                    if (member.getMember().equals(primitive)) {
1134                        it.remove();
1135                        removed = true;
1136                    }
1137                }
1138
1139                if (removed) {
1140                    relation.setMembers(members);
1141                    result.add(relation);
1142                }
1143            }
1144        } finally {
1145            endUpdate();
1146        }
1147        return result;
1148    }
1149
1150    /**
1151     * Removes all references from other primitives to the referenced primitive.
1152     *
1153     * @param referencedPrimitive the referenced primitive
1154     * @return The set of primitives that have been modified
1155     */
1156    public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
1157        Set<OsmPrimitive> result = new HashSet<>();
1158        beginUpdate();
1159        try {
1160            if (referencedPrimitive instanceof Node) {
1161                result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
1162            }
1163            result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
1164        } finally {
1165            endUpdate();
1166        }
1167        return result;
1168    }
1169
1170    /**
1171     * Replies true if there is at least one primitive in this dataset with
1172     * {@link OsmPrimitive#isModified()} == <code>true</code>.
1173     *
1174     * @return true if there is at least one primitive in this dataset with
1175     * {@link OsmPrimitive#isModified()} == <code>true</code>.
1176     */
1177    public boolean isModified() {
1178        for (OsmPrimitive p: allPrimitives) {
1179            if (p.isModified())
1180                return true;
1181        }
1182        return false;
1183    }
1184
1185    private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
1186        if (!nodes.remove(node))
1187            throw new JosmRuntimeException("Reindexing node failed to remove");
1188        node.setCoorInternal(newCoor, eastNorth);
1189        if (!nodes.add(node))
1190            throw new JosmRuntimeException("Reindexing node failed to add");
1191        for (OsmPrimitive primitive: node.getReferrers()) {
1192            if (primitive instanceof Way) {
1193                reindexWay((Way) primitive);
1194            } else {
1195                reindexRelation((Relation) primitive);
1196            }
1197        }
1198    }
1199
1200    private void reindexWay(Way way) {
1201        BBox before = way.getBBox();
1202        if (!ways.remove(way))
1203            throw new JosmRuntimeException("Reindexing way failed to remove");
1204        way.updatePosition();
1205        if (!ways.add(way))
1206            throw new JosmRuntimeException("Reindexing way failed to add");
1207        if (!way.getBBox().equals(before)) {
1208            for (OsmPrimitive primitive: way.getReferrers()) {
1209                reindexRelation((Relation) primitive);
1210            }
1211        }
1212    }
1213
1214    private static void reindexRelation(Relation relation) {
1215        BBox before = relation.getBBox();
1216        relation.updatePosition();
1217        if (!before.equals(relation.getBBox())) {
1218            for (OsmPrimitive primitive: relation.getReferrers()) {
1219                reindexRelation((Relation) primitive);
1220            }
1221        }
1222    }
1223
1224    /**
1225     * Adds a new data set listener.
1226     * @param dsl The data set listener to add
1227     */
1228    public void addDataSetListener(DataSetListener dsl) {
1229        listeners.addIfAbsent(dsl);
1230    }
1231
1232    /**
1233     * Removes a data set listener.
1234     * @param dsl The data set listener to remove
1235     */
1236    public void removeDataSetListener(DataSetListener dsl) {
1237        listeners.remove(dsl);
1238    }
1239
1240    /**
1241     * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
1242     * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
1243     * <br>
1244     * Typical usecase should look like this:
1245     * <pre>
1246     * ds.beginUpdate();
1247     * try {
1248     *   ...
1249     * } finally {
1250     *   ds.endUpdate();
1251     * }
1252     * </pre>
1253     */
1254    public void beginUpdate() {
1255        lock.writeLock().lock();
1256        updateCount++;
1257    }
1258
1259    /**
1260     * @see DataSet#beginUpdate()
1261     */
1262    public void endUpdate() {
1263        if (updateCount > 0) {
1264            updateCount--;
1265            List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList();
1266            if (updateCount == 0) {
1267                eventsToFire = new ArrayList<>(cachedEvents);
1268                cachedEvents.clear();
1269            }
1270
1271            if (!eventsToFire.isEmpty()) {
1272                lock.readLock().lock();
1273                lock.writeLock().unlock();
1274                try {
1275                    if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
1276                        for (AbstractDatasetChangedEvent event: eventsToFire) {
1277                            fireEventToListeners(event);
1278                        }
1279                    } else if (eventsToFire.size() == MAX_EVENTS) {
1280                        fireEventToListeners(new DataChangedEvent(this));
1281                    } else {
1282                        fireEventToListeners(new DataChangedEvent(this, eventsToFire));
1283                    }
1284                } finally {
1285                    lock.readLock().unlock();
1286                }
1287            } else {
1288                lock.writeLock().unlock();
1289            }
1290
1291        } else
1292            throw new AssertionError("endUpdate called without beginUpdate");
1293    }
1294
1295    private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1296        for (DataSetListener listener: listeners) {
1297            event.fire(listener);
1298        }
1299    }
1300
1301    private void fireEvent(AbstractDatasetChangedEvent event) {
1302        if (updateCount == 0)
1303            throw new AssertionError("dataset events can be fired only when dataset is locked");
1304        if (cachedEvents.size() < MAX_EVENTS) {
1305            cachedEvents.add(event);
1306        }
1307    }
1308
1309    void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1310        fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1311    }
1312
1313    void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1314        fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1315    }
1316
1317    void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1318        fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1319    }
1320
1321    void fireRelationMembersChanged(Relation r) {
1322        reindexRelation(r);
1323        fireEvent(new RelationMembersChangedEvent(this, r));
1324    }
1325
1326    void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1327        reindexNode(node, newCoor, eastNorth);
1328        fireEvent(new NodeMovedEvent(this, node));
1329    }
1330
1331    void fireWayNodesChanged(Way way) {
1332        reindexWay(way);
1333        fireEvent(new WayNodesChangedEvent(this, way));
1334    }
1335
1336    void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1337        fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
1338    }
1339
1340    void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1341        fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1342    }
1343
1344    void fireHighlightingChanged() {
1345        HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this);
1346        highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e));
1347    }
1348
1349    /**
1350     * Invalidates the internal cache of projected east/north coordinates.
1351     *
1352     * This method can be invoked after the globally configured projection method
1353     * changed.
1354     */
1355    public void invalidateEastNorthCache() {
1356        if (Main.getProjection() == null) return; // sanity check
1357        try {
1358            beginUpdate();
1359            for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) {
1360                n.invalidateEastNorthCache();
1361            }
1362        } finally {
1363            endUpdate();
1364        }
1365    }
1366
1367    /**
1368     * Cleanups all deleted primitives (really delete them from the dataset).
1369     */
1370    public void cleanupDeletedPrimitives() {
1371        beginUpdate();
1372        try {
1373            cleanupDeleted(Stream.concat(
1374                    nodes.stream(), Stream.concat(ways.stream(), relations.stream())));
1375        } finally {
1376            endUpdate();
1377        }
1378    }
1379
1380    private void cleanupDeleted(Stream<? extends OsmPrimitive> it) {
1381        clearSelection(it
1382                .filter(primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()))
1383                .peek(allPrimitives::remove)
1384                .peek(primitive -> primitive.setDataset(null)));
1385    }
1386
1387    /**
1388     * Removes all primitives from the dataset and resets the currently selected primitives
1389     * to the empty collection. Also notifies selection change listeners if necessary.
1390     *
1391     */
1392    public void clear() {
1393        beginUpdate();
1394        try {
1395            clearSelection();
1396            for (OsmPrimitive primitive:allPrimitives) {
1397                primitive.setDataset(null);
1398            }
1399            nodes.clear();
1400            ways.clear();
1401            relations.clear();
1402            allPrimitives.clear();
1403        } finally {
1404            endUpdate();
1405        }
1406    }
1407
1408    /**
1409     * Marks all "invisible" objects as deleted. These objects should be always marked as
1410     * deleted when downloaded from the server. They can be undeleted later if necessary.
1411     *
1412     */
1413    public void deleteInvisible() {
1414        for (OsmPrimitive primitive:allPrimitives) {
1415            if (!primitive.isVisible()) {
1416                primitive.setDeleted(true);
1417            }
1418        }
1419    }
1420
1421    /**
1422     * Moves all primitives and datasources from DataSet "from" to this DataSet.
1423     * @param from The source DataSet
1424     */
1425    public void mergeFrom(DataSet from) {
1426        mergeFrom(from, null);
1427    }
1428
1429    /**
1430     * Moves all primitives and datasources from DataSet "from" to this DataSet.
1431     * @param from The source DataSet
1432     * @param progressMonitor The progress monitor
1433     */
1434    public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1435        if (from != null) {
1436            new DataSetMerger(this, from).merge(progressMonitor);
1437            synchronized (from) {
1438                if (!from.dataSources.isEmpty()) {
1439                    if (dataSources.addAll(from.dataSources)) {
1440                        cachedDataSourceArea = null;
1441                        cachedDataSourceBounds = null;
1442                    }
1443                    from.dataSources.clear();
1444                    from.cachedDataSourceArea = null;
1445                    from.cachedDataSourceBounds = null;
1446                }
1447            }
1448        }
1449    }
1450
1451    /* --------------------------------------------------------------------------------- */
1452    /* interface ProjectionChangeListner                                                 */
1453    /* --------------------------------------------------------------------------------- */
1454    @Override
1455    public void projectionChanged(Projection oldValue, Projection newValue) {
1456        invalidateEastNorthCache();
1457    }
1458
1459    /**
1460     * Returns the data sources bounding box.
1461     * @return the data sources bounding box
1462     */
1463    public synchronized ProjectionBounds getDataSourceBoundingBox() {
1464        BoundingXYVisitor bbox = new BoundingXYVisitor();
1465        for (DataSource source : dataSources) {
1466            bbox.visit(source.bounds);
1467        }
1468        if (bbox.hasExtend()) {
1469            return bbox.getBounds();
1470        }
1471        return null;
1472    }
1473}
Note: See TracBrowser for help on using the repository browser.