source: josm/trunk/src/org/openstreetmap/josm/data/osm/DataSet.java@ 3504

Last change on this file since 3504 was 3504, checked in by bastiK, 14 years ago

reworked reverseWay and combineWay such that it can be used by other actions without hack (see #5179 - Join overlapping areas rewrite)

  • Property svn:eol-style set to native
File size: 33.2 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.concurrent.CopyOnWriteArrayList;
18import java.util.concurrent.locks.Lock;
19import java.util.concurrent.locks.ReadWriteLock;
20import java.util.concurrent.locks.ReentrantReadWriteLock;
21
22import org.openstreetmap.josm.data.SelectionChangedListener;
23import org.openstreetmap.josm.data.coor.EastNorth;
24import org.openstreetmap.josm.data.coor.LatLon;
25import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
26import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
27import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
28import org.openstreetmap.josm.data.osm.event.DataSetListener;
29import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
30import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
31import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
32import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
33import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
34import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
35import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
36import org.openstreetmap.josm.tools.Predicate;
37
38/**
39 * DataSet is the data behind the application. It can consists of only a few points up to the whole
40 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
41 *
42 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
43 * store some information.
44 *
45 * @author imi
46 */
47public class DataSet implements Cloneable {
48
49 /**
50 * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
51 */
52 private static final int MAX_SINGLE_EVENTS = 30;
53
54 /**
55 * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
56 */
57 private static final int MAX_EVENTS = 1000;
58
59 private static class IdHash implements Hash<PrimitiveId,OsmPrimitive> {
60
61 public int getHashCode(PrimitiveId k) {
62 return (int)k.getUniqueId() ^ k.getType().hashCode();
63 }
64
65 public boolean equals(PrimitiveId key, OsmPrimitive value) {
66 if (key == null || value == null) return false;
67 return key.getUniqueId() == value.getUniqueId() && key.getType() == value.getType();
68 }
69 }
70
71 private Storage<OsmPrimitive> allPrimitives = new Storage<OsmPrimitive>(new IdHash(), true);
72 private Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new IdHash());
73 private CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<DataSetListener>();
74
75 // Number of open calls to beginUpdate
76 private int updateCount;
77 // Events that occurred while dataset was locked but should be fired after write lock is released
78 private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<AbstractDatasetChangedEvent>();
79
80 private int highlightUpdateCount;
81
82 private final ReadWriteLock lock = new ReentrantReadWriteLock();
83 private final Object selectionLock = new Object();
84
85 public Lock getReadLock() {
86 return lock.readLock();
87 }
88
89 /**
90 * This method can be used to detect changes in highlight state of primitives. If highlighting was changed
91 * then the method will return different number.
92 * @return
93 */
94 public int getHighlightUpdateCount() {
95 return highlightUpdateCount;
96 }
97
98 /**
99 * Maintain a list of used tags for autocompletion
100 */
101 private AutoCompletionManager autocomplete;
102
103 public AutoCompletionManager getAutoCompletionManager() {
104 if (autocomplete == null) {
105 autocomplete = new AutoCompletionManager(this);
106 addDataSetListener(autocomplete);
107 }
108 return autocomplete;
109 }
110
111 /**
112 * The API version that created this data set, if any.
113 */
114 private String version;
115
116 /**
117 * Replies the API version this dataset was created from. May be null.
118 *
119 * @return the API version this dataset was created from. May be null.
120 */
121 public String getVersion() {
122 return version;
123 }
124
125 /**
126 * Sets the API version this dataset was created from.
127 *
128 * @param version the API version, i.e. "0.5" or "0.6"
129 */
130 public void setVersion(String version) {
131 this.version = version;
132 }
133
134 /**
135 * All nodes goes here, even when included in other data (ways etc). This enables the instant
136 * conversion of the whole DataSet by iterating over this data structure.
137 */
138 private QuadBuckets<Node> nodes = new QuadBuckets<Node>();
139
140 private <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<OsmPrimitive> predicate) {
141 return new DatasetCollection<T>(allPrimitives, predicate);
142 }
143
144 /**
145 * Replies an unmodifiable collection of nodes in this dataset
146 *
147 * @return an unmodifiable collection of nodes in this dataset
148 */
149 public Collection<Node> getNodes() {
150 return getPrimitives(OsmPrimitive.nodePredicate);
151 }
152
153 public List<Node> searchNodes(BBox bbox) {
154 return nodes.search(bbox);
155 }
156
157 /**
158 * All ways (Streets etc.) in the DataSet.
159 *
160 * The way nodes are stored only in the way list.
161 */
162 private QuadBuckets<Way> ways = new QuadBuckets<Way>();
163
164 /**
165 * Replies an unmodifiable collection of ways in this dataset
166 *
167 * @return an unmodifiable collection of ways in this dataset
168 */
169 public Collection<Way> getWays() {
170 return getPrimitives(OsmPrimitive.wayPredicate);
171 }
172
173 public List<Way> searchWays(BBox bbox) {
174 return ways.search(bbox);
175 }
176
177 /**
178 * All relations/relationships
179 */
180 private Collection<Relation> relations = new ArrayList<Relation>();
181
182 /**
183 * Replies an unmodifiable collection of relations in this dataset
184 *
185 * @return an unmodifiable collection of relations in this dataset
186 */
187 public Collection<Relation> getRelations() {
188 return getPrimitives(OsmPrimitive.relationPredicate);
189 }
190
191 public List<Relation> searchRelations(BBox bbox) {
192 // QuadBuckets might be useful here (don't forget to do reindexing after some of rm is changed)
193 List<Relation> result = new ArrayList<Relation>();
194 for (Relation r: relations) {
195 if (r.getBBox().intersects(bbox)) {
196 result.add(r);
197 }
198 }
199 return result;
200 }
201
202 /**
203 * All data sources of this DataSet.
204 */
205 public Collection<DataSource> dataSources = new LinkedList<DataSource>();
206
207 /**
208 * @return A collection containing all primitives of the dataset. Data are not ordered
209 */
210 public Collection<OsmPrimitive> allPrimitives() {
211 return getPrimitives(OsmPrimitive.allPredicate);
212 }
213
214 /**
215 * @return A collection containing all not-deleted primitives (except keys).
216 */
217 public Collection<OsmPrimitive> allNonDeletedPrimitives() {
218 return getPrimitives(OsmPrimitive.nonDeletedPredicate);
219 }
220
221 public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() {
222 return getPrimitives(OsmPrimitive.nonDeletedCompletePredicate);
223 }
224
225 public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() {
226 return getPrimitives(OsmPrimitive.nonDeletedPhysicalPredicate);
227 }
228
229 public Collection<OsmPrimitive> allModifiedPrimitives() {
230 return getPrimitives(OsmPrimitive.modifiedPredicate);
231 }
232
233 /**
234 * Adds a primitive to the dataset
235 *
236 * @param primitive the primitive.
237 */
238 public void addPrimitive(OsmPrimitive primitive) {
239 beginUpdate();
240 try {
241 if (getPrimitiveById(primitive) != null)
242 throw new DataIntegrityProblemException(
243 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()));
244
245 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reinexRelation to work properly)
246 boolean success = false;
247 if (primitive instanceof Node) {
248 success = nodes.add((Node) primitive);
249 } else if (primitive instanceof Way) {
250 success = ways.add((Way) primitive);
251 } else if (primitive instanceof Relation) {
252 success = relations.add((Relation) primitive);
253 }
254 if (!success)
255 throw new RuntimeException("failed to add primitive: "+primitive);
256 allPrimitives.add(primitive);
257 primitive.setDataset(this);
258 firePrimitivesAdded(Collections.singletonList(primitive), false);
259 } finally {
260 endUpdate();
261 }
262 }
263
264 /**
265 * Removes a primitive from the dataset. This method only removes the
266 * primitive form the respective collection of primitives managed
267 * by this dataset, i.e. from {@see #nodes}, {@see #ways}, or
268 * {@see #relations}. References from other primitives to this
269 * primitive are left unchanged.
270 *
271 * @param primitive the primitive
272 */
273 public void removePrimitive(PrimitiveId primitiveId) {
274 beginUpdate();
275 try {
276 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
277 if (primitive == null)
278 return;
279 boolean success = false;
280 if (primitive instanceof Node) {
281 success = nodes.remove(primitive);
282 } else if (primitive instanceof Way) {
283 success = ways.remove(primitive);
284 } else if (primitive instanceof Relation) {
285 success = relations.remove(primitive);
286 }
287 if (!success)
288 throw new RuntimeException("failed to remove primitive: "+primitive);
289 synchronized (selectionLock) {
290 selectedPrimitives.remove(primitive);
291 selectionSnapshot = null;
292 }
293 allPrimitives.remove(primitive);
294 primitive.setDataset(null);
295 firePrimitivesRemoved(Collections.singletonList(primitive), false);
296 } finally {
297 endUpdate();
298 }
299 }
300
301 /*---------------------------------------------------
302 * SELECTION HANDLING
303 *---------------------------------------------------*/
304
305 /**
306 * A list of listeners to selection changed events. The list is static, as listeners register
307 * themselves for any dataset selection changes that occur, regardless of the current active
308 * dataset. (However, the selection does only change in the active layer)
309 * @deprecated Use addSelectionListener/removeSelectionListener instead
310 */
311 @Deprecated
312 public static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<SelectionChangedListener>();
313
314 public static void addSelectionListener(SelectionChangedListener listener) {
315 ((CopyOnWriteArrayList<SelectionChangedListener>)selListeners).addIfAbsent(listener);
316 }
317
318 public static void removeSelectionListener(SelectionChangedListener listener) {
319 selListeners.remove(listener);
320 }
321
322 /**
323 * Notifies all registered {@see SelectionChangedListener} about the current selection in
324 * this dataset.
325 *
326 */
327 public void fireSelectionChanged(){
328 Collection<? extends OsmPrimitive> currentSelection = getSelected();
329 for (SelectionChangedListener l : selListeners) {
330 l.selectionChanged(currentSelection);
331 }
332 }
333
334 private LinkedHashSet<OsmPrimitive> selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
335 private Collection<OsmPrimitive> selectionSnapshot;
336
337 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
338 return new DatasetCollection<OsmPrimitive>(getSelected(), new Predicate<OsmPrimitive>() {
339 @Override
340 public boolean evaluate(OsmPrimitive primitive) {
341 return primitive instanceof Node || primitive instanceof Way;
342 }
343 });
344 }
345
346 /**
347 * Replies an unmodifiable collection of primitives currently selected
348 * in this dataset. May be empty, but not null.
349 *
350 * @return unmodifiable collection of primitives
351 */
352 public Collection<OsmPrimitive> getSelected() {
353 Collection<OsmPrimitive> currentList;
354 synchronized (selectionLock) {
355 if (selectionSnapshot == null) {
356 selectionSnapshot = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(selectedPrimitives));
357 }
358 currentList = selectionSnapshot;
359 }
360 return currentList;
361 }
362
363 /**
364 * Return selected nodes.
365 */
366 public Collection<Node> getSelectedNodes() {
367 return new DatasetCollection<Node>(getSelected(), OsmPrimitive.nodePredicate);
368 }
369
370 /**
371 * Return selected ways.
372 */
373 public Collection<Way> getSelectedWays() {
374 return new DatasetCollection<Way>(getSelected(), OsmPrimitive.wayPredicate);
375 }
376
377 /**
378 * Return selected relations.
379 */
380 public Collection<Relation> getSelectedRelations() {
381 return new DatasetCollection<Relation>(getSelected(), OsmPrimitive.relationPredicate);
382 }
383
384 /**
385 * @return whether the selection is empty or not
386 */
387 public boolean selectionEmpty() {
388 return selectedPrimitives.isEmpty();
389 }
390
391 public boolean isSelected(OsmPrimitive osm) {
392 return selectedPrimitives.contains(osm);
393 }
394
395 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
396 boolean changed = false;
397 synchronized (selectionLock) {
398 for (PrimitiveId o : osm) {
399 changed = changed | this.__toggleSelected(o);
400 }
401 if (changed) {
402 selectionSnapshot = null;
403 }
404 }
405 if (changed) {
406 fireSelectionChanged();
407 }
408 }
409 public void toggleSelected(PrimitiveId... osm) {
410 toggleSelected(Arrays.asList(osm));
411 }
412 private boolean __toggleSelected(PrimitiveId primitiveId) {
413 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
414 if (primitive == null)
415 return false;
416 if (!selectedPrimitives.remove(primitive)) {
417 selectedPrimitives.add(primitive);
418 }
419 selectionSnapshot = null;
420 return true;
421 }
422
423 /**
424 * Sets the current selection to the primitives in <code>selection</code>.
425 * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
426 *
427 * @param selection the selection
428 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
429 */
430 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
431 boolean changed;
432 synchronized (selectionLock) {
433 boolean wasEmpty = selectedPrimitives.isEmpty();
434 selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
435 changed = addSelected(selection, false)
436 || (!wasEmpty && selectedPrimitives.isEmpty());
437 if (changed) {
438 selectionSnapshot = null;
439 }
440 }
441
442 if (changed && fireSelectionChangeEvent) {
443 // If selection is not empty then event was already fired in addSelecteds
444 fireSelectionChanged();
445 }
446 }
447
448 /**
449 * Sets the current selection to the primitives in <code>selection</code>
450 * and notifies all {@see SelectionChangedListener}.
451 *
452 * @param selection the selection
453 */
454 public void setSelected(Collection<? extends PrimitiveId> selection) {
455 setSelected(selection, true /* fire selection change event */);
456 }
457
458 public void setSelected(PrimitiveId... osm) {
459 if (osm.length == 1 && osm[0] == null) {
460 setSelected();
461 return;
462 }
463 List<PrimitiveId> list = Arrays.asList(osm);
464 setSelected(list);
465 }
466
467 /**
468 * Adds the primitives in <code>selection</code> to the current selection
469 * and notifies all {@see SelectionChangedListener}.
470 *
471 * @param selection the selection
472 */
473 public void addSelected(Collection<? extends PrimitiveId> selection) {
474 addSelected(selection, true /* fire selection change event */);
475 }
476
477 public void addSelected(PrimitiveId... osm) {
478 addSelected(Arrays.asList(osm));
479 }
480
481 /**
482 * Adds the primitives in <code>selection</code> to the current selection.
483 * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
484 *
485 * @param selection the selection
486 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
487 * @return if the selection was changed in the process
488 */
489 private boolean addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
490 boolean changed = false;
491 synchronized (selectionLock) {
492 for (PrimitiveId id: selection) {
493 OsmPrimitive primitive = getPrimitiveByIdChecked(id);
494 if (primitive != null) {
495 changed = changed | selectedPrimitives.add(primitive);
496 }
497 }
498 if (changed) {
499 selectionSnapshot = null;
500 }
501 }
502 if (fireSelectionChangeEvent && changed) {
503 fireSelectionChanged();
504 }
505 return changed;
506 }
507
508 /**
509 * Remove the selection from every value in the collection.
510 * @param list The collection to remove the selection from.
511 */
512 public void clearSelection(PrimitiveId... osm) {
513 clearSelection(Arrays.asList(osm));
514 }
515 public void clearSelection(Collection<? extends PrimitiveId> list) {
516 boolean changed = false;
517 synchronized (selectionLock) {
518 for (PrimitiveId id:list) {
519 OsmPrimitive primitive = getPrimitiveById(id);
520 if (primitive != null) {
521 changed = changed | selectedPrimitives.remove(primitive);
522 }
523 }
524 if (changed) {
525 selectionSnapshot = null;
526 }
527 }
528 if (changed) {
529 fireSelectionChanged();
530 }
531 }
532 public void clearSelection() {
533 if (!selectedPrimitives.isEmpty()) {
534 synchronized (selectionLock) {
535 selectedPrimitives.clear();
536 selectionSnapshot = null;
537 }
538 fireSelectionChanged();
539 }
540 }
541
542 @Override public DataSet clone() {
543 getReadLock().lock();
544 try {
545 DataSet ds = new DataSet();
546 HashMap<OsmPrimitive, OsmPrimitive> primMap = new HashMap<OsmPrimitive, OsmPrimitive>();
547 for (Node n : nodes) {
548 Node newNode = new Node(n);
549 primMap.put(n, newNode);
550 ds.addPrimitive(newNode);
551 }
552 for (Way w : ways) {
553 Way newWay = new Way(w);
554 primMap.put(w, newWay);
555 List<Node> newNodes = new ArrayList<Node>();
556 for (Node n: w.getNodes()) {
557 newNodes.add((Node)primMap.get(n));
558 }
559 newWay.setNodes(newNodes);
560 ds.addPrimitive(newWay);
561 }
562 // Because relations can have other relations as members we first clone all relations
563 // and then get the cloned members
564 for (Relation r : relations) {
565 Relation newRelation = new Relation(r, r.isNew());
566 newRelation.setMembers(null);
567 primMap.put(r, newRelation);
568 ds.addPrimitive(newRelation);
569 }
570 for (Relation r : relations) {
571 Relation newRelation = (Relation)primMap.get(r);
572 List<RelationMember> newMembers = new ArrayList<RelationMember>();
573 for (RelationMember rm: r.getMembers()) {
574 newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
575 }
576 newRelation.setMembers(newMembers);
577 }
578 for (DataSource source : dataSources) {
579 ds.dataSources.add(new DataSource(source.bounds, source.origin));
580 }
581 ds.version = version;
582 return ds;
583 } finally {
584 getReadLock().unlock();
585 }
586 }
587
588 /**
589 * Returns the total area of downloaded data (the "yellow rectangles").
590 * @return Area object encompassing downloaded data.
591 */
592 public Area getDataSourceArea() {
593 if (dataSources.isEmpty()) return null;
594 Area a = new Area();
595 for (DataSource source : dataSources) {
596 // create area from data bounds
597 a.add(new Area(source.bounds.asRect()));
598 }
599 return a;
600 }
601
602 /**
603 * returns a primitive with a given id from the data set. null, if no such primitive
604 * exists
605 *
606 * @param id uniqueId of the primitive. Might be < 0 for newly created primitives
607 * @param type the type of the primitive. Must not be null.
608 * @return the primitive
609 * @exception NullPointerException thrown, if type is null
610 */
611 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
612 return getPrimitiveById(new SimplePrimitiveId(id, type));
613 }
614
615 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
616 return primitivesMap.get(primitiveId);
617 }
618
619 /**
620 *
621 * @param primitiveId
622 * @param createNew
623 * @return
624 * @deprecated This method can created inconsistent dataset when called for node with id < 0 and createNew=true. That will add
625 * complete node without coordinates to dataset which is not allowed.
626 */
627 @Deprecated
628 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId, boolean createNew) {
629 OsmPrimitive result = primitivesMap.get(primitiveId);
630
631 if (result == null && createNew) {
632 switch (primitiveId.getType()) {
633 case NODE: result = new Node(primitiveId.getUniqueId(), true); break;
634 case WAY: result = new Way(primitiveId.getUniqueId(), true); break;
635 case RELATION: result = new Relation(primitiveId.getUniqueId(), true); break;
636 }
637 addPrimitive(result);
638 }
639
640 return result;
641 }
642
643 /**
644 * Show message and stack trace in log in case primitive is not found
645 * @param primitiveId
646 * @return Primitive by id.
647 */
648 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
649 OsmPrimitive result = getPrimitiveById(primitiveId);
650 if (result == null) {
651 System.out.println(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
652 + " at http://josm.openstreetmap.de . This is not a critical error, it should be safe to continue in your work.",
653 primitiveId.getType(), Long.toString(primitiveId.getUniqueId())));
654 new Exception().printStackTrace();
655 }
656
657 return result;
658 }
659
660 private void deleteWay(Way way) {
661 way.setNodes(null);
662 way.setDeleted(true);
663 }
664
665 /**
666 * removes all references from ways in this dataset to a particular node
667 *
668 * @param node the node
669 */
670 public void unlinkNodeFromWays(Node node) {
671 beginUpdate();
672 try {
673 for (Way way: ways) {
674 List<Node> wayNodes = way.getNodes();
675 if (wayNodes.remove(node)) {
676 if (wayNodes.size() < 2) {
677 deleteWay(way);
678 } else {
679 way.setNodes(wayNodes);
680 }
681 }
682 }
683 } finally {
684 endUpdate();
685 }
686 }
687
688 /**
689 * removes all references from relations in this dataset to this primitive
690 *
691 * @param primitive the primitive
692 */
693 public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
694 beginUpdate();
695 try {
696 for (Relation relation : relations) {
697 List<RelationMember> members = relation.getMembers();
698
699 Iterator<RelationMember> it = members.iterator();
700 boolean removed = false;
701 while(it.hasNext()) {
702 RelationMember member = it.next();
703 if (member.getMember().equals(primitive)) {
704 it.remove();
705 removed = true;
706 }
707 }
708
709 if (removed) {
710 relation.setMembers(members);
711 }
712 }
713 } finally {
714 endUpdate();
715 }
716 }
717
718 /**
719 * removes all references from other primitives to the
720 * referenced primitive
721 *
722 * @param referencedPrimitive the referenced primitive
723 */
724 public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
725 beginUpdate();
726 try {
727 if (referencedPrimitive instanceof Node) {
728 unlinkNodeFromWays((Node)referencedPrimitive);
729 unlinkPrimitiveFromRelations(referencedPrimitive);
730 } else {
731 unlinkPrimitiveFromRelations(referencedPrimitive);
732 }
733 } finally {
734 endUpdate();
735 }
736 }
737
738 /**
739 * Replies true if there is at least one primitive in this dataset with
740 * {@see OsmPrimitive#isModified()} == <code>true</code>.
741 *
742 * @return true if there is at least one primitive in this dataset with
743 * {@see OsmPrimitive#isModified()} == <code>true</code>.
744 */
745 public boolean isModified() {
746 for (OsmPrimitive p: allPrimitives) {
747 if (p.isModified())
748 return true;
749 }
750 return false;
751 }
752
753 private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
754 if (!nodes.remove(node))
755 throw new RuntimeException("Reindexing node failed to remove");
756 node.setCoorInternal(newCoor, eastNorth);
757 if (!nodes.add(node))
758 throw new RuntimeException("Reindexing node failed to add");
759 for (OsmPrimitive primitive: node.getReferrers()) {
760 if (primitive instanceof Way) {
761 reindexWay((Way)primitive);
762 } else {
763 reindexRelation((Relation) primitive);
764 }
765 }
766 }
767
768 private void reindexWay(Way way) {
769 BBox before = way.getBBox();
770 if (!ways.remove(way))
771 throw new RuntimeException("Reindexing way failed to remove");
772 way.updatePosition();
773 if (!ways.add(way))
774 throw new RuntimeException("Reindexing way failed to add");
775 if (!way.getBBox().equals(before)) {
776 for (OsmPrimitive primitive: way.getReferrers()) {
777 reindexRelation((Relation)primitive);
778 }
779 }
780 }
781
782 private void reindexRelation(Relation relation) {
783 BBox before = relation.getBBox();
784 relation.updatePosition();
785 if (!before.equals(relation.getBBox())) {
786 for (OsmPrimitive primitive: relation.getReferrers()) {
787 reindexRelation((Relation) primitive);
788 }
789 }
790 }
791
792 public void addDataSetListener(DataSetListener dsl) {
793 listeners.addIfAbsent(dsl);
794 }
795
796 public void removeDataSetListener(DataSetListener dsl) {
797 listeners.remove(dsl);
798 }
799
800 /**
801 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
802 * {@link DataSetListener#dataChanged()} event is triggered after end of changes
803 * <br>
804 * Typical usecase should look like this:
805 * <pre>
806 * ds.beginUpdate();
807 * try {
808 * ...
809 * } finally {
810 * ds.endUpdate();
811 * }
812 * </pre>
813 */
814 public void beginUpdate() {
815 lock.writeLock().lock();
816 updateCount++;
817 }
818
819 /**
820 * @see DataSet#beginUpdate()
821 */
822 public void endUpdate() {
823 if (updateCount > 0) {
824 updateCount--;
825 if (updateCount == 0) {
826 List<AbstractDatasetChangedEvent> eventsCopy = new ArrayList<AbstractDatasetChangedEvent>(cachedEvents);
827 cachedEvents.clear();
828 lock.writeLock().unlock();
829
830 if (!eventsCopy.isEmpty()) {
831 lock.readLock().lock();
832 try {
833 if (eventsCopy.size() < MAX_SINGLE_EVENTS) {
834 for (AbstractDatasetChangedEvent event: eventsCopy) {
835 fireEventToListeners(event);
836 }
837 } else if (eventsCopy.size() == MAX_EVENTS) {
838 fireEventToListeners(new DataChangedEvent(this));
839 } else {
840 fireEventToListeners(new DataChangedEvent(this, eventsCopy));
841 }
842 } finally {
843 lock.readLock().unlock();
844 }
845 }
846 } else {
847 lock.writeLock().unlock();
848 }
849
850 } else
851 throw new AssertionError("endUpdate called without beginUpdate");
852 }
853
854 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
855 for (DataSetListener listener: listeners) {
856 event.fire(listener);
857 }
858 }
859
860 private void fireEvent(AbstractDatasetChangedEvent event) {
861 if (updateCount == 0)
862 throw new AssertionError("dataset events can be fired only when dataset is locked");
863 if (cachedEvents.size() < MAX_EVENTS) {
864 cachedEvents.add(event);
865 }
866 }
867
868 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
869 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
870 }
871
872 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
873 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
874 }
875
876 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
877 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
878 }
879
880 void fireRelationMembersChanged(Relation r) {
881 reindexRelation(r);
882 fireEvent(new RelationMembersChangedEvent(this, r));
883 }
884
885 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
886 reindexNode(node, newCoor, eastNorth);
887 fireEvent(new NodeMovedEvent(this, node));
888 }
889
890 void fireWayNodesChanged(Way way) {
891 reindexWay(way);
892 fireEvent(new WayNodesChangedEvent(this, way));
893 }
894
895 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
896 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
897 }
898
899 void fireHighlightingChanged(OsmPrimitive primitive) {
900 highlightUpdateCount++;
901 }
902
903 public void cleanupDeletedPrimitives() {
904 beginUpdate();
905 try {
906 if (cleanupDeleted(nodes.iterator())
907 | cleanupDeleted(ways.iterator())
908 | cleanupDeleted(relations.iterator())) {
909 fireSelectionChanged();
910 }
911 } finally {
912 endUpdate();
913 }
914 }
915
916 private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
917 boolean changed = false;
918 synchronized (selectionLock) {
919 while (it.hasNext()) {
920 OsmPrimitive primitive = it.next();
921 if (primitive.isDeleted() && !primitive.isVisible()) {
922 selectedPrimitives.remove(primitive);
923 selectionSnapshot = null;
924 allPrimitives.remove(primitive);
925 primitive.setDataset(null);
926 changed = true;
927 it.remove();
928 }
929 }
930 if (changed) {
931 selectionSnapshot = null;
932 }
933 }
934 return changed;
935 }
936
937 /**
938 * Removes all primitives from the dataset and resets the currently selected primitives
939 * to the empty collection. Also notifies selection change listeners if necessary.
940 *
941 */
942 public void clear() {
943 beginUpdate();
944 try {
945 clearSelection();
946 for (OsmPrimitive primitive:allPrimitives) {
947 primitive.setDataset(null);
948 }
949 nodes.clear();
950 ways.clear();
951 relations.clear();
952 allPrimitives.clear();
953 } finally {
954 endUpdate();
955 }
956 }
957
958 /**
959 * Marks all "invisible" objects as deleted. These objects should be always marked as
960 * deleted when downloaded from the server. They can be undeleted later if necessary.
961 *
962 */
963 public void deleteInvisible() {
964 for (OsmPrimitive primitive:allPrimitives) {
965 if (!primitive.isVisible()) {
966 primitive.setDeleted(true);
967 }
968 }
969 }
970}
Note: See TracBrowser for help on using the repository browser.