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

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

fix #18928 - fix various crashes with empty ways

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