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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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