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.DataSource; 031import org.openstreetmap.josm.data.ProjectionBounds; 032import org.openstreetmap.josm.data.SelectionChangedListener; 033import org.openstreetmap.josm.data.conflict.ConflictCollection; 034import org.openstreetmap.josm.data.coor.EastNorth; 035import org.openstreetmap.josm.data.coor.LatLon; 036import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent; 037import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent; 038import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent; 039import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent; 040import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent; 041import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 042import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent; 043import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 044import org.openstreetmap.josm.data.osm.event.DataSetListener; 045import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 046import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent; 047import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 048import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 049import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 050import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 051import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 052import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 053import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 054import org.openstreetmap.josm.data.projection.Projection; 055import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 056import org.openstreetmap.josm.gui.progress.ProgressMonitor; 057import org.openstreetmap.josm.tools.ListenerList; 058import org.openstreetmap.josm.tools.Logging; 059import org.openstreetmap.josm.tools.SubclassFilteredCollection; 060 061/** 062 * DataSet is the data behind the application. It can consists of only a few points up to the whole 063 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc. 064 * 065 * Note that DataSet is not an osm-primitive and so has no key association but a few members to 066 * store some information. 067 * 068 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never 069 * lead to data corruption or ConcurrentModificationException. However when for example one thread 070 * removes primitive and other thread try to add another primitive referring to the removed primitive, 071 * DataIntegrityException will occur. 072 * 073 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that 074 * Dataset will not change. Sample usage: 075 * <code> 076 * ds.getReadLock().lock(); 077 * try { 078 * // .. do something with dataset 079 * } finally { 080 * ds.getReadLock().unlock(); 081 * } 082 * </code> 083 * 084 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't 085 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance 086 * reasons - GUI can be updated after all changes are done. 087 * Sample usage: 088 * <code> 089 * ds.beginUpdate() 090 * try { 091 * // .. do modifications 092 * } finally { 093 * ds.endUpdate(); 094 * } 095 * </code> 096 * 097 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked 098 * automatically. 099 * 100 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for 101 * sample ticket 102 * 103 * @author imi 104 */ 105public final class DataSet implements OsmData<OsmPrimitive, Node, Way, Relation>, ProjectionChangeListener { 106 107 /** 108 * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent) 109 */ 110 private static final int MAX_SINGLE_EVENTS = 30; 111 112 /** 113 * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent) 114 */ 115 private static final int MAX_EVENTS = 1000; 116 117 private final QuadBucketPrimitiveStore<Node, Way, Relation> store = new QuadBucketPrimitiveStore<>(); 118 119 private final Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true); 120 private final Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives 121 .foreignKey(new Storage.PrimitiveIdHash()); 122 private final CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>(); 123 124 // provide means to highlight map elements that are not osm primitives 125 private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>(); 126 private Collection<WaySegment> highlightedWaySegments = new LinkedList<>(); 127 private final ListenerList<HighlightUpdateListener> highlightUpdateListeners = ListenerList.create(); 128 129 // Number of open calls to beginUpdate 130 private int updateCount; 131 // Events that occurred while dataset was locked but should be fired after write lock is released 132 private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>(); 133 134 private String name; 135 private DownloadPolicy downloadPolicy; 136 private UploadPolicy uploadPolicy; 137 /** Flag used to know if the dataset should not be editable */ 138 private final AtomicBoolean isReadOnly = new AtomicBoolean(false); 139 140 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 141 142 /** 143 * The mutex lock that is used to synchronize selection changes. 144 */ 145 private final Object selectionLock = new Object(); 146 /** 147 * The current selected primitives. This is always a unmodifiable set. 148 * 149 * The set should be ordered in the order in which the primitives have been added to the selection. 150 */ 151 private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet(); 152 153 /** 154 * A list of listeners that listen to selection changes on this layer. 155 */ 156 private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create(); 157 158 private Area cachedDataSourceArea; 159 private List<Bounds> cachedDataSourceBounds; 160 161 /** 162 * All data sources of this DataSet. 163 */ 164 private final Collection<DataSource> dataSources = new LinkedList<>(); 165 166 private final ConflictCollection conflicts = new ConflictCollection(); 167 168 private short mappaintCacheIdx = 1; 169 170 /** 171 * Constructs a new {@code DataSet}. 172 */ 173 public DataSet() { 174 // Transparently register as projection change listener. No need to explicitly remove 175 // the listener, projection change listeners are managed as WeakReferences. 176 Main.addProjectionChangeListener(this); 177 addSelectionListener((DataSelectionListener) e -> fireSelectionChange(e.getSelection())); 178 } 179 180 /** 181 * Creates a new {@link DataSet}. 182 * @param copyFrom An other {@link DataSet} to copy the contents of this dataset from. 183 * @since 10346 184 */ 185 public DataSet(DataSet copyFrom) { 186 this(); 187 copyFrom.getReadLock().lock(); 188 try { 189 Map<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>(); 190 for (Node n : copyFrom.getNodes()) { 191 Node newNode = new Node(n); 192 primMap.put(n, newNode); 193 addPrimitive(newNode); 194 } 195 for (Way w : copyFrom.getWays()) { 196 Way newWay = new Way(w); 197 primMap.put(w, newWay); 198 List<Node> newNodes = new ArrayList<>(); 199 for (Node n : w.getNodes()) { 200 newNodes.add((Node) primMap.get(n)); 201 } 202 newWay.setNodes(newNodes); 203 addPrimitive(newWay); 204 } 205 // Because relations can have other relations as members we first clone all relations 206 // and then get the cloned members 207 Collection<Relation> relations = copyFrom.getRelations(); 208 for (Relation r : relations) { 209 Relation newRelation = new Relation(r); 210 newRelation.setMembers(null); 211 primMap.put(r, newRelation); 212 addPrimitive(newRelation); 213 } 214 for (Relation r : relations) { 215 Relation newRelation = (Relation) primMap.get(r); 216 List<RelationMember> newMembers = new ArrayList<>(); 217 for (RelationMember rm : r.getMembers()) { 218 newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember()))); 219 } 220 newRelation.setMembers(newMembers); 221 } 222 for (DataSource source : copyFrom.dataSources) { 223 dataSources.add(new DataSource(source)); 224 } 225 version = copyFrom.version; 226 uploadPolicy = copyFrom.uploadPolicy; 227 downloadPolicy = copyFrom.downloadPolicy; 228 isReadOnly.set(copyFrom.isReadOnly.get()); 229 } finally { 230 copyFrom.getReadLock().unlock(); 231 } 232 } 233 234 /** 235 * Constructs a new {@code DataSet} initially filled with the given primitives. 236 * @param osmPrimitives primitives to add to this data set 237 * @since 12726 238 */ 239 public DataSet(OsmPrimitive... osmPrimitives) { 240 this(); 241 beginUpdate(); 242 try { 243 for (OsmPrimitive o : osmPrimitives) { 244 addPrimitive(o); 245 } 246 } finally { 247 endUpdate(); 248 } 249 } 250 251 /** 252 * Adds a new data source. 253 * @param source data source to add 254 * @return {@code true} if the collection changed as a result of the call 255 * @since 11626 256 */ 257 public synchronized boolean addDataSource(DataSource source) { 258 return addDataSources(Collections.singleton(source)); 259 } 260 261 /** 262 * Adds new data sources. 263 * @param sources data sources to add 264 * @return {@code true} if the collection changed as a result of the call 265 * @since 11626 266 */ 267 public synchronized boolean addDataSources(Collection<DataSource> sources) { 268 boolean changed = dataSources.addAll(sources); 269 if (changed) { 270 cachedDataSourceArea = null; 271 cachedDataSourceBounds = null; 272 } 273 return changed; 274 } 275 276 @Override 277 public Lock getReadLock() { 278 return lock.readLock(); 279 } 280 281 /** 282 * History of selections - shared by plugins and SelectionListDialog 283 */ 284 private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>(); 285 286 /** 287 * Replies the history of JOSM selections 288 * 289 * @return list of history entries 290 */ 291 public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() { 292 return selectionHistory; 293 } 294 295 /** 296 * Clears selection history list 297 */ 298 public void clearSelectionHistory() { 299 selectionHistory.clear(); 300 } 301 302 /** 303 * The API version that created this data set, if any. 304 */ 305 private String version; 306 307 @Override 308 public String getVersion() { 309 return version; 310 } 311 312 /** 313 * Sets the API version this dataset was created from. 314 * 315 * @param version the API version, i.e. "0.6" 316 * @throws IllegalStateException if the dataset is read-only 317 */ 318 public void setVersion(String version) { 319 checkModifiable(); 320 this.version = version; 321 } 322 323 @Override 324 public DownloadPolicy getDownloadPolicy() { 325 return this.downloadPolicy; 326 } 327 328 @Override 329 public void setDownloadPolicy(DownloadPolicy downloadPolicy) { 330 this.downloadPolicy = downloadPolicy; 331 } 332 333 @Override 334 public UploadPolicy getUploadPolicy() { 335 return this.uploadPolicy; 336 } 337 338 @Override 339 public void setUploadPolicy(UploadPolicy uploadPolicy) { 340 this.uploadPolicy = uploadPolicy; 341 } 342 343 /** 344 * Holding bin for changeset tag information, to be applied when or if this is ever uploaded. 345 */ 346 private final Map<String, String> changeSetTags = new HashMap<>(); 347 348 /** 349 * Replies the set of changeset tags to be applied when or if this is ever uploaded. 350 * @return the set of changeset tags 351 * @see #addChangeSetTag 352 */ 353 public Map<String, String> getChangeSetTags() { 354 return changeSetTags; 355 } 356 357 /** 358 * Adds a new changeset tag. 359 * @param k Key 360 * @param v Value 361 * @see #getChangeSetTags 362 */ 363 public void addChangeSetTag(String k, String v) { 364 this.changeSetTags.put(k, v); 365 } 366 367 @Override 368 public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) { 369 return new SubclassFilteredCollection<>(allPrimitives, predicate); 370 } 371 372 @Override 373 public Collection<Node> getNodes() { 374 return getPrimitives(Node.class::isInstance); 375 } 376 377 @Override 378 public List<Node> searchNodes(BBox bbox) { 379 lock.readLock().lock(); 380 try { 381 return store.searchNodes(bbox); 382 } finally { 383 lock.readLock().unlock(); 384 } 385 } 386 387 @Override 388 public Collection<Way> getWays() { 389 return getPrimitives(Way.class::isInstance); 390 } 391 392 @Override 393 public List<Way> searchWays(BBox bbox) { 394 lock.readLock().lock(); 395 try { 396 return store.searchWays(bbox); 397 } finally { 398 lock.readLock().unlock(); 399 } 400 } 401 402 @Override 403 public List<Relation> searchRelations(BBox bbox) { 404 lock.readLock().lock(); 405 try { 406 return store.searchRelations(bbox); 407 } finally { 408 lock.readLock().unlock(); 409 } 410 } 411 412 @Override 413 public Collection<Relation> getRelations() { 414 return getPrimitives(Relation.class::isInstance); 415 } 416 417 /** 418 * Determines if the given node can be retrieved in the data set through its bounding box. Useful for dataset consistency test. 419 * For efficiency reasons this method does not lock the dataset, you have to lock it manually. 420 * 421 * @param n The node to search 422 * @return {@code true} if {@code n} can be retrieved in this data set, {@code false} otherwise 423 * @since 7501 424 */ 425 @Override 426 public boolean containsNode(Node n) { 427 return store.containsNode(n); 428 } 429 430 /** 431 * Determines if the given way can be retrieved in the data set through its bounding box. Useful for dataset consistency test. 432 * For efficiency reasons this method does not lock the dataset, you have to lock it manually. 433 * 434 * @param w The way to search 435 * @return {@code true} if {@code w} can be retrieved in this data set, {@code false} otherwise 436 * @since 7501 437 */ 438 @Override 439 public boolean containsWay(Way w) { 440 return store.containsWay(w); 441 } 442 443 /** 444 * Determines if the given relation can be retrieved in the data set through its bounding box. Useful for dataset consistency test. 445 * For efficiency reasons this method does not lock the dataset, you have to lock it manually. 446 * 447 * @param r The relation to search 448 * @return {@code true} if {@code r} can be retrieved in this data set, {@code false} otherwise 449 * @since 7501 450 */ 451 @Override 452 public boolean containsRelation(Relation r) { 453 return store.containsRelation(r); 454 } 455 456 /** 457 * Adds a primitive to the dataset. 458 * 459 * @param primitive the primitive. 460 * @throws IllegalStateException if the dataset is read-only 461 */ 462 @Override 463 public void addPrimitive(OsmPrimitive primitive) { 464 Objects.requireNonNull(primitive, "primitive"); 465 checkModifiable(); 466 beginUpdate(); 467 try { 468 if (getPrimitiveById(primitive) != null) 469 throw new DataIntegrityProblemException( 470 tr("Unable to add primitive {0} to the dataset because it is already included", 471 primitive.toString())); 472 473 allPrimitives.add(primitive); 474 primitive.setDataset(this); 475 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly) 476 store.addPrimitive(primitive); 477 firePrimitivesAdded(Collections.singletonList(primitive), false); 478 } finally { 479 endUpdate(); 480 } 481 } 482 483 /** 484 * Removes a primitive from the dataset. This method only removes the 485 * primitive form the respective collection of primitives managed 486 * by this dataset, i.e. from {@code store.nodes}, {@code store.ways}, or 487 * {@code store.relations}. References from other primitives to this 488 * primitive are left unchanged. 489 * 490 * @param primitiveId the id of the primitive 491 * @throws IllegalStateException if the dataset is read-only 492 */ 493 public void removePrimitive(PrimitiveId primitiveId) { 494 checkModifiable(); 495 beginUpdate(); 496 try { 497 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId); 498 if (primitive == null) 499 return; 500 removePrimitiveImpl(primitive); 501 firePrimitivesRemoved(Collections.singletonList(primitive), false); 502 } finally { 503 endUpdate(); 504 } 505 } 506 507 private void removePrimitiveImpl(OsmPrimitive primitive) { 508 clearSelection(primitive.getPrimitiveId()); 509 if (primitive.isSelected()) { 510 throw new DataIntegrityProblemException("Primitive was re-selected by a selection listener: " + primitive); 511 } 512 store.removePrimitive(primitive); 513 allPrimitives.remove(primitive); 514 primitive.setDataset(null); 515 } 516 517 void removePrimitive(OsmPrimitive primitive) { 518 checkModifiable(); 519 beginUpdate(); 520 try { 521 removePrimitiveImpl(primitive); 522 firePrimitivesRemoved(Collections.singletonList(primitive), false); 523 } finally { 524 endUpdate(); 525 } 526 } 527 528 /*--------------------------------------------------- 529 * SELECTION HANDLING 530 *---------------------------------------------------*/ 531 532 @Override 533 public void addSelectionListener(DataSelectionListener listener) { 534 selectionListeners.addListener(listener); 535 } 536 537 @Override 538 public void removeSelectionListener(DataSelectionListener listener) { 539 selectionListeners.removeListener(listener); 540 } 541 542 /*--------------------------------------------------- 543 * OLD SELECTION HANDLING 544 *---------------------------------------------------*/ 545 546 /** 547 * A list of listeners to selection changed events. The list is static, as listeners register 548 * themselves for any dataset selection changes that occur, regardless of the current active 549 * dataset. (However, the selection does only change in the active layer) 550 * @deprecated to be removed 551 */ 552 @Deprecated 553 private static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<>(); 554 555 /** 556 * Adds a new selection listener. 557 * @param listener The selection listener to add 558 * @see #addSelectionListener(DataSelectionListener) 559 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener) 560 * @deprecated Use {@link SelectionEventManager#addSelectionListener(DataSelectionListener)} instead 561 */ 562 @Deprecated 563 public static void addSelectionListener(SelectionChangedListener listener) { 564 ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener); 565 } 566 567 /** 568 * Removes a selection listener. 569 * @param listener The selection listener to remove 570 * @see #removeSelectionListener(DataSelectionListener) 571 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener) 572 * @deprecated Use {@link SelectionEventManager#removeSelectionListener(DataSelectionListener)} instead 573 */ 574 @Deprecated 575 public static void removeSelectionListener(SelectionChangedListener listener) { 576 selListeners.remove(listener); 577 } 578 579 /** 580 * @deprecated to be removed 581 * @param currentSelection current selection 582 */ 583 @Deprecated 584 private static void fireSelectionChange(Collection<? extends OsmPrimitive> currentSelection) { 585 for (SelectionChangedListener l : selListeners) { 586 l.selectionChanged(currentSelection); 587 } 588 } 589 590 /** 591 * Returns selected nodes and ways. 592 * @return selected nodes and ways 593 */ 594 public Collection<OsmPrimitive> getSelectedNodesAndWays() { 595 return new SubclassFilteredCollection<>(getSelected(), 596 primitive -> primitive instanceof Node || primitive instanceof Way); 597 } 598 599 @Override 600 public Collection<WaySegment> getHighlightedVirtualNodes() { 601 return Collections.unmodifiableCollection(highlightedVirtualNodes); 602 } 603 604 @Override 605 public Collection<WaySegment> getHighlightedWaySegments() { 606 return Collections.unmodifiableCollection(highlightedWaySegments); 607 } 608 609 @Override 610 public void addHighlightUpdateListener(HighlightUpdateListener listener) { 611 highlightUpdateListeners.addListener(listener); 612 } 613 614 @Override 615 public void removeHighlightUpdateListener(HighlightUpdateListener listener) { 616 highlightUpdateListeners.removeListener(listener); 617 } 618 619 @Override 620 public Collection<OsmPrimitive> getAllSelected() { 621 return currentSelectedPrimitives; 622 } 623 624 @Override 625 public boolean selectionEmpty() { 626 return currentSelectedPrimitives.isEmpty(); 627 } 628 629 @Override 630 public boolean isSelected(OsmPrimitive osm) { 631 return currentSelectedPrimitives.contains(osm); 632 } 633 634 @Override 635 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) { 636 if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty()) 637 return; 638 639 highlightedVirtualNodes = waySegments; 640 fireHighlightingChanged(); 641 } 642 643 @Override 644 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) { 645 if (highlightedWaySegments.isEmpty() && waySegments.isEmpty()) 646 return; 647 648 highlightedWaySegments = waySegments; 649 fireHighlightingChanged(); 650 } 651 652 @Override 653 public void setSelected(Collection<? extends PrimitiveId> selection) { 654 setSelected(selection.stream()); 655 } 656 657 @Override 658 public void setSelected(PrimitiveId... osm) { 659 setSelected(Stream.of(osm).filter(Objects::nonNull)); 660 } 661 662 private void setSelected(Stream<? extends PrimitiveId> stream) { 663 doSelectionChange(old -> new SelectionReplaceEvent(this, old, 664 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 665 } 666 667 @Override 668 public void addSelected(Collection<? extends PrimitiveId> selection) { 669 addSelected(selection.stream()); 670 } 671 672 @Override 673 public void addSelected(PrimitiveId... osm) { 674 addSelected(Stream.of(osm)); 675 } 676 677 private void addSelected(Stream<? extends PrimitiveId> stream) { 678 doSelectionChange(old -> new SelectionAddEvent(this, old, 679 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 680 } 681 682 @Override 683 public void clearSelection(PrimitiveId... osm) { 684 clearSelection(Stream.of(osm)); 685 } 686 687 @Override 688 public void clearSelection(Collection<? extends PrimitiveId> list) { 689 clearSelection(list.stream()); 690 } 691 692 @Override 693 public void clearSelection() { 694 setSelected(Stream.empty()); 695 } 696 697 private void clearSelection(Stream<? extends PrimitiveId> stream) { 698 doSelectionChange(old -> new SelectionRemoveEvent(this, old, 699 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 700 } 701 702 @Override 703 public void toggleSelected(Collection<? extends PrimitiveId> osm) { 704 toggleSelected(osm.stream()); 705 } 706 707 @Override 708 public void toggleSelected(PrimitiveId... osm) { 709 toggleSelected(Stream.of(osm)); 710 } 711 712 private void toggleSelected(Stream<? extends PrimitiveId> stream) { 713 doSelectionChange(old -> new SelectionToggleEvent(this, old, 714 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 715 } 716 717 /** 718 * Do a selection change. 719 * <p> 720 * This is the only method that changes the current selection state. 721 * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives. 722 * @return true iff the command did change the selection. 723 * @since 12048 724 */ 725 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) { 726 synchronized (selectionLock) { 727 SelectionChangeEvent event = command.apply(currentSelectedPrimitives); 728 if (event.isNop()) { 729 return false; 730 } 731 currentSelectedPrimitives = event.getSelection(); 732 selectionListeners.fireEvent(l -> l.selectionChanged(event)); 733 return true; 734 } 735 } 736 737 @Override 738 public synchronized Area getDataSourceArea() { 739 if (cachedDataSourceArea == null) { 740 cachedDataSourceArea = OsmData.super.getDataSourceArea(); 741 } 742 return cachedDataSourceArea; 743 } 744 745 @Override 746 public synchronized List<Bounds> getDataSourceBounds() { 747 if (cachedDataSourceBounds == null) { 748 cachedDataSourceBounds = OsmData.super.getDataSourceBounds(); 749 } 750 return Collections.unmodifiableList(cachedDataSourceBounds); 751 } 752 753 @Override 754 public synchronized Collection<DataSource> getDataSources() { 755 return Collections.unmodifiableCollection(dataSources); 756 } 757 758 @Override 759 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) { 760 return primitiveId != null ? primitivesMap.get(primitiveId) : null; 761 } 762 763 /** 764 * Show message and stack trace in log in case primitive is not found 765 * @param primitiveId primitive id to look for 766 * @return Primitive by id. 767 */ 768 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) { 769 OsmPrimitive result = getPrimitiveById(primitiveId); 770 if (result == null && primitiveId != null) { 771 Logging.warn(tr( 772 "JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this " 773 + "at {2}. This is not a critical error, it should be safe to continue in your work.", 774 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite())); 775 Logging.error(new Exception()); 776 } 777 778 return result; 779 } 780 781 private static void deleteWay(Way way) { 782 way.setNodes(null); 783 way.setDeleted(true); 784 } 785 786 /** 787 * Removes all references from ways in this dataset to a particular node. 788 * 789 * @param node the node 790 * @return The set of ways that have been modified 791 * @throws IllegalStateException if the dataset is read-only 792 */ 793 public Set<Way> unlinkNodeFromWays(Node node) { 794 checkModifiable(); 795 Set<Way> result = new HashSet<>(); 796 beginUpdate(); 797 try { 798 for (Way way : node.getParentWays()) { 799 List<Node> wayNodes = way.getNodes(); 800 if (wayNodes.remove(node)) { 801 if (wayNodes.size() < 2) { 802 deleteWay(way); 803 } else { 804 way.setNodes(wayNodes); 805 } 806 result.add(way); 807 } 808 } 809 } finally { 810 endUpdate(); 811 } 812 return result; 813 } 814 815 /** 816 * removes all references from relations in this dataset to this primitive 817 * 818 * @param primitive the primitive 819 * @return The set of relations that have been modified 820 * @throws IllegalStateException if the dataset is read-only 821 */ 822 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) { 823 checkModifiable(); 824 Set<Relation> result = new HashSet<>(); 825 beginUpdate(); 826 try { 827 for (Relation relation : getRelations()) { 828 List<RelationMember> members = relation.getMembers(); 829 830 Iterator<RelationMember> it = members.iterator(); 831 boolean removed = false; 832 while (it.hasNext()) { 833 RelationMember member = it.next(); 834 if (member.getMember().equals(primitive)) { 835 it.remove(); 836 removed = true; 837 } 838 } 839 840 if (removed) { 841 relation.setMembers(members); 842 result.add(relation); 843 } 844 } 845 } finally { 846 endUpdate(); 847 } 848 return result; 849 } 850 851 /** 852 * Removes all references from other primitives to the referenced primitive. 853 * 854 * @param referencedPrimitive the referenced primitive 855 * @return The set of primitives that have been modified 856 * @throws IllegalStateException if the dataset is read-only 857 */ 858 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) { 859 checkModifiable(); 860 Set<OsmPrimitive> result = new HashSet<>(); 861 beginUpdate(); 862 try { 863 if (referencedPrimitive instanceof Node) { 864 result.addAll(unlinkNodeFromWays((Node) referencedPrimitive)); 865 } 866 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive)); 867 } finally { 868 endUpdate(); 869 } 870 return result; 871 } 872 873 @Override 874 public boolean isModified() { 875 for (OsmPrimitive p : allPrimitives) { 876 if (p.isModified()) 877 return true; 878 } 879 return false; 880 } 881 882 /** 883 * Replies true if there is at least one primitive in this dataset which requires to be uploaded to server. 884 * @return true if there is at least one primitive in this dataset which requires to be uploaded to server 885 * @since 13161 886 */ 887 public boolean requiresUploadToServer() { 888 for (OsmPrimitive p : allPrimitives) { 889 if (APIOperation.of(p) != null) 890 return true; 891 } 892 return false; 893 } 894 895 /** 896 * Adds a new data set listener. 897 * @param dsl The data set listener to add 898 */ 899 public void addDataSetListener(DataSetListener dsl) { 900 listeners.addIfAbsent(dsl); 901 } 902 903 /** 904 * Removes a data set listener. 905 * @param dsl The data set listener to remove 906 */ 907 public void removeDataSetListener(DataSetListener dsl) { 908 listeners.remove(dsl); 909 } 910 911 /** 912 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}. 913 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes 914 * <br> 915 * Typical usecase should look like this: 916 * <pre> 917 * ds.beginUpdate(); 918 * try { 919 * ... 920 * } finally { 921 * ds.endUpdate(); 922 * } 923 * </pre> 924 * @see #endUpdate() 925 */ 926 public void beginUpdate() { 927 lock.writeLock().lock(); 928 updateCount++; 929 } 930 931 /** 932 * Must be called after a previous call to {@link #beginUpdate()} to fire change events. 933 * <br> 934 * Typical usecase should look like this: 935 * <pre> 936 * ds.beginUpdate(); 937 * try { 938 * ... 939 * } finally { 940 * ds.endUpdate(); 941 * } 942 * </pre> 943 * @see DataSet#beginUpdate() 944 */ 945 public void endUpdate() { 946 if (updateCount > 0) { 947 updateCount--; 948 List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList(); 949 if (updateCount == 0) { 950 eventsToFire = new ArrayList<>(cachedEvents); 951 cachedEvents.clear(); 952 } 953 954 if (!eventsToFire.isEmpty()) { 955 lock.readLock().lock(); 956 lock.writeLock().unlock(); 957 try { 958 if (eventsToFire.size() < MAX_SINGLE_EVENTS) { 959 for (AbstractDatasetChangedEvent event : eventsToFire) { 960 fireEventToListeners(event); 961 } 962 } else if (eventsToFire.size() == MAX_EVENTS) { 963 fireEventToListeners(new DataChangedEvent(this)); 964 } else { 965 fireEventToListeners(new DataChangedEvent(this, eventsToFire)); 966 } 967 } finally { 968 lock.readLock().unlock(); 969 } 970 } else { 971 lock.writeLock().unlock(); 972 } 973 974 } else 975 throw new AssertionError("endUpdate called without beginUpdate"); 976 } 977 978 private void fireEventToListeners(AbstractDatasetChangedEvent event) { 979 for (DataSetListener listener : listeners) { 980 event.fire(listener); 981 } 982 } 983 984 private void fireEvent(AbstractDatasetChangedEvent event) { 985 if (updateCount == 0) 986 throw new AssertionError("dataset events can be fired only when dataset is locked"); 987 if (cachedEvents.size() < MAX_EVENTS) { 988 cachedEvents.add(event); 989 } 990 } 991 992 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) { 993 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete)); 994 } 995 996 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) { 997 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete)); 998 } 999 1000 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) { 1001 fireEvent(new TagsChangedEvent(this, prim, originalKeys)); 1002 } 1003 1004 void fireRelationMembersChanged(Relation r) { 1005 store.reindexRelation(r, Relation::updatePosition); 1006 fireEvent(new RelationMembersChangedEvent(this, r)); 1007 } 1008 1009 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) { 1010 store.reindexNode(node, n -> n.setCoorInternal(newCoor, eastNorth), Way::updatePosition, Relation::updatePosition); 1011 fireEvent(new NodeMovedEvent(this, node)); 1012 } 1013 1014 void fireWayNodesChanged(Way way) { 1015 if (way.getNodesCount() > 0) { 1016 store.reindexWay(way, Way::updatePosition, Relation::updatePosition); 1017 } 1018 fireEvent(new WayNodesChangedEvent(this, way)); 1019 } 1020 1021 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) { 1022 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, 1023 newChangesetId)); 1024 } 1025 1026 void firePrimitiveFlagsChanged(OsmPrimitive primitive) { 1027 fireEvent(new PrimitiveFlagsChangedEvent(this, primitive)); 1028 } 1029 1030 void fireFilterChanged() { 1031 fireEvent(new DataChangedEvent(this)); 1032 } 1033 1034 void fireHighlightingChanged() { 1035 HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this); 1036 highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e)); 1037 } 1038 1039 /** 1040 * Invalidates the internal cache of projected east/north coordinates. 1041 * 1042 * This method can be invoked after the globally configured projection method 1043 * changed. 1044 */ 1045 public void invalidateEastNorthCache() { 1046 if (Main.getProjection() == null) 1047 return; // sanity check 1048 beginUpdate(); 1049 try { 1050 for (Node n : getNodes()) { 1051 n.invalidateEastNorthCache(); 1052 } 1053 } finally { 1054 endUpdate(); 1055 } 1056 } 1057 1058 /** 1059 * Cleanups all deleted primitives (really delete them from the dataset). 1060 */ 1061 public void cleanupDeletedPrimitives() { 1062 beginUpdate(); 1063 try { 1064 Collection<OsmPrimitive> toCleanUp = getPrimitives( 1065 primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())); 1066 if (!toCleanUp.isEmpty()) { 1067 // We unselect them in advance to not fire a selection change for every primitive 1068 clearSelection(toCleanUp.stream().map(OsmPrimitive::getPrimitiveId)); 1069 for (OsmPrimitive primitive : toCleanUp) { 1070 removePrimitiveImpl(primitive); 1071 } 1072 firePrimitivesRemoved(toCleanUp, false); 1073 } 1074 } finally { 1075 endUpdate(); 1076 } 1077 } 1078 1079 /** 1080 * Removes all primitives from the dataset and resets the currently selected primitives 1081 * to the empty collection. Also notifies selection change listeners if necessary. 1082 * @throws IllegalStateException if the dataset is read-only 1083 */ 1084 @Override 1085 public void clear() { 1086 checkModifiable(); 1087 beginUpdate(); 1088 try { 1089 clearSelection(); 1090 for (OsmPrimitive primitive : allPrimitives) { 1091 primitive.setDataset(null); 1092 } 1093 store.clear(); 1094 allPrimitives.clear(); 1095 } finally { 1096 endUpdate(); 1097 } 1098 } 1099 1100 /** 1101 * Marks all "invisible" objects as deleted. These objects should be always marked as 1102 * deleted when downloaded from the server. They can be undeleted later if necessary. 1103 * @throws IllegalStateException if the dataset is read-only 1104 */ 1105 public void deleteInvisible() { 1106 checkModifiable(); 1107 for (OsmPrimitive primitive : allPrimitives) { 1108 if (!primitive.isVisible()) { 1109 primitive.setDeleted(true); 1110 } 1111 } 1112 } 1113 1114 /** 1115 * Moves all primitives and datasources from DataSet "from" to this DataSet. 1116 * @param from The source DataSet 1117 */ 1118 public void mergeFrom(DataSet from) { 1119 mergeFrom(from, null); 1120 } 1121 1122 /** 1123 * Moves all primitives and datasources from DataSet "from" to this DataSet. 1124 * @param from The source DataSet 1125 * @param progressMonitor The progress monitor 1126 * @throws IllegalStateException if the dataset is read-only 1127 */ 1128 public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) { 1129 if (from != null) { 1130 checkModifiable(); 1131 new DataSetMerger(this, from).merge(progressMonitor); 1132 synchronized (from) { 1133 if (!from.dataSources.isEmpty()) { 1134 if (dataSources.addAll(from.dataSources)) { 1135 cachedDataSourceArea = null; 1136 cachedDataSourceBounds = null; 1137 } 1138 from.dataSources.clear(); 1139 from.cachedDataSourceArea = null; 1140 from.cachedDataSourceBounds = null; 1141 } 1142 } 1143 } 1144 } 1145 1146 /** 1147 * Replies the set of conflicts currently managed in this layer. 1148 * 1149 * @return the set of conflicts currently managed in this layer 1150 * @since 12672 1151 */ 1152 public ConflictCollection getConflicts() { 1153 return conflicts; 1154 } 1155 1156 @Override 1157 public String getName() { 1158 return name; 1159 } 1160 1161 @Override 1162 public void setName(String name) { 1163 this.name = name; 1164 } 1165 1166 /* --------------------------------------------------------------------------------- */ 1167 /* interface ProjectionChangeListner */ 1168 /* --------------------------------------------------------------------------------- */ 1169 @Override 1170 public void projectionChanged(Projection oldValue, Projection newValue) { 1171 invalidateEastNorthCache(); 1172 } 1173 1174 @Override 1175 public synchronized ProjectionBounds getDataSourceBoundingBox() { 1176 BoundingXYVisitor bbox = new BoundingXYVisitor(); 1177 for (DataSource source : dataSources) { 1178 bbox.visit(source.bounds); 1179 } 1180 if (bbox.hasExtend()) { 1181 return bbox.getBounds(); 1182 } 1183 return null; 1184 } 1185 1186 /** 1187 * Returns mappaint cache index for this DataSet. 1188 * 1189 * If the {@link OsmPrimitive#mappaintCacheIdx} is not equal to the DataSet mappaint 1190 * cache index, this means the cache for that primitive is out of date. 1191 * @return mappaint cache index 1192 * @since 13420 1193 */ 1194 public short getMappaintCacheIndex() { 1195 return mappaintCacheIdx; 1196 } 1197 1198 @Override 1199 public void clearMappaintCache() { 1200 mappaintCacheIdx++; 1201 } 1202 1203 @Override 1204 public void lock() { 1205 if (!isReadOnly.compareAndSet(false, true)) { 1206 Logging.warn("Trying to set readOnly flag on a readOnly dataset ", getName()); 1207 } 1208 } 1209 1210 @Override 1211 public void unlock() { 1212 if (!isReadOnly.compareAndSet(true, false)) { 1213 Logging.warn("Trying to unset readOnly flag on a non-readOnly dataset ", getName()); 1214 } 1215 } 1216 1217 @Override 1218 public boolean isLocked() { 1219 return isReadOnly.get(); 1220 } 1221 1222 /** 1223 * Checks the dataset is modifiable (not read-only). 1224 * @throws IllegalStateException if the dataset is read-only 1225 */ 1226 private void checkModifiable() { 1227 if (isLocked()) { 1228 throw new IllegalStateException("DataSet is read-only"); 1229 } 1230 } 1231}