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

Last change on this file since 13434 was 13434, checked in by Don-vip, 7 months ago

see #8039, see #10456 - support read-only data layers

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