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

Last change on this file since 12479 was 12479, checked in by Don-vip, 7 years ago

see #14833 - fix DataSet copy constructor, add unit test

  • Property svn:eol-style set to native
File size: 47.3 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.Collection;
9import java.util.Collections;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Objects;
17import java.util.Set;
18import java.util.concurrent.CopyOnWriteArrayList;
19import java.util.concurrent.locks.Lock;
20import java.util.concurrent.locks.ReadWriteLock;
21import java.util.concurrent.locks.ReentrantReadWriteLock;
22import java.util.function.Function;
23import java.util.function.Predicate;
24import java.util.stream.Stream;
25
26import org.openstreetmap.josm.Main;
27import org.openstreetmap.josm.data.Bounds;
28import org.openstreetmap.josm.data.Data;
29import org.openstreetmap.josm.data.DataSource;
30import org.openstreetmap.josm.data.ProjectionBounds;
31import org.openstreetmap.josm.data.SelectionChangedListener;
32import org.openstreetmap.josm.data.coor.EastNorth;
33import org.openstreetmap.josm.data.coor.LatLon;
34import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent;
35import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent;
36import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent;
37import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent;
38import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent;
39import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
40import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
41import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
42import org.openstreetmap.josm.data.osm.event.DataSetListener;
43import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
44import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent;
45import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
46import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
47import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
48import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
49import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
50import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
51import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
52import org.openstreetmap.josm.data.projection.Projection;
53import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
54import org.openstreetmap.josm.gui.progress.ProgressMonitor;
55import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
56import org.openstreetmap.josm.tools.ListenerList;
57import org.openstreetmap.josm.tools.SubclassFilteredCollection;
58
59/**
60 * DataSet is the data behind the application. It can consists of only a few points up to the whole
61 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
62 *
63 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
64 * store some information.
65 *
66 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
67 * lead to data corruption or ConcurrentModificationException. However when for example one thread
68 * removes primitive and other thread try to add another primitive referring to the removed primitive,
69 * DataIntegrityException will occur.
70 *
71 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
72 * Dataset will not change. Sample usage:
73 * <code>
74 * ds.getReadLock().lock();
75 * try {
76 * // .. do something with dataset
77 * } finally {
78 * ds.getReadLock().unlock();
79 * }
80 * </code>
81 *
82 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
83 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
84 * reasons - GUI can be updated after all changes are done.
85 * Sample usage:
86 * <code>
87 * ds.beginUpdate()
88 * try {
89 * // .. do modifications
90 * } finally {
91 * ds.endUpdate();
92 * }
93 * </code>
94 *
95 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
96 * automatically.
97 *
98 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
99 * sample ticket
100 *
101 * @author imi
102 */
103public final class DataSet extends QuadBucketPrimitiveStore implements Data, ProjectionChangeListener {
104
105 /**
106 * Upload policy.
107 *
108 * Determines if upload to the OSM server is intended, discouraged, or
109 * disabled / blocked.
110 */
111 public enum UploadPolicy {
112 /**
113 * Normal dataset, upload intended.
114 */
115 NORMAL("true"),
116 /**
117 * Upload discouraged, for example when using or distributing a private dataset.
118 */
119 DISCOURAGED("false"),
120 /**
121 * Upload blocked.
122 * Upload options completely disabled. Intended for special cases
123 * where a warning dialog is not enough, see #12731.
124 *
125 * For the user, it shouldn't be too easy to disable this flag.
126 */
127 BLOCKED("never");
128
129 final String xmlFlag;
130
131 UploadPolicy(String xmlFlag) {
132 this.xmlFlag = xmlFlag;
133 }
134
135 /**
136 * Get the corresponding value of the <code>upload='...'</code> XML-attribute
137 * in the .osm file.
138 * @return value of the <code>upload</code> attribute
139 */
140 public String getXmlFlag() {
141 return xmlFlag;
142 }
143 }
144
145 /**
146 * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
147 */
148 private static final int MAX_SINGLE_EVENTS = 30;
149
150 /**
151 * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
152 */
153 private static final int MAX_EVENTS = 1000;
154
155 private final Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true);
156 private final Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new Storage.PrimitiveIdHash());
157 private final CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>();
158
159 // provide means to highlight map elements that are not osm primitives
160 private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>();
161 private Collection<WaySegment> highlightedWaySegments = new LinkedList<>();
162 private final ListenerList<HighlightUpdateListener> highlightUpdateListeners = ListenerList.create();
163
164 // Number of open calls to beginUpdate
165 private int updateCount;
166 // Events that occurred while dataset was locked but should be fired after write lock is released
167 private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>();
168
169 private UploadPolicy uploadPolicy;
170
171 private final ReadWriteLock lock = new ReentrantReadWriteLock();
172
173 /**
174 * The mutex lock that is used to synchronize selection changes.
175 */
176 private final Object selectionLock = new Object();
177 /**
178 * The current selected primitives. This is always a unmodifiable set.
179 *
180 * The set should be ordered in the order in which the primitives have been added to the selection.
181 */
182 private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet();
183
184 /**
185 * A list of listeners that listen to selection changes on this layer.
186 */
187 private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
188
189 private Area cachedDataSourceArea;
190 private List<Bounds> cachedDataSourceBounds;
191
192 /**
193 * All data sources of this DataSet.
194 */
195 private final Collection<DataSource> dataSources = new LinkedList<>();
196
197 /**
198 * Constructs a new {@code DataSet}.
199 */
200 public DataSet() {
201 // Transparently register as projection change listener. No need to explicitly remove
202 // the listener, projection change listeners are managed as WeakReferences.
203 Main.addProjectionChangeListener(this);
204 addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection()));
205 }
206
207 /**
208 * Creates a new {@link DataSet}.
209 * @param copyFrom An other {@link DataSet} to copy the contents of this dataset from.
210 * @since 10346
211 */
212 public DataSet(DataSet copyFrom) {
213 this();
214 copyFrom.getReadLock().lock();
215 try {
216 Map<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>();
217 for (Node n : copyFrom.getNodes()) {
218 Node newNode = new Node(n);
219 primMap.put(n, newNode);
220 addPrimitive(newNode);
221 }
222 for (Way w : copyFrom.getWays()) {
223 Way newWay = new Way(w);
224 primMap.put(w, newWay);
225 List<Node> newNodes = new ArrayList<>();
226 for (Node n: w.getNodes()) {
227 newNodes.add((Node) primMap.get(n));
228 }
229 newWay.setNodes(newNodes);
230 addPrimitive(newWay);
231 }
232 // Because relations can have other relations as members we first clone all relations
233 // and then get the cloned members
234 Collection<Relation> relations = copyFrom.getRelations();
235 for (Relation r : relations) {
236 Relation newRelation = new Relation(r);
237 newRelation.setMembers(null);
238 primMap.put(r, newRelation);
239 addPrimitive(newRelation);
240 }
241 for (Relation r : relations) {
242 Relation newRelation = (Relation) primMap.get(r);
243 List<RelationMember> newMembers = new ArrayList<>();
244 for (RelationMember rm: r.getMembers()) {
245 newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
246 }
247 newRelation.setMembers(newMembers);
248 }
249 for (DataSource source : copyFrom.dataSources) {
250 dataSources.add(new DataSource(source));
251 }
252 version = copyFrom.version;
253 uploadPolicy = copyFrom.uploadPolicy;
254 } finally {
255 copyFrom.getReadLock().unlock();
256 }
257 }
258
259 /**
260 * Adds a new data source.
261 * @param source data source to add
262 * @return {@code true} if the collection changed as a result of the call
263 * @since 11626
264 */
265 public synchronized boolean addDataSource(DataSource source) {
266 return addDataSources(Collections.singleton(source));
267 }
268
269 /**
270 * Adds new data sources.
271 * @param sources data sources to add
272 * @return {@code true} if the collection changed as a result of the call
273 * @since 11626
274 */
275 public synchronized boolean addDataSources(Collection<DataSource> sources) {
276 boolean changed = dataSources.addAll(sources);
277 if (changed) {
278 cachedDataSourceArea = null;
279 cachedDataSourceBounds = null;
280 }
281 return changed;
282 }
283
284 /**
285 * Returns the lock used for reading.
286 * @return the lock used for reading
287 */
288 public Lock getReadLock() {
289 return lock.readLock();
290 }
291
292 /**
293 * History of selections - shared by plugins and SelectionListDialog
294 */
295 private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>();
296
297 /**
298 * Replies the history of JOSM selections
299 *
300 * @return list of history entries
301 */
302 public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() {
303 return selectionHistory;
304 }
305
306 /**
307 * Clears selection history list
308 */
309 public void clearSelectionHistory() {
310 selectionHistory.clear();
311 }
312
313 /**
314 * Maintains a list of used tags for autocompletion.
315 */
316 private AutoCompletionManager autocomplete;
317
318 /**
319 * Returns the autocompletion manager, which maintains a list of used tags for autocompletion.
320 * @return the autocompletion manager
321 */
322 public AutoCompletionManager getAutoCompletionManager() {
323 if (autocomplete == null) {
324 autocomplete = new AutoCompletionManager(this);
325 addDataSetListener(autocomplete);
326 }
327 return autocomplete;
328 }
329
330 /**
331 * The API version that created this data set, if any.
332 */
333 private String version;
334
335 /**
336 * Replies the API version this dataset was created from. May be null.
337 *
338 * @return the API version this dataset was created from. May be null.
339 */
340 public String getVersion() {
341 return version;
342 }
343
344 /**
345 * Sets the API version this dataset was created from.
346 *
347 * @param version the API version, i.e. "0.6"
348 */
349 public void setVersion(String version) {
350 this.version = version;
351 }
352
353 /**
354 * Determines if upload is being discouraged.
355 * (i.e. this dataset contains private data which should not be uploaded)
356 * @return {@code true} if upload is being discouraged, {@code false} otherwise
357 * @see #setUploadDiscouraged
358 * @deprecated use {@link #getUploadPolicy()}
359 */
360 @Deprecated
361 public boolean isUploadDiscouraged() {
362 return uploadPolicy == UploadPolicy.DISCOURAGED || uploadPolicy == UploadPolicy.BLOCKED;
363 }
364
365 /**
366 * Sets the "upload discouraged" flag.
367 * @param uploadDiscouraged {@code true} if this dataset contains private data which should not be uploaded
368 * @see #isUploadDiscouraged
369 * @deprecated use {@link #setUploadPolicy(UploadPolicy)}
370 */
371 @Deprecated
372 public void setUploadDiscouraged(boolean uploadDiscouraged) {
373 if (uploadPolicy != UploadPolicy.BLOCKED) {
374 this.uploadPolicy = uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL;
375 }
376 }
377
378 /**
379 * Get the upload policy.
380 * @return the upload policy
381 * @see #setUploadPolicy(UploadPolicy)
382 */
383 public UploadPolicy getUploadPolicy() {
384 return this.uploadPolicy;
385 }
386
387 /**
388 * Sets the upload policy.
389 * @param uploadPolicy the upload policy
390 * @see #getUploadPolicy()
391 */
392 public void setUploadPolicy(UploadPolicy uploadPolicy) {
393 this.uploadPolicy = uploadPolicy;
394 }
395
396 /**
397 * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
398 */
399 private final Map<String, String> changeSetTags = new HashMap<>();
400
401 /**
402 * Replies the set of changeset tags to be applied when or if this is ever uploaded.
403 * @return the set of changeset tags
404 * @see #addChangeSetTag
405 */
406 public Map<String, String> getChangeSetTags() {
407 return changeSetTags;
408 }
409
410 /**
411 * Adds a new changeset tag.
412 * @param k Key
413 * @param v Value
414 * @see #getChangeSetTags
415 */
416 public void addChangeSetTag(String k, String v) {
417 this.changeSetTags.put(k, v);
418 }
419
420 /**
421 * Gets a filtered collection of primitives matching the given predicate.
422 * @param <T> The primitive type.
423 * @param predicate The predicate to match
424 * @return The list of primtives.
425 * @since 10590
426 */
427 public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) {
428 return new SubclassFilteredCollection<>(allPrimitives, predicate);
429 }
430
431 /**
432 * Replies an unmodifiable collection of nodes in this dataset
433 *
434 * @return an unmodifiable collection of nodes in this dataset
435 */
436 public Collection<Node> getNodes() {
437 return getPrimitives(Node.class::isInstance);
438 }
439
440 @Override
441 public List<Node> searchNodes(BBox bbox) {
442 lock.readLock().lock();
443 try {
444 return super.searchNodes(bbox);
445 } finally {
446 lock.readLock().unlock();
447 }
448 }
449
450 /**
451 * Replies an unmodifiable collection of ways in this dataset
452 *
453 * @return an unmodifiable collection of ways in this dataset
454 */
455 public Collection<Way> getWays() {
456 return getPrimitives(Way.class::isInstance);
457 }
458
459 @Override
460 public List<Way> searchWays(BBox bbox) {
461 lock.readLock().lock();
462 try {
463 return super.searchWays(bbox);
464 } finally {
465 lock.readLock().unlock();
466 }
467 }
468
469 /**
470 * Searches for relations in the given bounding box.
471 * @param bbox the bounding box
472 * @return List of relations in the given bbox. Can be empty but not null
473 */
474 @Override
475 public List<Relation> searchRelations(BBox bbox) {
476 lock.readLock().lock();
477 try {
478 return super.searchRelations(bbox);
479 } finally {
480 lock.readLock().unlock();
481 }
482 }
483
484 /**
485 * Replies an unmodifiable collection of relations in this dataset
486 *
487 * @return an unmodifiable collection of relations in this dataset
488 */
489 public Collection<Relation> getRelations() {
490 return getPrimitives(Relation.class::isInstance);
491 }
492
493 /**
494 * Returns a collection containing all primitives of the dataset.
495 * @return A collection containing all primitives of the dataset. Data is not ordered
496 */
497 public Collection<OsmPrimitive> allPrimitives() {
498 return getPrimitives(o -> true);
499 }
500
501 /**
502 * Returns a collection containing all not-deleted primitives.
503 * @return A collection containing all not-deleted primitives.
504 * @see OsmPrimitive#isDeleted
505 */
506 public Collection<OsmPrimitive> allNonDeletedPrimitives() {
507 return getPrimitives(p -> !p.isDeleted());
508 }
509
510 /**
511 * Returns a collection containing all not-deleted complete primitives.
512 * @return A collection containing all not-deleted complete primitives.
513 * @see OsmPrimitive#isDeleted
514 * @see OsmPrimitive#isIncomplete
515 */
516 public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() {
517 return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete());
518 }
519
520 /**
521 * Returns a collection containing all not-deleted complete physical primitives.
522 * @return A collection containing all not-deleted complete physical primitives (nodes and ways).
523 * @see OsmPrimitive#isDeleted
524 * @see OsmPrimitive#isIncomplete
525 */
526 public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() {
527 return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation));
528 }
529
530 /**
531 * Returns a collection containing all modified primitives.
532 * @return A collection containing all modified primitives.
533 * @see OsmPrimitive#isModified
534 */
535 public Collection<OsmPrimitive> allModifiedPrimitives() {
536 return getPrimitives(OsmPrimitive::isModified);
537 }
538
539 /**
540 * Adds a primitive to the dataset.
541 *
542 * @param primitive the primitive.
543 */
544 @Override
545 public void addPrimitive(OsmPrimitive primitive) {
546 Objects.requireNonNull(primitive, "primitive");
547 beginUpdate();
548 try {
549 if (getPrimitiveById(primitive) != null)
550 throw new DataIntegrityProblemException(
551 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()));
552
553 allPrimitives.add(primitive);
554 primitive.setDataset(this);
555 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly)
556 super.addPrimitive(primitive);
557 firePrimitivesAdded(Collections.singletonList(primitive), false);
558 } finally {
559 endUpdate();
560 }
561 }
562
563 /**
564 * Removes a primitive from the dataset. This method only removes the
565 * primitive form the respective collection of primitives managed
566 * by this dataset, i.e. from {@link #nodes}, {@link #ways}, or
567 * {@link #relations}. References from other primitives to this
568 * primitive are left unchanged.
569 *
570 * @param primitiveId the id of the primitive
571 */
572 public void removePrimitive(PrimitiveId primitiveId) {
573 beginUpdate();
574 try {
575 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
576 if (primitive == null)
577 return;
578 removePrimitiveImpl(primitive);
579 firePrimitivesRemoved(Collections.singletonList(primitive), false);
580 } finally {
581 endUpdate();
582 }
583 }
584
585 private void removePrimitiveImpl(OsmPrimitive primitive) {
586 clearSelection(primitive.getPrimitiveId());
587 if (primitive.isSelected()) {
588 throw new DataIntegrityProblemException("Primitive was re-selected by a selection listener: " + primitive);
589 }
590 super.removePrimitive(primitive);
591 allPrimitives.remove(primitive);
592 primitive.setDataset(null);
593 }
594
595 @Override
596 protected void removePrimitive(OsmPrimitive primitive) {
597 beginUpdate();
598 try {
599 removePrimitiveImpl(primitive);
600 firePrimitivesRemoved(Collections.singletonList(primitive), false);
601 } finally {
602 endUpdate();
603 }
604 }
605
606 /*---------------------------------------------------
607 * SELECTION HANDLING
608 *---------------------------------------------------*/
609
610 /**
611 * Add a listener that listens to selection changes in this specific data set.
612 * @param listener The listener.
613 * @see #removeSelectionListener(DataSelectionListener)
614 * @see SelectionEventManager#addSelectionListener(SelectionChangedListener,
615 * org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
616 * To add a global listener.
617 */
618 public void addSelectionListener(DataSelectionListener listener) {
619 selectionListeners.addListener(listener);
620 }
621
622 /**
623 * Remove a listener that listens to selection changes in this specific data set.
624 * @param listener The listener.
625 * @see #addSelectionListener(DataSelectionListener)
626 */
627 public void removeSelectionListener(DataSelectionListener listener) {
628 selectionListeners.removeListener(listener);
629 }
630
631 /*---------------------------------------------------
632 * OLD SELECTION HANDLING
633 *---------------------------------------------------*/
634
635 /**
636 * A list of listeners to selection changed events. The list is static, as listeners register
637 * themselves for any dataset selection changes that occur, regardless of the current active
638 * dataset. (However, the selection does only change in the active layer)
639 */
640 private static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<>();
641
642 /**
643 * Adds a new selection listener.
644 * @param listener The selection listener to add
645 * @see #addSelectionListener(DataSelectionListener)
646 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
647 */
648 public static void addSelectionListener(SelectionChangedListener listener) {
649 ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener);
650 }
651
652 /**
653 * Removes a selection listener.
654 * @param listener The selection listener to remove
655 * @see #removeSelectionListener(DataSelectionListener)
656 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
657 */
658 public static void removeSelectionListener(SelectionChangedListener listener) {
659 selListeners.remove(listener);
660 }
661
662 /**
663 * Notifies all registered {@link SelectionChangedListener} about the current selection in
664 * this dataset.
665 * @deprecated You should never need to do this from the outside.
666 */
667 @Deprecated
668 public void fireSelectionChanged() {
669 fireDreprecatedSelectionChange(getAllSelected());
670 }
671
672 private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
673 for (SelectionChangedListener l : selListeners) {
674 l.selectionChanged(currentSelection);
675 }
676 }
677
678 /**
679 * Returns selected nodes and ways.
680 * @return selected nodes and ways
681 */
682 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
683 return new SubclassFilteredCollection<>(getSelected(), primitive -> primitive instanceof Node || primitive instanceof Way);
684 }
685
686 /**
687 * Returns an unmodifiable collection of *WaySegments* whose virtual
688 * nodes should be highlighted. WaySegments are used to avoid having
689 * to create a VirtualNode class that wouldn't have much purpose otherwise.
690 *
691 * @return unmodifiable collection of WaySegments
692 */
693 public Collection<WaySegment> getHighlightedVirtualNodes() {
694 return Collections.unmodifiableCollection(highlightedVirtualNodes);
695 }
696
697 /**
698 * Returns an unmodifiable collection of WaySegments that should be highlighted.
699 *
700 * @return unmodifiable collection of WaySegments
701 */
702 public Collection<WaySegment> getHighlightedWaySegments() {
703 return Collections.unmodifiableCollection(highlightedWaySegments);
704 }
705
706 /**
707 * Adds a listener that gets notified whenever way segment / virtual nodes highlights change.
708 * @param listener The Listener
709 * @since 12014
710 */
711 public void addHighlightUpdateListener(HighlightUpdateListener listener) {
712 highlightUpdateListeners.addListener(listener);
713 }
714
715 /**
716 * Removes a listener that was added with {@link #addHighlightUpdateListener(HighlightUpdateListener)}
717 * @param listener The Listener
718 * @since 12014
719 */
720 public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
721 highlightUpdateListeners.removeListener(listener);
722 }
723
724 /**
725 * Replies an unmodifiable collection of primitives currently selected
726 * in this dataset, except deleted ones. May be empty, but not null.
727 *
728 * When iterating through the set it is ordered by the order in which the primitives were added to the selection.
729 *
730 * @return unmodifiable collection of primitives
731 */
732 public Collection<OsmPrimitive> getSelected() {
733 return new SubclassFilteredCollection<>(getAllSelected(), p -> !p.isDeleted());
734 }
735
736 /**
737 * Replies an unmodifiable collection of primitives currently selected
738 * in this dataset, including deleted ones. May be empty, but not null.
739 *
740 * When iterating through the set it is ordered by the order in which the primitives were added to the selection.
741 *
742 * @return unmodifiable collection of primitives
743 */
744 public Collection<OsmPrimitive> getAllSelected() {
745 return currentSelectedPrimitives;
746 }
747
748 /**
749 * Returns selected nodes.
750 * @return selected nodes
751 */
752 public Collection<Node> getSelectedNodes() {
753 return new SubclassFilteredCollection<>(getSelected(), Node.class::isInstance);
754 }
755
756 /**
757 * Returns selected ways.
758 * @return selected ways
759 */
760 public Collection<Way> getSelectedWays() {
761 return new SubclassFilteredCollection<>(getSelected(), Way.class::isInstance);
762 }
763
764 /**
765 * Returns selected relations.
766 * @return selected relations
767 */
768 public Collection<Relation> getSelectedRelations() {
769 return new SubclassFilteredCollection<>(getSelected(), Relation.class::isInstance);
770 }
771
772 /**
773 * Determines whether the selection is empty or not
774 * @return whether the selection is empty or not
775 */
776 public boolean selectionEmpty() {
777 return currentSelectedPrimitives.isEmpty();
778 }
779
780 /**
781 * Determines whether the given primitive is selected or not
782 * @param osm the primitive
783 * @return whether {@code osm} is selected or not
784 */
785 public boolean isSelected(OsmPrimitive osm) {
786 return currentSelectedPrimitives.contains(osm);
787 }
788
789 /**
790 * set what virtual nodes should be highlighted. Requires a Collection of
791 * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
792 * otherwise.
793 * @param waySegments Collection of way segments
794 */
795 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
796 if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
797 return;
798
799 highlightedVirtualNodes = waySegments;
800 fireHighlightingChanged();
801 }
802
803 /**
804 * set what virtual ways should be highlighted.
805 * @param waySegments Collection of way segments
806 */
807 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
808 if (highlightedWaySegments.isEmpty() && waySegments.isEmpty())
809 return;
810
811 highlightedWaySegments = waySegments;
812 fireHighlightingChanged();
813 }
814
815 /**
816 * Sets the current selection to the primitives in <code>selection</code>.
817 * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
818 *
819 * @param selection the selection
820 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
821 * @deprecated Use {@link #setSelected(Collection)} instead. To bee removed end of 2017. Does not seem to be used by plugins.
822 */
823 @Deprecated
824 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
825 setSelected(selection);
826 }
827
828 /**
829 * Sets the current selection to the primitives in <code>selection</code>
830 * and notifies all {@link SelectionChangedListener}.
831 *
832 * @param selection the selection
833 */
834 public void setSelected(Collection<? extends PrimitiveId> selection) {
835 setSelected(selection.stream());
836 }
837
838 /**
839 * Sets the current selection to the primitives in <code>osm</code>
840 * and notifies all {@link SelectionChangedListener}.
841 *
842 * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
843 */
844 public void setSelected(PrimitiveId... osm) {
845 setSelected(Stream.of(osm).filter(Objects::nonNull));
846 }
847
848 private void setSelected(Stream<? extends PrimitiveId> stream) {
849 doSelectionChange(old -> new SelectionReplaceEvent(this, old,
850 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
851 }
852
853 /**
854 * Adds the primitives in <code>selection</code> to the current selection
855 * and notifies all {@link SelectionChangedListener}.
856 *
857 * @param selection the selection
858 */
859 public void addSelected(Collection<? extends PrimitiveId> selection) {
860 addSelected(selection.stream());
861 }
862
863 /**
864 * Adds the primitives in <code>osm</code> to the current selection
865 * and notifies all {@link SelectionChangedListener}.
866 *
867 * @param osm the primitives to add
868 */
869 public void addSelected(PrimitiveId... osm) {
870 addSelected(Stream.of(osm));
871 }
872
873 private void addSelected(Stream<? extends PrimitiveId> stream) {
874 doSelectionChange(old -> new SelectionAddEvent(this, old,
875 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
876 }
877
878 /**
879 * Removes the selection from every value in the collection.
880 * @param osm The collection of ids to remove the selection from.
881 */
882 public void clearSelection(PrimitiveId... osm) {
883 clearSelection(Stream.of(osm));
884 }
885
886 /**
887 * Removes the selection from every value in the collection.
888 * @param list The collection of ids to remove the selection from.
889 */
890 public void clearSelection(Collection<? extends PrimitiveId> list) {
891 clearSelection(list.stream());
892 }
893
894 /**
895 * Clears the current selection.
896 */
897 public void clearSelection() {
898 setSelected(Stream.empty());
899 }
900
901 private void clearSelection(Stream<? extends PrimitiveId> stream) {
902 doSelectionChange(old -> new SelectionRemoveEvent(this, old,
903 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
904 }
905
906 /**
907 * Toggles the selected state of the given collection of primitives.
908 * @param osm The primitives to toggle
909 */
910 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
911 toggleSelected(osm.stream());
912 }
913
914 /**
915 * Toggles the selected state of the given collection of primitives.
916 * @param osm The primitives to toggle
917 */
918 public void toggleSelected(PrimitiveId... osm) {
919 toggleSelected(Stream.of(osm));
920 }
921
922 private void toggleSelected(Stream<? extends PrimitiveId> stream) {
923 doSelectionChange(old -> new SelectionToggleEvent(this, old,
924 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
925 }
926
927 /**
928 * Do a selection change.
929 * <p>
930 * This is the only method that changes the current selection state.
931 * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
932 * @return true iff the command did change the selection.
933 * @since 12048
934 */
935 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
936 synchronized (selectionLock) {
937 SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
938 if (event.isNop()) {
939 return false;
940 }
941 currentSelectedPrimitives = event.getSelection();
942 selectionListeners.fireEvent(l -> l.selectionChanged(event));
943 return true;
944 }
945 }
946
947 /**
948 * clear all highlights of virtual nodes
949 */
950 public void clearHighlightedVirtualNodes() {
951 setHighlightedVirtualNodes(new ArrayList<WaySegment>());
952 }
953
954 /**
955 * clear all highlights of way segments
956 */
957 public void clearHighlightedWaySegments() {
958 setHighlightedWaySegments(new ArrayList<WaySegment>());
959 }
960
961 @Override
962 public synchronized Area getDataSourceArea() {
963 if (cachedDataSourceArea == null) {
964 cachedDataSourceArea = Data.super.getDataSourceArea();
965 }
966 return cachedDataSourceArea;
967 }
968
969 @Override
970 public synchronized List<Bounds> getDataSourceBounds() {
971 if (cachedDataSourceBounds == null) {
972 cachedDataSourceBounds = Data.super.getDataSourceBounds();
973 }
974 return Collections.unmodifiableList(cachedDataSourceBounds);
975 }
976
977 @Override
978 public synchronized Collection<DataSource> getDataSources() {
979 return Collections.unmodifiableCollection(dataSources);
980 }
981
982 /**
983 * Returns a primitive with a given id from the data set. null, if no such primitive exists
984 *
985 * @param id uniqueId of the primitive. Might be &lt; 0 for newly created primitives
986 * @param type the type of the primitive. Must not be null.
987 * @return the primitive
988 * @throws NullPointerException if type is null
989 */
990 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
991 return getPrimitiveById(new SimplePrimitiveId(id, type));
992 }
993
994 /**
995 * Returns a primitive with a given id from the data set. null, if no such primitive exists
996 *
997 * @param primitiveId type and uniqueId of the primitive. Might be &lt; 0 for newly created primitives
998 * @return the primitive
999 */
1000 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
1001 return primitiveId != null ? primitivesMap.get(primitiveId) : null;
1002 }
1003
1004 /**
1005 * Show message and stack trace in log in case primitive is not found
1006 * @param primitiveId primitive id to look for
1007 * @return Primitive by id.
1008 */
1009 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
1010 OsmPrimitive result = getPrimitiveById(primitiveId);
1011 if (result == null && primitiveId != null) {
1012 Main.warn(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
1013 + "at {2}. This is not a critical error, it should be safe to continue in your work.",
1014 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite()));
1015 Main.error(new Exception());
1016 }
1017
1018 return result;
1019 }
1020
1021 private static void deleteWay(Way way) {
1022 way.setNodes(null);
1023 way.setDeleted(true);
1024 }
1025
1026 /**
1027 * Removes all references from ways in this dataset to a particular node.
1028 *
1029 * @param node the node
1030 * @return The set of ways that have been modified
1031 */
1032 public Set<Way> unlinkNodeFromWays(Node node) {
1033 Set<Way> result = new HashSet<>();
1034 beginUpdate();
1035 try {
1036 for (Way way : node.getParentWays()) {
1037 List<Node> wayNodes = way.getNodes();
1038 if (wayNodes.remove(node)) {
1039 if (wayNodes.size() < 2) {
1040 deleteWay(way);
1041 } else {
1042 way.setNodes(wayNodes);
1043 }
1044 result.add(way);
1045 }
1046 }
1047 } finally {
1048 endUpdate();
1049 }
1050 return result;
1051 }
1052
1053 /**
1054 * removes all references from relations in this dataset to this primitive
1055 *
1056 * @param primitive the primitive
1057 * @return The set of relations that have been modified
1058 */
1059 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
1060 Set<Relation> result = new HashSet<>();
1061 beginUpdate();
1062 try {
1063 for (Relation relation : getRelations()) {
1064 List<RelationMember> members = relation.getMembers();
1065
1066 Iterator<RelationMember> it = members.iterator();
1067 boolean removed = false;
1068 while (it.hasNext()) {
1069 RelationMember member = it.next();
1070 if (member.getMember().equals(primitive)) {
1071 it.remove();
1072 removed = true;
1073 }
1074 }
1075
1076 if (removed) {
1077 relation.setMembers(members);
1078 result.add(relation);
1079 }
1080 }
1081 } finally {
1082 endUpdate();
1083 }
1084 return result;
1085 }
1086
1087 /**
1088 * Removes all references from other primitives to the referenced primitive.
1089 *
1090 * @param referencedPrimitive the referenced primitive
1091 * @return The set of primitives that have been modified
1092 */
1093 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
1094 Set<OsmPrimitive> result = new HashSet<>();
1095 beginUpdate();
1096 try {
1097 if (referencedPrimitive instanceof Node) {
1098 result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
1099 }
1100 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
1101 } finally {
1102 endUpdate();
1103 }
1104 return result;
1105 }
1106
1107 /**
1108 * Replies true if there is at least one primitive in this dataset with
1109 * {@link OsmPrimitive#isModified()} == <code>true</code>.
1110 *
1111 * @return true if there is at least one primitive in this dataset with
1112 * {@link OsmPrimitive#isModified()} == <code>true</code>.
1113 */
1114 public boolean isModified() {
1115 for (OsmPrimitive p: allPrimitives) {
1116 if (p.isModified())
1117 return true;
1118 }
1119 return false;
1120 }
1121
1122 /**
1123 * Adds a new data set listener.
1124 * @param dsl The data set listener to add
1125 */
1126 public void addDataSetListener(DataSetListener dsl) {
1127 listeners.addIfAbsent(dsl);
1128 }
1129
1130 /**
1131 * Removes a data set listener.
1132 * @param dsl The data set listener to remove
1133 */
1134 public void removeDataSetListener(DataSetListener dsl) {
1135 listeners.remove(dsl);
1136 }
1137
1138 /**
1139 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
1140 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
1141 * <br>
1142 * Typical usecase should look like this:
1143 * <pre>
1144 * ds.beginUpdate();
1145 * try {
1146 * ...
1147 * } finally {
1148 * ds.endUpdate();
1149 * }
1150 * </pre>
1151 */
1152 public void beginUpdate() {
1153 lock.writeLock().lock();
1154 updateCount++;
1155 }
1156
1157 /**
1158 * @see DataSet#beginUpdate()
1159 */
1160 public void endUpdate() {
1161 if (updateCount > 0) {
1162 updateCount--;
1163 List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList();
1164 if (updateCount == 0) {
1165 eventsToFire = new ArrayList<>(cachedEvents);
1166 cachedEvents.clear();
1167 }
1168
1169 if (!eventsToFire.isEmpty()) {
1170 lock.readLock().lock();
1171 lock.writeLock().unlock();
1172 try {
1173 if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
1174 for (AbstractDatasetChangedEvent event: eventsToFire) {
1175 fireEventToListeners(event);
1176 }
1177 } else if (eventsToFire.size() == MAX_EVENTS) {
1178 fireEventToListeners(new DataChangedEvent(this));
1179 } else {
1180 fireEventToListeners(new DataChangedEvent(this, eventsToFire));
1181 }
1182 } finally {
1183 lock.readLock().unlock();
1184 }
1185 } else {
1186 lock.writeLock().unlock();
1187 }
1188
1189 } else
1190 throw new AssertionError("endUpdate called without beginUpdate");
1191 }
1192
1193 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1194 for (DataSetListener listener: listeners) {
1195 event.fire(listener);
1196 }
1197 }
1198
1199 private void fireEvent(AbstractDatasetChangedEvent event) {
1200 if (updateCount == 0)
1201 throw new AssertionError("dataset events can be fired only when dataset is locked");
1202 if (cachedEvents.size() < MAX_EVENTS) {
1203 cachedEvents.add(event);
1204 }
1205 }
1206
1207 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1208 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1209 }
1210
1211 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1212 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1213 }
1214
1215 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1216 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1217 }
1218
1219 void fireRelationMembersChanged(Relation r) {
1220 reindexRelation(r);
1221 fireEvent(new RelationMembersChangedEvent(this, r));
1222 }
1223
1224 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1225 reindexNode(node, newCoor, eastNorth);
1226 fireEvent(new NodeMovedEvent(this, node));
1227 }
1228
1229 void fireWayNodesChanged(Way way) {
1230 reindexWay(way);
1231 fireEvent(new WayNodesChangedEvent(this, way));
1232 }
1233
1234 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1235 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
1236 }
1237
1238 void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1239 fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1240 }
1241
1242 void fireHighlightingChanged() {
1243 HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this);
1244 highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e));
1245 }
1246
1247 /**
1248 * Invalidates the internal cache of projected east/north coordinates.
1249 *
1250 * This method can be invoked after the globally configured projection method
1251 * changed.
1252 */
1253 public void invalidateEastNorthCache() {
1254 if (Main.getProjection() == null) return; // sanity check
1255 beginUpdate();
1256 try {
1257 for (Node n: getNodes()) {
1258 n.invalidateEastNorthCache();
1259 }
1260 } finally {
1261 endUpdate();
1262 }
1263 }
1264
1265 /**
1266 * Cleanups all deleted primitives (really delete them from the dataset).
1267 */
1268 public void cleanupDeletedPrimitives() {
1269 beginUpdate();
1270 try {
1271 Collection<OsmPrimitive> toCleanUp = getPrimitives(
1272 primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()));
1273 if (!toCleanUp.isEmpty()) {
1274 // We unselect them in advance to not fire a selection change for every primitive
1275 clearSelection(toCleanUp.stream().map(OsmPrimitive::getPrimitiveId));
1276 for (OsmPrimitive primitive : toCleanUp) {
1277 removePrimitiveImpl(primitive);
1278 }
1279 firePrimitivesRemoved(toCleanUp, false);
1280 }
1281 } finally {
1282 endUpdate();
1283 }
1284 }
1285
1286 /**
1287 * Removes all primitives from the dataset and resets the currently selected primitives
1288 * to the empty collection. Also notifies selection change listeners if necessary.
1289 */
1290 @Override
1291 public void clear() {
1292 beginUpdate();
1293 try {
1294 clearSelection();
1295 for (OsmPrimitive primitive:allPrimitives) {
1296 primitive.setDataset(null);
1297 }
1298 super.clear();
1299 allPrimitives.clear();
1300 } finally {
1301 endUpdate();
1302 }
1303 }
1304
1305 /**
1306 * Marks all "invisible" objects as deleted. These objects should be always marked as
1307 * deleted when downloaded from the server. They can be undeleted later if necessary.
1308 *
1309 */
1310 public void deleteInvisible() {
1311 for (OsmPrimitive primitive:allPrimitives) {
1312 if (!primitive.isVisible()) {
1313 primitive.setDeleted(true);
1314 }
1315 }
1316 }
1317
1318 /**
1319 * Moves all primitives and datasources from DataSet "from" to this DataSet.
1320 * @param from The source DataSet
1321 */
1322 public void mergeFrom(DataSet from) {
1323 mergeFrom(from, null);
1324 }
1325
1326 /**
1327 * Moves all primitives and datasources from DataSet "from" to this DataSet.
1328 * @param from The source DataSet
1329 * @param progressMonitor The progress monitor
1330 */
1331 public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1332 if (from != null) {
1333 new DataSetMerger(this, from).merge(progressMonitor);
1334 synchronized (from) {
1335 if (!from.dataSources.isEmpty()) {
1336 if (dataSources.addAll(from.dataSources)) {
1337 cachedDataSourceArea = null;
1338 cachedDataSourceBounds = null;
1339 }
1340 from.dataSources.clear();
1341 from.cachedDataSourceArea = null;
1342 from.cachedDataSourceBounds = null;
1343 }
1344 }
1345 }
1346 }
1347
1348 /* --------------------------------------------------------------------------------- */
1349 /* interface ProjectionChangeListner */
1350 /* --------------------------------------------------------------------------------- */
1351 @Override
1352 public void projectionChanged(Projection oldValue, Projection newValue) {
1353 invalidateEastNorthCache();
1354 }
1355
1356 /**
1357 * Returns the data sources bounding box.
1358 * @return the data sources bounding box
1359 */
1360 public synchronized ProjectionBounds getDataSourceBoundingBox() {
1361 BoundingXYVisitor bbox = new BoundingXYVisitor();
1362 for (DataSource source : dataSources) {
1363 bbox.visit(source.bounds);
1364 }
1365 if (bbox.hasExtend()) {
1366 return bbox.getBounds();
1367 }
1368 return null;
1369 }
1370}
Note: See TracBrowser for help on using the repository browser.