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