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