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

Last change on this file since 5674 was 5674, checked in by jttt, 11 years ago

Move IdHash to Storage class and rename to PrimitiveIdHash to make it easier for other classes to use Storage class

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