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