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

Last change on this file since 13666 was 13559, checked in by Don-vip, 6 years ago

extract DownloadPolicy / UploadPolicy to separate classes

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