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

Last change on this file since 12873 was 12873, checked in by Don-vip, 7 years ago
  • mark kendzi3d_Improved_by_Andrei plugin as unmaintained
  • fix javadoc issues in deprecated comments
  • update animal-sniffer-ant-tasks to 1.16 + patch to make it not fail the build (see https://github.com/mojohaus/animal-sniffer/pull/34)
  • update the check-plugins task, make it not fail the build
  • Property svn:eol-style set to native
File size: 48.5 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 * Returns the autocompletion manager, which maintains a list of used tags for autocompletion.
337 * @return the autocompletion manager
338 * @deprecated to be removed end of 2017. Use {@link AutoCompletionManager#of(DataSet)} instead.
339 */
340 @Deprecated
341 public AutoCompletionManager getAutoCompletionManager() {
342 return AutoCompletionManager.of(this);
343 }
344
345 /**
346 * The API version that created this data set, if any.
347 */
348 private String version;
349
350 /**
351 * Replies the API version this dataset was created from. May be null.
352 *
353 * @return the API version this dataset was created from. May be null.
354 */
355 public String getVersion() {
356 return version;
357 }
358
359 /**
360 * Sets the API version this dataset was created from.
361 *
362 * @param version the API version, i.e. "0.6"
363 */
364 public void setVersion(String version) {
365 this.version = version;
366 }
367
368 /**
369 * Determines if upload is being discouraged.
370 * (i.e. this dataset contains private data which should not be uploaded)
371 * @return {@code true} if upload is being discouraged, {@code false} otherwise
372 * @see #setUploadDiscouraged
373 * @deprecated use {@link #getUploadPolicy()}
374 */
375 @Deprecated
376 public boolean isUploadDiscouraged() {
377 return uploadPolicy == UploadPolicy.DISCOURAGED || uploadPolicy == UploadPolicy.BLOCKED;
378 }
379
380 /**
381 * Sets the "upload discouraged" flag.
382 * @param uploadDiscouraged {@code true} if this dataset contains private data which should not be uploaded
383 * @see #isUploadDiscouraged
384 * @deprecated use {@link #setUploadPolicy(UploadPolicy)}
385 */
386 @Deprecated
387 public void setUploadDiscouraged(boolean uploadDiscouraged) {
388 if (uploadPolicy != UploadPolicy.BLOCKED) {
389 this.uploadPolicy = uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL;
390 }
391 }
392
393 /**
394 * Get the upload policy.
395 * @return the upload policy
396 * @see #setUploadPolicy(UploadPolicy)
397 */
398 public UploadPolicy getUploadPolicy() {
399 return this.uploadPolicy;
400 }
401
402 /**
403 * Sets the upload policy.
404 * @param uploadPolicy the upload policy
405 * @see #getUploadPolicy()
406 */
407 public void setUploadPolicy(UploadPolicy uploadPolicy) {
408 this.uploadPolicy = uploadPolicy;
409 }
410
411 /**
412 * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
413 */
414 private final Map<String, String> changeSetTags = new HashMap<>();
415
416 /**
417 * Replies the set of changeset tags to be applied when or if this is ever uploaded.
418 * @return the set of changeset tags
419 * @see #addChangeSetTag
420 */
421 public Map<String, String> getChangeSetTags() {
422 return changeSetTags;
423 }
424
425 /**
426 * Adds a new changeset tag.
427 * @param k Key
428 * @param v Value
429 * @see #getChangeSetTags
430 */
431 public void addChangeSetTag(String k, String v) {
432 this.changeSetTags.put(k, v);
433 }
434
435 /**
436 * Gets a filtered collection of primitives matching the given predicate.
437 * @param <T> The primitive type.
438 * @param predicate The predicate to match
439 * @return The list of primtives.
440 * @since 10590
441 */
442 public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) {
443 return new SubclassFilteredCollection<>(allPrimitives, predicate);
444 }
445
446 /**
447 * Replies an unmodifiable collection of nodes in this dataset
448 *
449 * @return an unmodifiable collection of nodes in this dataset
450 */
451 public Collection<Node> getNodes() {
452 return getPrimitives(Node.class::isInstance);
453 }
454
455 @Override
456 public List<Node> searchNodes(BBox bbox) {
457 lock.readLock().lock();
458 try {
459 return super.searchNodes(bbox);
460 } finally {
461 lock.readLock().unlock();
462 }
463 }
464
465 /**
466 * Replies an unmodifiable collection of ways in this dataset
467 *
468 * @return an unmodifiable collection of ways in this dataset
469 */
470 public Collection<Way> getWays() {
471 return getPrimitives(Way.class::isInstance);
472 }
473
474 @Override
475 public List<Way> searchWays(BBox bbox) {
476 lock.readLock().lock();
477 try {
478 return super.searchWays(bbox);
479 } finally {
480 lock.readLock().unlock();
481 }
482 }
483
484 /**
485 * Searches for relations in the given bounding box.
486 * @param bbox the bounding box
487 * @return List of relations in the given bbox. Can be empty but not null
488 */
489 @Override
490 public List<Relation> searchRelations(BBox bbox) {
491 lock.readLock().lock();
492 try {
493 return super.searchRelations(bbox);
494 } finally {
495 lock.readLock().unlock();
496 }
497 }
498
499 /**
500 * Replies an unmodifiable collection of relations in this dataset
501 *
502 * @return an unmodifiable collection of relations in this dataset
503 */
504 public Collection<Relation> getRelations() {
505 return getPrimitives(Relation.class::isInstance);
506 }
507
508 /**
509 * Returns a collection containing all primitives of the dataset.
510 * @return A collection containing all primitives of the dataset. Data is not ordered
511 */
512 public Collection<OsmPrimitive> allPrimitives() {
513 return getPrimitives(o -> true);
514 }
515
516 /**
517 * Returns a collection containing all not-deleted primitives.
518 * @return A collection containing all not-deleted primitives.
519 * @see OsmPrimitive#isDeleted
520 */
521 public Collection<OsmPrimitive> allNonDeletedPrimitives() {
522 return getPrimitives(p -> !p.isDeleted());
523 }
524
525 /**
526 * Returns a collection containing all not-deleted complete primitives.
527 * @return A collection containing all not-deleted complete primitives.
528 * @see OsmPrimitive#isDeleted
529 * @see OsmPrimitive#isIncomplete
530 */
531 public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() {
532 return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete());
533 }
534
535 /**
536 * Returns a collection containing all not-deleted complete physical primitives.
537 * @return A collection containing all not-deleted complete physical primitives (nodes and ways).
538 * @see OsmPrimitive#isDeleted
539 * @see OsmPrimitive#isIncomplete
540 */
541 public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() {
542 return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation));
543 }
544
545 /**
546 * Returns a collection containing all modified primitives.
547 * @return A collection containing all modified primitives.
548 * @see OsmPrimitive#isModified
549 */
550 public Collection<OsmPrimitive> allModifiedPrimitives() {
551 return getPrimitives(OsmPrimitive::isModified);
552 }
553
554 /**
555 * Adds a primitive to the dataset.
556 *
557 * @param primitive the primitive.
558 */
559 @Override
560 public void addPrimitive(OsmPrimitive primitive) {
561 Objects.requireNonNull(primitive, "primitive");
562 beginUpdate();
563 try {
564 if (getPrimitiveById(primitive) != null)
565 throw new DataIntegrityProblemException(
566 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()));
567
568 allPrimitives.add(primitive);
569 primitive.setDataset(this);
570 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly)
571 super.addPrimitive(primitive);
572 firePrimitivesAdded(Collections.singletonList(primitive), false);
573 } finally {
574 endUpdate();
575 }
576 }
577
578 /**
579 * Removes a primitive from the dataset. This method only removes the
580 * primitive form the respective collection of primitives managed
581 * by this dataset, i.e. from {@link #nodes}, {@link #ways}, or
582 * {@link #relations}. References from other primitives to this
583 * primitive are left unchanged.
584 *
585 * @param primitiveId the id of the primitive
586 */
587 public void removePrimitive(PrimitiveId primitiveId) {
588 beginUpdate();
589 try {
590 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
591 if (primitive == null)
592 return;
593 removePrimitiveImpl(primitive);
594 firePrimitivesRemoved(Collections.singletonList(primitive), false);
595 } finally {
596 endUpdate();
597 }
598 }
599
600 private void removePrimitiveImpl(OsmPrimitive primitive) {
601 clearSelection(primitive.getPrimitiveId());
602 if (primitive.isSelected()) {
603 throw new DataIntegrityProblemException("Primitive was re-selected by a selection listener: " + primitive);
604 }
605 super.removePrimitive(primitive);
606 allPrimitives.remove(primitive);
607 primitive.setDataset(null);
608 }
609
610 @Override
611 protected void removePrimitive(OsmPrimitive primitive) {
612 beginUpdate();
613 try {
614 removePrimitiveImpl(primitive);
615 firePrimitivesRemoved(Collections.singletonList(primitive), false);
616 } finally {
617 endUpdate();
618 }
619 }
620
621 /*---------------------------------------------------
622 * SELECTION HANDLING
623 *---------------------------------------------------*/
624
625 /**
626 * Add a listener that listens to selection changes in this specific data set.
627 * @param listener The listener.
628 * @see #removeSelectionListener(DataSelectionListener)
629 * @see SelectionEventManager#addSelectionListener(SelectionChangedListener,
630 * org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
631 * To add a global listener.
632 */
633 public void addSelectionListener(DataSelectionListener listener) {
634 selectionListeners.addListener(listener);
635 }
636
637 /**
638 * Remove a listener that listens to selection changes in this specific data set.
639 * @param listener The listener.
640 * @see #addSelectionListener(DataSelectionListener)
641 */
642 public void removeSelectionListener(DataSelectionListener listener) {
643 selectionListeners.removeListener(listener);
644 }
645
646 /*---------------------------------------------------
647 * OLD SELECTION HANDLING
648 *---------------------------------------------------*/
649
650 /**
651 * A list of listeners to selection changed events. The list is static, as listeners register
652 * themselves for any dataset selection changes that occur, regardless of the current active
653 * dataset. (However, the selection does only change in the active layer)
654 */
655 private static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<>();
656
657 /**
658 * Adds a new selection listener.
659 * @param listener The selection listener to add
660 * @see #addSelectionListener(DataSelectionListener)
661 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
662 */
663 public static void addSelectionListener(SelectionChangedListener listener) {
664 ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener);
665 }
666
667 /**
668 * Removes a selection listener.
669 * @param listener The selection listener to remove
670 * @see #removeSelectionListener(DataSelectionListener)
671 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
672 */
673 public static void removeSelectionListener(SelectionChangedListener listener) {
674 selListeners.remove(listener);
675 }
676
677 /**
678 * Notifies all registered {@link SelectionChangedListener} about the current selection in
679 * this dataset.
680 * @deprecated You should never need to do this from the outside.
681 */
682 @Deprecated
683 public void fireSelectionChanged() {
684 fireDreprecatedSelectionChange(getAllSelected());
685 }
686
687 private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
688 for (SelectionChangedListener l : selListeners) {
689 l.selectionChanged(currentSelection);
690 }
691 }
692
693 /**
694 * Returns selected nodes and ways.
695 * @return selected nodes and ways
696 */
697 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
698 return new SubclassFilteredCollection<>(getSelected(), primitive -> primitive instanceof Node || primitive instanceof Way);
699 }
700
701 /**
702 * Returns an unmodifiable collection of *WaySegments* whose virtual
703 * nodes should be highlighted. WaySegments are used to avoid having
704 * to create a VirtualNode class that wouldn't have much purpose otherwise.
705 *
706 * @return unmodifiable collection of WaySegments
707 */
708 public Collection<WaySegment> getHighlightedVirtualNodes() {
709 return Collections.unmodifiableCollection(highlightedVirtualNodes);
710 }
711
712 /**
713 * Returns an unmodifiable collection of WaySegments that should be highlighted.
714 *
715 * @return unmodifiable collection of WaySegments
716 */
717 public Collection<WaySegment> getHighlightedWaySegments() {
718 return Collections.unmodifiableCollection(highlightedWaySegments);
719 }
720
721 /**
722 * Adds a listener that gets notified whenever way segment / virtual nodes highlights change.
723 * @param listener The Listener
724 * @since 12014
725 */
726 public void addHighlightUpdateListener(HighlightUpdateListener listener) {
727 highlightUpdateListeners.addListener(listener);
728 }
729
730 /**
731 * Removes a listener that was added with {@link #addHighlightUpdateListener(HighlightUpdateListener)}
732 * @param listener The Listener
733 * @since 12014
734 */
735 public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
736 highlightUpdateListeners.removeListener(listener);
737 }
738
739 /**
740 * Replies an unmodifiable collection of primitives currently selected
741 * in this dataset, except deleted ones. May be empty, but not null.
742 *
743 * When iterating through the set it is ordered by the order in which the primitives were added to the selection.
744 *
745 * @return unmodifiable collection of primitives
746 */
747 public Collection<OsmPrimitive> getSelected() {
748 return new SubclassFilteredCollection<>(getAllSelected(), p -> !p.isDeleted());
749 }
750
751 /**
752 * Replies an unmodifiable collection of primitives currently selected
753 * in this dataset, including deleted ones. May be empty, but not null.
754 *
755 * When iterating through the set it is ordered by the order in which the primitives were added to the selection.
756 *
757 * @return unmodifiable collection of primitives
758 */
759 public Collection<OsmPrimitive> getAllSelected() {
760 return currentSelectedPrimitives;
761 }
762
763 /**
764 * Returns selected nodes.
765 * @return selected nodes
766 */
767 public Collection<Node> getSelectedNodes() {
768 return new SubclassFilteredCollection<>(getSelected(), Node.class::isInstance);
769 }
770
771 /**
772 * Returns selected ways.
773 * @return selected ways
774 */
775 public Collection<Way> getSelectedWays() {
776 return new SubclassFilteredCollection<>(getSelected(), Way.class::isInstance);
777 }
778
779 /**
780 * Returns selected relations.
781 * @return selected relations
782 */
783 public Collection<Relation> getSelectedRelations() {
784 return new SubclassFilteredCollection<>(getSelected(), Relation.class::isInstance);
785 }
786
787 /**
788 * Determines whether the selection is empty or not
789 * @return whether the selection is empty or not
790 */
791 public boolean selectionEmpty() {
792 return currentSelectedPrimitives.isEmpty();
793 }
794
795 /**
796 * Determines whether the given primitive is selected or not
797 * @param osm the primitive
798 * @return whether {@code osm} is selected or not
799 */
800 public boolean isSelected(OsmPrimitive osm) {
801 return currentSelectedPrimitives.contains(osm);
802 }
803
804 /**
805 * set what virtual nodes should be highlighted. Requires a Collection of
806 * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
807 * otherwise.
808 * @param waySegments Collection of way segments
809 */
810 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
811 if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
812 return;
813
814 highlightedVirtualNodes = waySegments;
815 fireHighlightingChanged();
816 }
817
818 /**
819 * set what virtual ways should be highlighted.
820 * @param waySegments Collection of way segments
821 */
822 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
823 if (highlightedWaySegments.isEmpty() && waySegments.isEmpty())
824 return;
825
826 highlightedWaySegments = waySegments;
827 fireHighlightingChanged();
828 }
829
830 /**
831 * Sets the current selection to the primitives in <code>selection</code>.
832 * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
833 *
834 * @param selection the selection
835 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
836 * @deprecated Use {@link #setSelected(Collection)} instead. To be removed end of 2017. Does not seem to be used by plugins.
837 */
838 @Deprecated
839 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
840 setSelected(selection);
841 }
842
843 /**
844 * Sets the current selection to the primitives in <code>selection</code>
845 * and notifies all {@link SelectionChangedListener}.
846 *
847 * @param selection the selection
848 */
849 public void setSelected(Collection<? extends PrimitiveId> selection) {
850 setSelected(selection.stream());
851 }
852
853 /**
854 * Sets the current selection to the primitives in <code>osm</code>
855 * and notifies all {@link SelectionChangedListener}.
856 *
857 * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
858 */
859 public void setSelected(PrimitiveId... osm) {
860 setSelected(Stream.of(osm).filter(Objects::nonNull));
861 }
862
863 private void setSelected(Stream<? extends PrimitiveId> stream) {
864 doSelectionChange(old -> new SelectionReplaceEvent(this, old,
865 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
866 }
867
868 /**
869 * Adds the primitives in <code>selection</code> to the current selection
870 * and notifies all {@link SelectionChangedListener}.
871 *
872 * @param selection the selection
873 */
874 public void addSelected(Collection<? extends PrimitiveId> selection) {
875 addSelected(selection.stream());
876 }
877
878 /**
879 * Adds the primitives in <code>osm</code> to the current selection
880 * and notifies all {@link SelectionChangedListener}.
881 *
882 * @param osm the primitives to add
883 */
884 public void addSelected(PrimitiveId... osm) {
885 addSelected(Stream.of(osm));
886 }
887
888 private void addSelected(Stream<? extends PrimitiveId> stream) {
889 doSelectionChange(old -> new SelectionAddEvent(this, old,
890 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
891 }
892
893 /**
894 * Removes the selection from every value in the collection.
895 * @param osm The collection of ids to remove the selection from.
896 */
897 public void clearSelection(PrimitiveId... osm) {
898 clearSelection(Stream.of(osm));
899 }
900
901 /**
902 * Removes the selection from every value in the collection.
903 * @param list The collection of ids to remove the selection from.
904 */
905 public void clearSelection(Collection<? extends PrimitiveId> list) {
906 clearSelection(list.stream());
907 }
908
909 /**
910 * Clears the current selection.
911 */
912 public void clearSelection() {
913 setSelected(Stream.empty());
914 }
915
916 private void clearSelection(Stream<? extends PrimitiveId> stream) {
917 doSelectionChange(old -> new SelectionRemoveEvent(this, old,
918 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
919 }
920
921 /**
922 * Toggles the selected state of the given collection of primitives.
923 * @param osm The primitives to toggle
924 */
925 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
926 toggleSelected(osm.stream());
927 }
928
929 /**
930 * Toggles the selected state of the given collection of primitives.
931 * @param osm The primitives to toggle
932 */
933 public void toggleSelected(PrimitiveId... osm) {
934 toggleSelected(Stream.of(osm));
935 }
936
937 private void toggleSelected(Stream<? extends PrimitiveId> stream) {
938 doSelectionChange(old -> new SelectionToggleEvent(this, old,
939 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
940 }
941
942 /**
943 * Do a selection change.
944 * <p>
945 * This is the only method that changes the current selection state.
946 * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
947 * @return true iff the command did change the selection.
948 * @since 12048
949 */
950 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
951 synchronized (selectionLock) {
952 SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
953 if (event.isNop()) {
954 return false;
955 }
956 currentSelectedPrimitives = event.getSelection();
957 selectionListeners.fireEvent(l -> l.selectionChanged(event));
958 return true;
959 }
960 }
961
962 /**
963 * clear all highlights of virtual nodes
964 */
965 public void clearHighlightedVirtualNodes() {
966 setHighlightedVirtualNodes(new ArrayList<WaySegment>());
967 }
968
969 /**
970 * clear all highlights of way segments
971 */
972 public void clearHighlightedWaySegments() {
973 setHighlightedWaySegments(new ArrayList<WaySegment>());
974 }
975
976 @Override
977 public synchronized Area getDataSourceArea() {
978 if (cachedDataSourceArea == null) {
979 cachedDataSourceArea = Data.super.getDataSourceArea();
980 }
981 return cachedDataSourceArea;
982 }
983
984 @Override
985 public synchronized List<Bounds> getDataSourceBounds() {
986 if (cachedDataSourceBounds == null) {
987 cachedDataSourceBounds = Data.super.getDataSourceBounds();
988 }
989 return Collections.unmodifiableList(cachedDataSourceBounds);
990 }
991
992 @Override
993 public synchronized Collection<DataSource> getDataSources() {
994 return Collections.unmodifiableCollection(dataSources);
995 }
996
997 /**
998 * Returns a primitive with a given id from the data set. null, if no such primitive exists
999 *
1000 * @param id uniqueId of the primitive. Might be &lt; 0 for newly created primitives
1001 * @param type the type of the primitive. Must not be null.
1002 * @return the primitive
1003 * @throws NullPointerException if type is null
1004 */
1005 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
1006 return getPrimitiveById(new SimplePrimitiveId(id, type));
1007 }
1008
1009 /**
1010 * Returns a primitive with a given id from the data set. null, if no such primitive exists
1011 *
1012 * @param primitiveId type and uniqueId of the primitive. Might be &lt; 0 for newly created primitives
1013 * @return the primitive
1014 */
1015 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
1016 return primitiveId != null ? primitivesMap.get(primitiveId) : null;
1017 }
1018
1019 /**
1020 * Show message and stack trace in log in case primitive is not found
1021 * @param primitiveId primitive id to look for
1022 * @return Primitive by id.
1023 */
1024 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
1025 OsmPrimitive result = getPrimitiveById(primitiveId);
1026 if (result == null && primitiveId != null) {
1027 Logging.warn(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
1028 + "at {2}. This is not a critical error, it should be safe to continue in your work.",
1029 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite()));
1030 Logging.error(new Exception());
1031 }
1032
1033 return result;
1034 }
1035
1036 private static void deleteWay(Way way) {
1037 way.setNodes(null);
1038 way.setDeleted(true);
1039 }
1040
1041 /**
1042 * Removes all references from ways in this dataset to a particular node.
1043 *
1044 * @param node the node
1045 * @return The set of ways that have been modified
1046 */
1047 public Set<Way> unlinkNodeFromWays(Node node) {
1048 Set<Way> result = new HashSet<>();
1049 beginUpdate();
1050 try {
1051 for (Way way : node.getParentWays()) {
1052 List<Node> wayNodes = way.getNodes();
1053 if (wayNodes.remove(node)) {
1054 if (wayNodes.size() < 2) {
1055 deleteWay(way);
1056 } else {
1057 way.setNodes(wayNodes);
1058 }
1059 result.add(way);
1060 }
1061 }
1062 } finally {
1063 endUpdate();
1064 }
1065 return result;
1066 }
1067
1068 /**
1069 * removes all references from relations in this dataset to this primitive
1070 *
1071 * @param primitive the primitive
1072 * @return The set of relations that have been modified
1073 */
1074 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
1075 Set<Relation> result = new HashSet<>();
1076 beginUpdate();
1077 try {
1078 for (Relation relation : getRelations()) {
1079 List<RelationMember> members = relation.getMembers();
1080
1081 Iterator<RelationMember> it = members.iterator();
1082 boolean removed = false;
1083 while (it.hasNext()) {
1084 RelationMember member = it.next();
1085 if (member.getMember().equals(primitive)) {
1086 it.remove();
1087 removed = true;
1088 }
1089 }
1090
1091 if (removed) {
1092 relation.setMembers(members);
1093 result.add(relation);
1094 }
1095 }
1096 } finally {
1097 endUpdate();
1098 }
1099 return result;
1100 }
1101
1102 /**
1103 * Removes all references from other primitives to the referenced primitive.
1104 *
1105 * @param referencedPrimitive the referenced primitive
1106 * @return The set of primitives that have been modified
1107 */
1108 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
1109 Set<OsmPrimitive> result = new HashSet<>();
1110 beginUpdate();
1111 try {
1112 if (referencedPrimitive instanceof Node) {
1113 result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
1114 }
1115 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
1116 } finally {
1117 endUpdate();
1118 }
1119 return result;
1120 }
1121
1122 /**
1123 * Replies true if there is at least one primitive in this dataset with
1124 * {@link OsmPrimitive#isModified()} == <code>true</code>.
1125 *
1126 * @return true if there is at least one primitive in this dataset with
1127 * {@link OsmPrimitive#isModified()} == <code>true</code>.
1128 */
1129 public boolean isModified() {
1130 for (OsmPrimitive p: allPrimitives) {
1131 if (p.isModified())
1132 return true;
1133 }
1134 return false;
1135 }
1136
1137 /**
1138 * Adds a new data set listener.
1139 * @param dsl The data set listener to add
1140 */
1141 public void addDataSetListener(DataSetListener dsl) {
1142 listeners.addIfAbsent(dsl);
1143 }
1144
1145 /**
1146 * Removes a data set listener.
1147 * @param dsl The data set listener to remove
1148 */
1149 public void removeDataSetListener(DataSetListener dsl) {
1150 listeners.remove(dsl);
1151 }
1152
1153 /**
1154 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
1155 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
1156 * <br>
1157 * Typical usecase should look like this:
1158 * <pre>
1159 * ds.beginUpdate();
1160 * try {
1161 * ...
1162 * } finally {
1163 * ds.endUpdate();
1164 * }
1165 * </pre>
1166 */
1167 public void beginUpdate() {
1168 lock.writeLock().lock();
1169 updateCount++;
1170 }
1171
1172 /**
1173 * @see DataSet#beginUpdate()
1174 */
1175 public void endUpdate() {
1176 if (updateCount > 0) {
1177 updateCount--;
1178 List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList();
1179 if (updateCount == 0) {
1180 eventsToFire = new ArrayList<>(cachedEvents);
1181 cachedEvents.clear();
1182 }
1183
1184 if (!eventsToFire.isEmpty()) {
1185 lock.readLock().lock();
1186 lock.writeLock().unlock();
1187 try {
1188 if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
1189 for (AbstractDatasetChangedEvent event: eventsToFire) {
1190 fireEventToListeners(event);
1191 }
1192 } else if (eventsToFire.size() == MAX_EVENTS) {
1193 fireEventToListeners(new DataChangedEvent(this));
1194 } else {
1195 fireEventToListeners(new DataChangedEvent(this, eventsToFire));
1196 }
1197 } finally {
1198 lock.readLock().unlock();
1199 }
1200 } else {
1201 lock.writeLock().unlock();
1202 }
1203
1204 } else
1205 throw new AssertionError("endUpdate called without beginUpdate");
1206 }
1207
1208 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1209 for (DataSetListener listener: listeners) {
1210 event.fire(listener);
1211 }
1212 }
1213
1214 private void fireEvent(AbstractDatasetChangedEvent event) {
1215 if (updateCount == 0)
1216 throw new AssertionError("dataset events can be fired only when dataset is locked");
1217 if (cachedEvents.size() < MAX_EVENTS) {
1218 cachedEvents.add(event);
1219 }
1220 }
1221
1222 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1223 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1224 }
1225
1226 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1227 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1228 }
1229
1230 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1231 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1232 }
1233
1234 void fireRelationMembersChanged(Relation r) {
1235 reindexRelation(r);
1236 fireEvent(new RelationMembersChangedEvent(this, r));
1237 }
1238
1239 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1240 reindexNode(node, newCoor, eastNorth);
1241 fireEvent(new NodeMovedEvent(this, node));
1242 }
1243
1244 void fireWayNodesChanged(Way way) {
1245 reindexWay(way);
1246 fireEvent(new WayNodesChangedEvent(this, way));
1247 }
1248
1249 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1250 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
1251 }
1252
1253 void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1254 fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1255 }
1256
1257 void fireHighlightingChanged() {
1258 HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this);
1259 highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e));
1260 }
1261
1262 /**
1263 * Invalidates the internal cache of projected east/north coordinates.
1264 *
1265 * This method can be invoked after the globally configured projection method
1266 * changed.
1267 */
1268 public void invalidateEastNorthCache() {
1269 if (Main.getProjection() == null) return; // sanity check
1270 beginUpdate();
1271 try {
1272 for (Node n: getNodes()) {
1273 n.invalidateEastNorthCache();
1274 }
1275 } finally {
1276 endUpdate();
1277 }
1278 }
1279
1280 /**
1281 * Cleanups all deleted primitives (really delete them from the dataset).
1282 */
1283 public void cleanupDeletedPrimitives() {
1284 beginUpdate();
1285 try {
1286 Collection<OsmPrimitive> toCleanUp = getPrimitives(
1287 primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()));
1288 if (!toCleanUp.isEmpty()) {
1289 // We unselect them in advance to not fire a selection change for every primitive
1290 clearSelection(toCleanUp.stream().map(OsmPrimitive::getPrimitiveId));
1291 for (OsmPrimitive primitive : toCleanUp) {
1292 removePrimitiveImpl(primitive);
1293 }
1294 firePrimitivesRemoved(toCleanUp, false);
1295 }
1296 } finally {
1297 endUpdate();
1298 }
1299 }
1300
1301 /**
1302 * Removes all primitives from the dataset and resets the currently selected primitives
1303 * to the empty collection. Also notifies selection change listeners if necessary.
1304 */
1305 @Override
1306 public void clear() {
1307 beginUpdate();
1308 try {
1309 clearSelection();
1310 for (OsmPrimitive primitive:allPrimitives) {
1311 primitive.setDataset(null);
1312 }
1313 super.clear();
1314 allPrimitives.clear();
1315 } finally {
1316 endUpdate();
1317 }
1318 }
1319
1320 /**
1321 * Marks all "invisible" objects as deleted. These objects should be always marked as
1322 * deleted when downloaded from the server. They can be undeleted later if necessary.
1323 *
1324 */
1325 public void deleteInvisible() {
1326 for (OsmPrimitive primitive:allPrimitives) {
1327 if (!primitive.isVisible()) {
1328 primitive.setDeleted(true);
1329 }
1330 }
1331 }
1332
1333 /**
1334 * Moves all primitives and datasources from DataSet "from" to this DataSet.
1335 * @param from The source DataSet
1336 */
1337 public void mergeFrom(DataSet from) {
1338 mergeFrom(from, null);
1339 }
1340
1341 /**
1342 * Moves all primitives and datasources from DataSet "from" to this DataSet.
1343 * @param from The source DataSet
1344 * @param progressMonitor The progress monitor
1345 */
1346 public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1347 if (from != null) {
1348 new DataSetMerger(this, from).merge(progressMonitor);
1349 synchronized (from) {
1350 if (!from.dataSources.isEmpty()) {
1351 if (dataSources.addAll(from.dataSources)) {
1352 cachedDataSourceArea = null;
1353 cachedDataSourceBounds = null;
1354 }
1355 from.dataSources.clear();
1356 from.cachedDataSourceArea = null;
1357 from.cachedDataSourceBounds = null;
1358 }
1359 }
1360 }
1361 }
1362
1363 /**
1364 * Replies the set of conflicts currently managed in this layer.
1365 *
1366 * @return the set of conflicts currently managed in this layer
1367 * @since 12672
1368 */
1369 public ConflictCollection getConflicts() {
1370 return conflicts;
1371 }
1372
1373 /**
1374 * Returns the name of this data set (optional).
1375 * @return the name of this data set. Can be {@code null}
1376 * @since 12718
1377 */
1378 public String getName() {
1379 return name;
1380 }
1381
1382 /**
1383 * Sets the name of this data set.
1384 * @param name the new name of this data set. Can be {@code null} to reset it
1385 * @since 12718
1386 */
1387 public void setName(String name) {
1388 this.name = name;
1389 }
1390
1391 /* --------------------------------------------------------------------------------- */
1392 /* interface ProjectionChangeListner */
1393 /* --------------------------------------------------------------------------------- */
1394 @Override
1395 public void projectionChanged(Projection oldValue, Projection newValue) {
1396 invalidateEastNorthCache();
1397 }
1398
1399 /**
1400 * Returns the data sources bounding box.
1401 * @return the data sources bounding box
1402 */
1403 public synchronized ProjectionBounds getDataSourceBoundingBox() {
1404 BoundingXYVisitor bbox = new BoundingXYVisitor();
1405 for (DataSource source : dataSources) {
1406 bbox.visit(source.bounds);
1407 }
1408 if (bbox.hasExtend()) {
1409 return bbox.getBounds();
1410 }
1411 return null;
1412 }
1413}
Note: See TracBrowser for help on using the repository browser.