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

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

see #13036 - deprecate Command() default constructor, fix unit tests and java warnings

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