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.data.APIDataSet.APIOperation; 028import org.openstreetmap.josm.data.Bounds; 029import org.openstreetmap.josm.data.DataSource; 030import org.openstreetmap.josm.data.ProjectionBounds; 031import org.openstreetmap.josm.data.conflict.ConflictCollection; 032import org.openstreetmap.josm.data.coor.EastNorth; 033import org.openstreetmap.josm.data.coor.LatLon; 034import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent; 035import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent; 036import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent; 037import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent; 038import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent; 039import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 040import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent; 041import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 042import org.openstreetmap.josm.data.osm.event.DataSetListener; 043import org.openstreetmap.josm.data.osm.event.FilterChangedEvent; 044import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 045import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent; 046import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 047import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 048import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 049import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 050import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 051import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 052import org.openstreetmap.josm.data.projection.Projection; 053import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 054import org.openstreetmap.josm.data.projection.ProjectionRegistry; 055import org.openstreetmap.josm.gui.progress.ProgressMonitor; 056import org.openstreetmap.josm.spi.preferences.Config; 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 = DownloadPolicy.NORMAL; 136 private UploadPolicy uploadPolicy = UploadPolicy.NORMAL; 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 private String remark; 170 171 /** 172 * Constructs a new {@code DataSet}. 173 */ 174 public DataSet() { 175 // Transparently register as projection change listener. No need to explicitly remove 176 // the listener, projection change listeners are managed as WeakReferences. 177 ProjectionRegistry.addProjectionChangeListener(this); 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 = Objects.requireNonNull(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 = Objects.requireNonNull(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 * Returns selected nodes and ways. 544 * @return selected nodes and ways 545 */ 546 public Collection<OsmPrimitive> getSelectedNodesAndWays() { 547 return new SubclassFilteredCollection<>(getSelected(), 548 primitive -> primitive instanceof Node || primitive instanceof Way); 549 } 550 551 @Override 552 public Collection<WaySegment> getHighlightedVirtualNodes() { 553 return Collections.unmodifiableCollection(highlightedVirtualNodes); 554 } 555 556 @Override 557 public Collection<WaySegment> getHighlightedWaySegments() { 558 return Collections.unmodifiableCollection(highlightedWaySegments); 559 } 560 561 @Override 562 public void addHighlightUpdateListener(HighlightUpdateListener listener) { 563 highlightUpdateListeners.addListener(listener); 564 } 565 566 @Override 567 public void removeHighlightUpdateListener(HighlightUpdateListener listener) { 568 highlightUpdateListeners.removeListener(listener); 569 } 570 571 @Override 572 public Collection<OsmPrimitive> getAllSelected() { 573 return currentSelectedPrimitives; 574 } 575 576 @Override 577 public boolean selectionEmpty() { 578 return currentSelectedPrimitives.isEmpty(); 579 } 580 581 @Override 582 public boolean isSelected(OsmPrimitive osm) { 583 return currentSelectedPrimitives.contains(osm); 584 } 585 586 @Override 587 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) { 588 if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty()) 589 return; 590 591 highlightedVirtualNodes = waySegments; 592 fireHighlightingChanged(); 593 } 594 595 @Override 596 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) { 597 if (highlightedWaySegments.isEmpty() && waySegments.isEmpty()) 598 return; 599 600 highlightedWaySegments = waySegments; 601 fireHighlightingChanged(); 602 } 603 604 @Override 605 public void setSelected(Collection<? extends PrimitiveId> selection) { 606 setSelected(selection.stream()); 607 } 608 609 @Override 610 public void setSelected(PrimitiveId... osm) { 611 setSelected(Stream.of(osm).filter(Objects::nonNull)); 612 } 613 614 private void setSelected(Stream<? extends PrimitiveId> stream) { 615 doSelectionChange(old -> new SelectionReplaceEvent(this, old, 616 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 617 } 618 619 @Override 620 public void addSelected(Collection<? extends PrimitiveId> selection) { 621 addSelected(selection.stream()); 622 } 623 624 @Override 625 public void addSelected(PrimitiveId... osm) { 626 addSelected(Stream.of(osm)); 627 } 628 629 private void addSelected(Stream<? extends PrimitiveId> stream) { 630 doSelectionChange(old -> new SelectionAddEvent(this, old, 631 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 632 } 633 634 @Override 635 public void clearSelection(PrimitiveId... osm) { 636 clearSelection(Stream.of(osm)); 637 } 638 639 @Override 640 public void clearSelection(Collection<? extends PrimitiveId> list) { 641 clearSelection(list.stream()); 642 } 643 644 @Override 645 public void clearSelection() { 646 setSelected(Stream.empty()); 647 } 648 649 private void clearSelection(Stream<? extends PrimitiveId> stream) { 650 doSelectionChange(old -> new SelectionRemoveEvent(this, old, 651 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 652 } 653 654 @Override 655 public void toggleSelected(Collection<? extends PrimitiveId> osm) { 656 toggleSelected(osm.stream()); 657 } 658 659 @Override 660 public void toggleSelected(PrimitiveId... osm) { 661 toggleSelected(Stream.of(osm)); 662 } 663 664 private void toggleSelected(Stream<? extends PrimitiveId> stream) { 665 doSelectionChange(old -> new SelectionToggleEvent(this, old, 666 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull))); 667 } 668 669 /** 670 * Do a selection change. 671 * <p> 672 * This is the only method that changes the current selection state. 673 * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives. 674 * @return true iff the command did change the selection. 675 * @since 12048 676 */ 677 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) { 678 synchronized (selectionLock) { 679 SelectionChangeEvent event = command.apply(currentSelectedPrimitives); 680 if (event.isNop()) { 681 return false; 682 } 683 currentSelectedPrimitives = event.getSelection(); 684 selectionListeners.fireEvent(l -> l.selectionChanged(event)); 685 return true; 686 } 687 } 688 689 @Override 690 public synchronized Area getDataSourceArea() { 691 if (cachedDataSourceArea == null) { 692 cachedDataSourceArea = OsmData.super.getDataSourceArea(); 693 } 694 return cachedDataSourceArea; 695 } 696 697 @Override 698 public synchronized List<Bounds> getDataSourceBounds() { 699 if (cachedDataSourceBounds == null) { 700 cachedDataSourceBounds = OsmData.super.getDataSourceBounds(); 701 } 702 return Collections.unmodifiableList(cachedDataSourceBounds); 703 } 704 705 @Override 706 public synchronized Collection<DataSource> getDataSources() { 707 return Collections.unmodifiableCollection(dataSources); 708 } 709 710 @Override 711 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) { 712 return primitiveId != null ? primitivesMap.get(primitiveId) : null; 713 } 714 715 /** 716 * Show message and stack trace in log in case primitive is not found 717 * @param primitiveId primitive id to look for 718 * @return Primitive by id. 719 */ 720 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) { 721 OsmPrimitive result = getPrimitiveById(primitiveId); 722 if (result == null && primitiveId != null) { 723 Logging.warn(tr( 724 "JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this " 725 + "at {2}. This is not a critical error, it should be safe to continue in your work.", 726 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Config.getUrls().getJOSMWebsite())); 727 Logging.error(new Exception()); 728 } 729 730 return result; 731 } 732 733 private static void deleteWay(Way way) { 734 way.setNodes(null); 735 way.setDeleted(true); 736 } 737 738 /** 739 * Removes all references from ways in this dataset to a particular node. 740 * 741 * @param node the node 742 * @return The set of ways that have been modified 743 * @throws IllegalStateException if the dataset is read-only 744 */ 745 public Set<Way> unlinkNodeFromWays(Node node) { 746 checkModifiable(); 747 Set<Way> result = new HashSet<>(); 748 beginUpdate(); 749 try { 750 for (Way way : node.getParentWays()) { 751 List<Node> wayNodes = way.getNodes(); 752 if (wayNodes.remove(node)) { 753 if (wayNodes.size() < 2) { 754 deleteWay(way); 755 } else { 756 way.setNodes(wayNodes); 757 } 758 result.add(way); 759 } 760 } 761 } finally { 762 endUpdate(); 763 } 764 return result; 765 } 766 767 /** 768 * removes all references from relations in this dataset to this primitive 769 * 770 * @param primitive the primitive 771 * @return The set of relations that have been modified 772 * @throws IllegalStateException if the dataset is read-only 773 */ 774 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) { 775 checkModifiable(); 776 Set<Relation> result = new HashSet<>(); 777 beginUpdate(); 778 try { 779 for (Relation relation : getRelations()) { 780 List<RelationMember> members = relation.getMembers(); 781 782 Iterator<RelationMember> it = members.iterator(); 783 boolean removed = false; 784 while (it.hasNext()) { 785 RelationMember member = it.next(); 786 if (member.getMember().equals(primitive)) { 787 it.remove(); 788 removed = true; 789 } 790 } 791 792 if (removed) { 793 relation.setMembers(members); 794 result.add(relation); 795 } 796 } 797 } finally { 798 endUpdate(); 799 } 800 return result; 801 } 802 803 /** 804 * Removes all references from other primitives to the referenced primitive. 805 * 806 * @param referencedPrimitive the referenced primitive 807 * @return The set of primitives that have been modified 808 * @throws IllegalStateException if the dataset is read-only 809 */ 810 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) { 811 checkModifiable(); 812 Set<OsmPrimitive> result = new HashSet<>(); 813 beginUpdate(); 814 try { 815 if (referencedPrimitive instanceof Node) { 816 result.addAll(unlinkNodeFromWays((Node) referencedPrimitive)); 817 } 818 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive)); 819 } finally { 820 endUpdate(); 821 } 822 return result; 823 } 824 825 @Override 826 public boolean isModified() { 827 for (OsmPrimitive p : allPrimitives) { 828 if (p.isModified()) 829 return true; 830 } 831 return false; 832 } 833 834 /** 835 * Replies true if there is at least one primitive in this dataset which requires to be uploaded to server. 836 * @return true if there is at least one primitive in this dataset which requires to be uploaded to server 837 * @since 13161 838 */ 839 public boolean requiresUploadToServer() { 840 for (OsmPrimitive p : allPrimitives) { 841 if (APIOperation.of(p) != null) 842 return true; 843 } 844 return false; 845 } 846 847 /** 848 * Adds a new data set listener. 849 * @param dsl The data set listener to add 850 */ 851 public void addDataSetListener(DataSetListener dsl) { 852 listeners.addIfAbsent(dsl); 853 } 854 855 /** 856 * Removes a data set listener. 857 * @param dsl The data set listener to remove 858 */ 859 public void removeDataSetListener(DataSetListener dsl) { 860 listeners.remove(dsl); 861 } 862 863 /** 864 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}. 865 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes 866 * <br> 867 * Typical usecase should look like this: 868 * <pre> 869 * ds.beginUpdate(); 870 * try { 871 * ... 872 * } finally { 873 * ds.endUpdate(); 874 * } 875 * </pre> 876 * @see #endUpdate() 877 */ 878 public void beginUpdate() { 879 lock.writeLock().lock(); 880 updateCount++; 881 } 882 883 /** 884 * Must be called after a previous call to {@link #beginUpdate()} to fire change events. 885 * <br> 886 * Typical usecase should look like this: 887 * <pre> 888 * ds.beginUpdate(); 889 * try { 890 * ... 891 * } finally { 892 * ds.endUpdate(); 893 * } 894 * </pre> 895 * @see DataSet#beginUpdate() 896 */ 897 public void endUpdate() { 898 if (updateCount > 0) { 899 updateCount--; 900 List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList(); 901 if (updateCount == 0) { 902 eventsToFire = new ArrayList<>(cachedEvents); 903 cachedEvents.clear(); 904 } 905 906 if (!eventsToFire.isEmpty()) { 907 lock.readLock().lock(); 908 try { 909 lock.writeLock().unlock(); 910 if (eventsToFire.size() < MAX_SINGLE_EVENTS) { 911 for (AbstractDatasetChangedEvent event : eventsToFire) { 912 fireEventToListeners(event); 913 } 914 } else if (eventsToFire.size() == MAX_EVENTS) { 915 fireEventToListeners(new DataChangedEvent(this)); 916 } else { 917 fireEventToListeners(new DataChangedEvent(this, eventsToFire)); 918 } 919 } finally { 920 lock.readLock().unlock(); 921 } 922 } else { 923 lock.writeLock().unlock(); 924 } 925 926 } else 927 throw new AssertionError("endUpdate called without beginUpdate"); 928 } 929 930 private void fireEventToListeners(AbstractDatasetChangedEvent event) { 931 for (DataSetListener listener : listeners) { 932 Logging.trace("Firing {0} to {1} (dataset)", event, listener); 933 event.fire(listener); 934 } 935 } 936 937 private void fireEvent(AbstractDatasetChangedEvent event) { 938 if (updateCount == 0) 939 throw new AssertionError("dataset events can be fired only when dataset is locked"); 940 if (cachedEvents.size() < MAX_EVENTS) { 941 cachedEvents.add(event); 942 } 943 } 944 945 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) { 946 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete)); 947 } 948 949 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) { 950 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete)); 951 } 952 953 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) { 954 fireEvent(new TagsChangedEvent(this, prim, originalKeys)); 955 } 956 957 void fireRelationMembersChanged(Relation r) { 958 store.reindexRelation(r, Relation::updatePosition); 959 fireEvent(new RelationMembersChangedEvent(this, r)); 960 } 961 962 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) { 963 store.reindexNode(node, n -> n.setCoorInternal(newCoor, eastNorth), Way::updatePosition, Relation::updatePosition); 964 fireEvent(new NodeMovedEvent(this, node)); 965 } 966 967 void fireWayNodesChanged(Way way) { 968 if (way.getNodesCount() > 0) { 969 store.reindexWay(way, Way::updatePosition, Relation::updatePosition); 970 } 971 fireEvent(new WayNodesChangedEvent(this, way)); 972 } 973 974 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) { 975 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, 976 newChangesetId)); 977 } 978 979 void firePrimitiveFlagsChanged(OsmPrimitive primitive) { 980 fireEvent(new PrimitiveFlagsChangedEvent(this, primitive)); 981 } 982 983 void fireFilterChanged() { 984 fireEvent(new FilterChangedEvent(this)); 985 } 986 987 void fireHighlightingChanged() { 988 HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this); 989 highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e)); 990 } 991 992 /** 993 * Invalidates the internal cache of projected east/north coordinates. 994 * 995 * This method can be invoked after the globally configured projection method 996 * changed. 997 */ 998 public void invalidateEastNorthCache() { 999 if (ProjectionRegistry.getProjection() == null) 1000 return; // sanity check 1001 beginUpdate(); 1002 try { 1003 for (Node n : getNodes()) { 1004 n.invalidateEastNorthCache(); 1005 } 1006 } finally { 1007 endUpdate(); 1008 } 1009 } 1010 1011 /** 1012 * Cleanups all deleted primitives (really delete them from the dataset). 1013 */ 1014 public void cleanupDeletedPrimitives() { 1015 beginUpdate(); 1016 try { 1017 Collection<OsmPrimitive> toCleanUp = getPrimitives( 1018 primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())); 1019 if (!toCleanUp.isEmpty()) { 1020 // We unselect them in advance to not fire a selection change for every primitive 1021 clearSelection(toCleanUp.stream().map(OsmPrimitive::getPrimitiveId)); 1022 for (OsmPrimitive primitive : toCleanUp) { 1023 removePrimitiveImpl(primitive); 1024 } 1025 firePrimitivesRemoved(toCleanUp, false); 1026 } 1027 } finally { 1028 endUpdate(); 1029 } 1030 } 1031 1032 /** 1033 * Removes all primitives from the dataset and resets the currently selected primitives 1034 * to the empty collection. Also notifies selection change listeners if necessary. 1035 * @throws IllegalStateException if the dataset is read-only 1036 */ 1037 @Override 1038 public void clear() { 1039 //TODO: Why can't we clear a dataset that is locked? 1040 //TODO: Report listeners that are still active (should be none) 1041 checkModifiable(); 1042 beginUpdate(); 1043 try { 1044 clearSelection(); 1045 for (OsmPrimitive primitive : allPrimitives) { 1046 primitive.setDataset(null); 1047 } 1048 store.clear(); 1049 allPrimitives.clear(); 1050 } finally { 1051 endUpdate(); 1052 } 1053 } 1054 1055 /** 1056 * Marks all "invisible" objects as deleted. These objects should be always marked as 1057 * deleted when downloaded from the server. They can be undeleted later if necessary. 1058 * @throws IllegalStateException if the dataset is read-only 1059 */ 1060 public void deleteInvisible() { 1061 checkModifiable(); 1062 for (OsmPrimitive primitive : allPrimitives) { 1063 if (!primitive.isVisible()) { 1064 primitive.setDeleted(true); 1065 } 1066 } 1067 } 1068 1069 /** 1070 * Moves all primitives and datasources from DataSet "from" to this DataSet. 1071 * @param from The source DataSet 1072 */ 1073 public void mergeFrom(DataSet from) { 1074 mergeFrom(from, null); 1075 } 1076 1077 /** 1078 * Moves all primitives and datasources from DataSet "from" to this DataSet. 1079 * @param from The source DataSet 1080 * @param progressMonitor The progress monitor 1081 * @throws IllegalStateException if the dataset is read-only 1082 */ 1083 public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) { 1084 if (from != null) { 1085 checkModifiable(); 1086 new DataSetMerger(this, from).merge(progressMonitor); 1087 synchronized (from) { 1088 if (!from.dataSources.isEmpty()) { 1089 if (dataSources.addAll(from.dataSources)) { 1090 cachedDataSourceArea = null; 1091 cachedDataSourceBounds = null; 1092 } 1093 from.dataSources.clear(); 1094 from.cachedDataSourceArea = null; 1095 from.cachedDataSourceBounds = null; 1096 } 1097 } 1098 } 1099 } 1100 1101 /** 1102 * Replies the set of conflicts currently managed in this layer. 1103 * 1104 * @return the set of conflicts currently managed in this layer 1105 * @since 12672 1106 */ 1107 public ConflictCollection getConflicts() { 1108 return conflicts; 1109 } 1110 1111 @Override 1112 public String getName() { 1113 return name; 1114 } 1115 1116 @Override 1117 public void setName(String name) { 1118 this.name = name; 1119 } 1120 1121 /* --------------------------------------------------------------------------------- */ 1122 /* interface ProjectionChangeListner */ 1123 /* --------------------------------------------------------------------------------- */ 1124 @Override 1125 public void projectionChanged(Projection oldValue, Projection newValue) { 1126 invalidateEastNorthCache(); 1127 } 1128 1129 @Override 1130 public synchronized ProjectionBounds getDataSourceBoundingBox() { 1131 BoundingXYVisitor bbox = new BoundingXYVisitor(); 1132 for (DataSource source : dataSources) { 1133 bbox.visit(source.bounds); 1134 } 1135 if (bbox.hasExtend()) { 1136 return bbox.getBounds(); 1137 } 1138 return null; 1139 } 1140 1141 /** 1142 * Returns mappaint cache index for this DataSet. 1143 * 1144 * If the {@link OsmPrimitive#mappaintCacheIdx} is not equal to the DataSet mappaint 1145 * cache index, this means the cache for that primitive is out of date. 1146 * @return mappaint cache index 1147 * @since 13420 1148 */ 1149 public short getMappaintCacheIndex() { 1150 return mappaintCacheIdx; 1151 } 1152 1153 @Override 1154 public void clearMappaintCache() { 1155 mappaintCacheIdx++; 1156 } 1157 1158 @Override 1159 public void lock() { 1160 if (!isReadOnly.compareAndSet(false, true)) { 1161 Logging.warn("Trying to set readOnly flag on a readOnly dataset ", getName()); 1162 } 1163 } 1164 1165 @Override 1166 public void unlock() { 1167 if (!isReadOnly.compareAndSet(true, false)) { 1168 Logging.warn("Trying to unset readOnly flag on a non-readOnly dataset ", getName()); 1169 } 1170 } 1171 1172 @Override 1173 public boolean isLocked() { 1174 return isReadOnly.get(); 1175 } 1176 1177 /** 1178 * Checks the dataset is modifiable (not read-only). 1179 * @throws IllegalStateException if the dataset is read-only 1180 */ 1181 private void checkModifiable() { 1182 if (isLocked()) { 1183 throw new IllegalStateException("DataSet is read-only"); 1184 } 1185 } 1186 1187 /** 1188 * Returns an optional remark about this data set (used by Overpass API). 1189 * @return a remark about this data set, or {@code null} 1190 * @since 14219 1191 */ 1192 public String getRemark() { 1193 return remark; 1194 } 1195 1196 /** 1197 * Sets an optional remark about this data set (used by Overpass API). 1198 * @param remark a remark about this data set, or {@code null} 1199 * @since 14219 1200 */ 1201 public void setRemark(String remark) { 1202 this.remark = remark; 1203 } 1204 1205 /** 1206 * @return true if this Dataset contains no primitives 1207 * @since 14835 1208 */ 1209 public boolean isEmpty() { 1210 return allPrimitives.isEmpty(); 1211 } 1212}