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