001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.geom.Area; 007import java.util.ArrayList; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.Iterator; 013import java.util.LinkedList; 014import java.util.List; 015import java.util.Map; 016import java.util.Objects; 017import java.util.Set; 018import java.util.concurrent.CopyOnWriteArrayList; 019import java.util.concurrent.atomic.AtomicBoolean; 020import java.util.concurrent.locks.Lock; 021import java.util.concurrent.locks.ReadWriteLock; 022import java.util.concurrent.locks.ReentrantReadWriteLock; 023import java.util.function.Function; 024import java.util.function.Predicate; 025import java.util.stream.Stream; 026 027import org.openstreetmap.josm.Main; 028import org.openstreetmap.josm.data.APIDataSet.APIOperation; 029import org.openstreetmap.josm.data.Bounds; 030import org.openstreetmap.josm.data.Data; 031import org.openstreetmap.josm.data.DataSource; 032import org.openstreetmap.josm.data.ProjectionBounds; 033import org.openstreetmap.josm.data.SelectionChangedListener; 034import org.openstreetmap.josm.data.conflict.ConflictCollection; 035import org.openstreetmap.josm.data.coor.EastNorth; 036import org.openstreetmap.josm.data.coor.LatLon; 037import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent; 038import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent; 039import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent; 040import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent; 041import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent; 042import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 043import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent; 044import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 045import org.openstreetmap.josm.data.osm.event.DataSetListener; 046import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 047import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent; 048import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 049import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 050import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 051import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 052import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 053import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 054import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 055import org.openstreetmap.josm.data.projection.Projection; 056import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 057import org.openstreetmap.josm.gui.progress.ProgressMonitor; 058import org.openstreetmap.josm.tools.ListenerList; 059import org.openstreetmap.josm.tools.Logging; 060import org.openstreetmap.josm.tools.SubclassFilteredCollection; 061 062/** 063 * DataSet is the data behind the application. It can consists of only a few points up to the whole 064 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc. 065 * 066 * Note that DataSet is not an osm-primitive and so has no key association but a few members to 067 * store some information. 068 * 069 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never 070 * lead to data corruption or ConcurrentModificationException. However when for example one thread 071 * removes primitive and other thread try to add another primitive referring to the removed primitive, 072 * DataIntegrityException will occur. 073 * 074 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that 075 * Dataset will not change. Sample usage: 076 * <code> 077 * ds.getReadLock().lock(); 078 * try { 079 * // .. do something with dataset 080 * } finally { 081 * ds.getReadLock().unlock(); 082 * } 083 * </code> 084 * 085 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't 086 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance 087 * reasons - GUI can be updated after all changes are done. 088 * Sample usage: 089 * <code> 090 * ds.beginUpdate() 091 * try { 092 * // .. do modifications 093 * } finally { 094 * ds.endUpdate(); 095 * } 096 * </code> 097 * 098 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked 099 * 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 < 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 < 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}