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

Last change on this file since 3801 was 3801, checked in by bastiK, 13 years ago

generalize DatasetCollection.java (make OsmPrimitive a generic type parameter)

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