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

Last change on this file since 7392 was 7073, checked in by bastiK, 10 years ago

see #9691 - really fix broken MapCSSTagChecker unit test

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