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

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

fix #8039, fix #10456: final fixes for the read-only/locked layers:

  • rename "read-only" to "locked" (in XML and Java classes/interfaces)
  • add a new download policy (true/never) to allow private layers forbidding only to download data, but allowing everything else

This leads to:

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