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

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

See #14120: Use a listener to get notified of way segment / virtual node highlight changes.

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