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

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

added purge action (some testing would be welcome)

  • Property svn:eol-style set to native
File size: 33.0 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(), 16, 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 if (primitive instanceof Node) {
247 nodes.add((Node) primitive);
248 } else if (primitive instanceof Way) {
249 ways.add((Way) primitive);
250 } else if (primitive instanceof Relation) {
251 relations.add((Relation) primitive);
252 }
253 allPrimitives.add(primitive);
254 primitive.setDataset(this);
255 firePrimitivesAdded(Collections.singletonList(primitive), false);
256 } finally {
257 endUpdate();
258 }
259 }
260
261 public OsmPrimitive addPrimitive(PrimitiveData data) {
262 beginUpdate();
263 try {
264 OsmPrimitive result;
265 if (data instanceof NodeData) {
266 result = new Node();
267 } else if (data instanceof WayData) {
268 result = new Way();
269 } else if (data instanceof RelationData) {
270 result = new Relation();
271 } else
272 throw new AssertionError();
273 result.setDataset(this);
274 result.load(data);
275 addPrimitive(result);
276 return result;
277 } finally {
278 endUpdate();
279 }
280 }
281
282 /**
283 * Removes a primitive from the dataset. This method only removes the
284 * primitive form the respective collection of primitives managed
285 * by this dataset, i.e. from {@see #nodes}, {@see #ways}, or
286 * {@see #relations}. References from other primitives to this
287 * primitive are left unchanged.
288 *
289 * @param primitive the primitive
290 */
291 public void removePrimitive(PrimitiveId primitiveId) {
292 beginUpdate();
293 try {
294 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
295 if (primitive == null)
296 return;
297 if (primitive instanceof Node) {
298 nodes.remove(primitive);
299 } else if (primitive instanceof Way) {
300 ways.remove(primitive);
301 } else if (primitive instanceof Relation) {
302 relations.remove(primitive);
303 }
304 synchronized (selectionLock) {
305 selectedPrimitives.remove(primitive);
306 selectionSnapshot = null;
307 }
308 allPrimitives.remove(primitive);
309 primitive.setDataset(null);
310 firePrimitivesRemoved(Collections.singletonList(primitive), false);
311 } finally {
312 endUpdate();
313 }
314 }
315
316 /*---------------------------------------------------
317 * SELECTION HANDLING
318 *---------------------------------------------------*/
319
320 /**
321 * A list of listeners to selection changed events. The list is static, as listeners register
322 * themselves for any dataset selection changes that occur, regardless of the current active
323 * dataset. (However, the selection does only change in the active layer)
324 * @deprecated Use addSelectionListener/removeSelectionListener instead
325 */
326 @Deprecated
327 public static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<SelectionChangedListener>();
328
329 public static void addSelectionListener(SelectionChangedListener listener) {
330 ((CopyOnWriteArrayList<SelectionChangedListener>)selListeners).addIfAbsent(listener);
331 }
332
333 public static void removeSelectionListener(SelectionChangedListener listener) {
334 selListeners.remove(listener);
335 }
336
337 /**
338 * Notifies all registered {@see SelectionChangedListener} about the current selection in
339 * this dataset.
340 *
341 */
342 public void fireSelectionChanged(){
343 synchronized (selListeners) {
344 Collection<? extends OsmPrimitive> currentSelection = getSelected();
345 for (SelectionChangedListener l : selListeners) {
346 l.selectionChanged(currentSelection);
347 }
348 }
349 }
350
351 private LinkedHashSet<OsmPrimitive> selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
352 private Collection<OsmPrimitive> selectionSnapshot;
353
354 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
355 return new DatasetCollection<OsmPrimitive>(getSelected(), new Predicate<OsmPrimitive>() {
356 @Override
357 public boolean evaluate(OsmPrimitive primitive) {
358 return primitive instanceof Node || primitive instanceof Way;
359 }
360 });
361 }
362
363 /**
364 * Replies an unmodifiable collection of primitives currently selected
365 * in this dataset
366 *
367 * @return unmodifiable collection of primitives
368 */
369 public Collection<OsmPrimitive> getSelected() {
370 Collection<OsmPrimitive> currentList;
371 synchronized (selectionLock) {
372 if (selectionSnapshot == null) {
373 selectionSnapshot = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(selectedPrimitives));
374 }
375 currentList = selectionSnapshot;
376 }
377 return currentList;
378 }
379
380 /**
381 * Return selected nodes.
382 */
383 public Collection<Node> getSelectedNodes() {
384 return new DatasetCollection<Node>(getSelected(), OsmPrimitive.nodePredicate);
385 }
386
387 /**
388 * Return selected ways.
389 */
390 public Collection<Way> getSelectedWays() {
391 return new DatasetCollection<Way>(getSelected(), OsmPrimitive.wayPredicate);
392 }
393
394 /**
395 * Return selected relations.
396 */
397 public Collection<Relation> getSelectedRelations() {
398 return new DatasetCollection<Relation>(getSelected(), OsmPrimitive.relationPredicate);
399 }
400
401 /**
402 * @return whether the selection is empty or not
403 */
404 public boolean selectionEmpty() {
405 return selectedPrimitives.isEmpty();
406 }
407
408 public boolean isSelected(OsmPrimitive osm) {
409 return selectedPrimitives.contains(osm);
410 }
411
412 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
413 boolean changed = false;
414 synchronized (selectionLock) {
415 for (PrimitiveId o : osm) {
416 changed = changed | this.__toggleSelected(o);
417 }
418 if (changed) {
419 selectionSnapshot = null;
420 }
421 }
422 if (changed) {
423 fireSelectionChanged();
424 }
425 }
426 public void toggleSelected(PrimitiveId... osm) {
427 toggleSelected(Arrays.asList(osm));
428 }
429 private boolean __toggleSelected(PrimitiveId primitiveId) {
430 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
431 if (primitive == null)
432 return false;
433 if (!selectedPrimitives.remove(primitive)) {
434 selectedPrimitives.add(primitive);
435 }
436 selectionSnapshot = null;
437 return true;
438 }
439
440 /**
441 * Sets the current selection to the primitives in <code>selection</code>.
442 * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
443 *
444 * @param selection the selection
445 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
446 */
447 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
448 boolean wasEmpty;
449 synchronized (selectionLock) {
450 wasEmpty = selectedPrimitives.isEmpty();
451 selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
452 addSelected(selection, fireSelectionChangeEvent);
453 if (!wasEmpty && selectedPrimitives.isEmpty()) {
454 selectionSnapshot = null;
455 }
456 }
457
458 if (!wasEmpty && selectedPrimitives.isEmpty() && fireSelectionChangeEvent) {
459 // If selection is not empty then event was already fired in addSelecteds
460 fireSelectionChanged();
461 }
462 }
463
464 /**
465 * Sets the current selection to the primitives in <code>selection</code>
466 * and notifies all {@see SelectionChangedListener}.
467 *
468 * @param selection the selection
469 */
470 public void setSelected(Collection<? extends PrimitiveId> selection) {
471 setSelected(selection, true /* fire selection change event */);
472 }
473
474 public void setSelected(PrimitiveId... osm) {
475 if (osm.length == 1 && osm[0] == null) {
476 setSelected();
477 return;
478 }
479 List<PrimitiveId> list = Arrays.asList(osm);
480 setSelected(list);
481 }
482
483 /**
484 * Adds the primitives in <code>selection</code> to the current selection
485 * and notifies all {@see SelectionChangedListener}.
486 *
487 * @param selection the selection
488 */
489 public void addSelected(Collection<? extends PrimitiveId> selection) {
490 addSelected(selection, true /* fire selection change event */);
491 }
492
493 public void addSelected(PrimitiveId... osm) {
494 addSelected(Arrays.asList(osm));
495 }
496
497 /**
498 * Adds the primitives in <code>selection</code> to the current selection.
499 * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
500 *
501 * @param selection the selection
502 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
503 */
504 public void addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
505 boolean changed = false;
506 synchronized (selectionLock) {
507 for (PrimitiveId id: selection) {
508 OsmPrimitive primitive = getPrimitiveByIdChecked(id);
509 if (primitive != null) {
510 changed = changed | selectedPrimitives.add(primitive);
511 }
512 }
513 if (changed) {
514 selectionSnapshot = null;
515 }
516 }
517 if (fireSelectionChangeEvent && changed) {
518 fireSelectionChanged();
519 }
520 }
521
522 /**
523 * Remove the selection from every value in the collection.
524 * @param list The collection to remove the selection from.
525 */
526 public void clearSelection(PrimitiveId... osm) {
527 clearSelection(Arrays.asList(osm));
528 }
529 public void clearSelection(Collection<? extends PrimitiveId> list) {
530 boolean changed = false;
531 synchronized (selectionLock) {
532 for (PrimitiveId id:list) {
533 OsmPrimitive primitive = getPrimitiveById(id);
534 if (primitive != null) {
535 changed = changed | selectedPrimitives.remove(primitive);
536 }
537 }
538 if (changed) {
539 selectionSnapshot = null;
540 }
541 }
542 if (changed) {
543 fireSelectionChanged();
544 }
545 }
546 public void clearSelection() {
547 if (!selectedPrimitives.isEmpty()) {
548 synchronized (selectionLock) {
549 selectedPrimitives.clear();
550 selectionSnapshot = null;
551 }
552 fireSelectionChanged();
553 }
554 }
555
556 @Override public DataSet clone() {
557 getReadLock().lock();
558 try {
559 DataSet ds = new DataSet();
560 HashMap<OsmPrimitive, OsmPrimitive> primitivesMap = new HashMap<OsmPrimitive, OsmPrimitive>();
561 for (Node n : nodes) {
562 Node newNode = new Node(n);
563 primitivesMap.put(n, newNode);
564 ds.addPrimitive(newNode);
565 }
566 for (Way w : ways) {
567 Way newWay = new Way(w);
568 primitivesMap.put(w, newWay);
569 List<Node> newNodes = new ArrayList<Node>();
570 for (Node n: w.getNodes()) {
571 newNodes.add((Node)primitivesMap.get(n));
572 }
573 newWay.setNodes(newNodes);
574 ds.addPrimitive(newWay);
575 }
576 // Because relations can have other relations as members we first clone all relations
577 // and then get the cloned members
578 for (Relation r : relations) {
579 Relation newRelation = new Relation(r, r.isNew());
580 newRelation.setMembers(null);
581 primitivesMap.put(r, newRelation);
582 ds.addPrimitive(newRelation);
583 }
584 for (Relation r : relations) {
585 Relation newRelation = (Relation)primitivesMap.get(r);
586 List<RelationMember> newMembers = new ArrayList<RelationMember>();
587 for (RelationMember rm: r.getMembers()) {
588 newMembers.add(new RelationMember(rm.getRole(), primitivesMap.get(rm.getMember())));
589 }
590 newRelation.setMembers(newMembers);
591 }
592 for (DataSource source : dataSources) {
593 ds.dataSources.add(new DataSource(source.bounds, source.origin));
594 }
595 ds.version = version;
596 return ds;
597 } finally {
598 getReadLock().unlock();
599 }
600 }
601
602 /**
603 * Returns the total area of downloaded data (the "yellow rectangles").
604 * @return Area object encompassing downloaded data.
605 */
606 public Area getDataSourceArea() {
607 if (dataSources.isEmpty()) return null;
608 Area a = new Area();
609 for (DataSource source : dataSources) {
610 // create area from data bounds
611 a.add(new Area(source.bounds.asRect()));
612 }
613 return a;
614 }
615
616 /**
617 * returns a primitive with a given id from the data set. null, if no such primitive
618 * exists
619 *
620 * @param id uniqueId of the primitive. Might be < 0 for newly created primitives
621 * @param type the type of the primitive. Must not be null.
622 * @return the primitive
623 * @exception NullPointerException thrown, if type is null
624 */
625 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
626 return getPrimitiveById(new SimplePrimitiveId(id, type), false);
627 }
628
629 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
630 return getPrimitiveById(primitiveId, false);
631 }
632
633 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId, boolean createNew) {
634 OsmPrimitive result = primitivesMap.get(primitiveId);
635
636 if (result == null && createNew) {
637 switch (primitiveId.getType()) {
638 case NODE: result = new Node(primitiveId.getUniqueId(), true); break;
639 case WAY: result = new Way(primitiveId.getUniqueId(), true); break;
640 case RELATION: result = new Relation(primitiveId.getUniqueId(), true); break;
641 }
642 addPrimitive(result);
643 }
644
645 return result;
646 }
647
648 /**
649 * Show message and stack trace in log in case primitive is not found
650 * @param primitiveId
651 * @return Primitive by id.
652 */
653 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
654 OsmPrimitive result = getPrimitiveById(primitiveId);
655 if (result == null) {
656 System.out.println(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
657 + " at http://josm.openstreetmap.de . This is not a critical error, it should be safe to continue in your work.",
658 primitiveId.getType(), Long.toString(primitiveId.getUniqueId())));
659 new Exception().printStackTrace();
660 }
661
662 return result;
663 }
664
665 private void deleteWay(Way way) {
666 way.setNodes(null);
667 way.setDeleted(true);
668 }
669
670 /**
671 * removes all references from ways in this dataset to a particular node
672 *
673 * @param node the node
674 */
675 public void unlinkNodeFromWays(Node node) {
676 beginUpdate();
677 try {
678 for (Way way: ways) {
679 List<Node> nodes = way.getNodes();
680 if (nodes.remove(node)) {
681 if (nodes.size() < 2) {
682 deleteWay(way);
683 } else {
684 way.setNodes(nodes);
685 }
686 }
687 }
688 } finally {
689 endUpdate();
690 }
691 }
692
693 /**
694 * removes all references from relations in this dataset to this primitive
695 *
696 * @param primitive the primitive
697 */
698 public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
699 beginUpdate();
700 try {
701 for (Relation relation : relations) {
702 List<RelationMember> members = relation.getMembers();
703
704 Iterator<RelationMember> it = members.iterator();
705 boolean removed = false;
706 while(it.hasNext()) {
707 RelationMember member = it.next();
708 if (member.getMember().equals(primitive)) {
709 it.remove();
710 removed = true;
711 }
712 }
713
714 if (removed) {
715 relation.setMembers(members);
716 }
717 }
718 } finally {
719 endUpdate();
720 }
721 }
722
723 /**
724 * removes all references from from other primitives to the
725 * referenced primitive
726 *
727 * @param referencedPrimitive the referenced primitive
728 */
729 public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
730 beginUpdate();
731 try {
732 if (referencedPrimitive instanceof Node) {
733 unlinkNodeFromWays((Node)referencedPrimitive);
734 unlinkPrimitiveFromRelations(referencedPrimitive);
735 } else {
736 unlinkPrimitiveFromRelations(referencedPrimitive);
737 }
738 } finally {
739 endUpdate();
740 }
741 }
742
743 /**
744 * Replies true if there is at least one primitive in this dataset with
745 * {@see OsmPrimitive#isModified()} == <code>true</code>.
746 *
747 * @return true if there is at least one primitive in this dataset with
748 * {@see OsmPrimitive#isModified()} == <code>true</code>.
749 */
750 public boolean isModified() {
751 for (Node n: nodes) {
752 if (n.isModified()) return true;
753 }
754 for (Way w: ways) {
755 if (w.isModified()) return true;
756 }
757 for (Relation r: relations) {
758 if (r.isModified()) return true;
759 }
760 return false;
761 }
762
763 private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
764 nodes.remove(node);
765 node.setCoorInternal(newCoor, eastNorth);
766 nodes.add(node);
767 for (OsmPrimitive primitive: node.getReferrers()) {
768 if (primitive instanceof Way) {
769 reindexWay((Way)primitive);
770 } else {
771 reindexRelation((Relation) primitive);
772 }
773 }
774 }
775
776 private void reindexWay(Way way) {
777 BBox before = way.getBBox();
778 ways.remove(way);
779 way.updatePosition();
780 ways.add(way);
781 if (!way.getBBox().equals(before)) {
782 for (OsmPrimitive primitive: way.getReferrers()) {
783 reindexRelation((Relation)primitive);
784 }
785 }
786 }
787
788 private void reindexRelation(Relation relation) {
789 BBox before = relation.getBBox();
790 relation.updatePosition();
791 if (!before.equals(relation.getBBox())) {
792 for (OsmPrimitive primitive: relation.getReferrers()) {
793 reindexRelation((Relation) primitive);
794 }
795 }
796 }
797
798 public void addDataSetListener(DataSetListener dsl) {
799 listeners.addIfAbsent(dsl);
800 }
801
802 public void removeDataSetListener(DataSetListener dsl) {
803 listeners.remove(dsl);
804 }
805
806 /**
807 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
808 * {@link DataSetListener#dataChanged()} event is triggered after end of changes
809 * <br>
810 * Typical usecase should look like this:
811 * <pre>
812 * ds.beginUpdate();
813 * try {
814 * ...
815 * } finally {
816 * ds.endUpdate();
817 * }
818 * </pre>
819 */
820 public void beginUpdate() {
821 lock.writeLock().lock();
822 updateCount++;
823 }
824
825 /**
826 * @see DataSet#beginUpdate()
827 */
828 public void endUpdate() {
829 if (updateCount > 0) {
830 updateCount--;
831 if (updateCount == 0) {
832 List<AbstractDatasetChangedEvent> eventsCopy = new ArrayList<AbstractDatasetChangedEvent>(cachedEvents);
833 cachedEvents.clear();
834 lock.writeLock().unlock();
835
836 if (!eventsCopy.isEmpty()) {
837 lock.readLock().lock();
838 try {
839 if (eventsCopy.size() < MAX_SINGLE_EVENTS) {
840 for (AbstractDatasetChangedEvent event: eventsCopy) {
841 fireEventToListeners(event);
842 }
843 } else if (eventsCopy.size() == MAX_EVENTS) {
844 fireEventToListeners(new DataChangedEvent(this));
845 } else {
846 fireEventToListeners(new DataChangedEvent(this, eventsCopy));
847 }
848 } finally {
849 lock.readLock().unlock();
850 }
851 }
852 } else {
853 lock.writeLock().unlock();
854 }
855
856 } else
857 throw new AssertionError("endUpdate called without beginUpdate");
858 }
859
860 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
861 for (DataSetListener listener: listeners) {
862 event.fire(listener);
863 }
864 }
865
866 private void fireEvent(AbstractDatasetChangedEvent event) {
867 if (updateCount == 0)
868 throw new AssertionError("dataset events can be fired only when dataset is locked");
869 if (cachedEvents.size() < MAX_EVENTS) {
870 cachedEvents.add(event);
871 }
872 }
873
874 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
875 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
876 }
877
878 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
879 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
880 }
881
882 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
883 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
884 }
885
886 void fireRelationMembersChanged(Relation r) {
887 reindexRelation(r);
888 fireEvent(new RelationMembersChangedEvent(this, r));
889 }
890
891 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
892 reindexNode(node, newCoor, eastNorth);
893 fireEvent(new NodeMovedEvent(this, node));
894 }
895
896 void fireWayNodesChanged(Way way) {
897 reindexWay(way);
898 fireEvent(new WayNodesChangedEvent(this, way));
899 }
900
901 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
902 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
903 }
904
905 void fireHighlightingChanged(OsmPrimitive primitive) {
906 highlightUpdateCount++;
907 }
908
909 public void cleanupDeletedPrimitives() {
910 beginUpdate();
911 try {
912 if (cleanupDeleted(nodes.iterator())
913 | cleanupDeleted(ways.iterator())
914 | cleanupDeleted(relations.iterator())) {
915 fireSelectionChanged();
916 }
917 } finally {
918 endUpdate();
919 }
920 }
921
922 private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
923 boolean changed = false;
924 synchronized (selectionLock) {
925 while (it.hasNext()) {
926 OsmPrimitive primitive = it.next();
927 if (primitive.isDeleted() && !primitive.isVisible()) {
928 selectedPrimitives.remove(primitive);
929 selectionSnapshot = null;
930 allPrimitives.remove(primitive);
931 primitive.setDataset(null);
932 changed = true;
933 it.remove();
934 }
935 }
936 if (changed) {
937 selectionSnapshot = null;
938 }
939 }
940 return changed;
941 }
942
943 /**
944 * Removes all primitives from the dataset and resets the currently selected primitives
945 * to the empty collection. Also notifies selection change listeners if necessary.
946 *
947 */
948 public void clear() {
949 beginUpdate();
950 try {
951 clearSelection();
952 for (OsmPrimitive primitive:allPrimitives) {
953 primitive.setDataset(null);
954 }
955 nodes.clear();
956 ways.clear();
957 relations.clear();
958 allPrimitives.clear();
959 } finally {
960 endUpdate();
961 }
962 }
963
964 /**
965 * Marks all "invisible" objects as deleted. These objects should be always marked as
966 * deleted when downloaded from the server. They can be undeleted later if necessary.
967 *
968 */
969 public void deleteInvisible() {
970 for (OsmPrimitive primitive:allPrimitives) {
971 if (!primitive.isVisible()) {
972 primitive.setDeleted(true);
973 }
974 }
975 }
976}
Note: See TracBrowser for help on using the repository browser.