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

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

see #19334 - javadoc fixes + protected constructors for abstract classes

  • Property svn:eol-style set to native
File size: 44.1 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.LinkedHashSet;
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.atomic.AtomicBoolean;
20import java.util.concurrent.locks.Lock;
21import java.util.concurrent.locks.ReadWriteLock;
22import java.util.concurrent.locks.ReentrantReadWriteLock;
23import java.util.function.Function;
24import java.util.function.Predicate;
25import java.util.function.Supplier;
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, false, false);
213 primMap.put(w, newWay);
214 List<Node> newNodes = w.getNodes().stream()
215 .map(n -> (Node) primMap.get(n))
216 .collect(Collectors.toList());
217 newWay.setNodes(newNodes);
218 addPrimitive(newWay);
219 }
220 // Because relations can have other relations as members we first clone all relations
221 // and then get the cloned members
222 Collection<Relation> relations = copyFrom.getRelations();
223 for (Relation r : relations) {
224 Relation newRelation = new Relation(r, false, false);
225 primMap.put(r, newRelation);
226 addPrimitive(newRelation);
227 }
228 for (Relation r : relations) {
229 ((Relation) primMap.get(r)).setMembers(r.getMembers().stream()
230 .map(rm -> new RelationMember(rm.getRole(), primMap.get(rm.getMember())))
231 .collect(Collectors.toList()));
232 }
233 DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(this,
234 new LinkedHashSet<>(dataSources), copyFrom.dataSources.stream());
235 for (DataSource source : copyFrom.dataSources) {
236 dataSources.add(new DataSource(source));
237 }
238 dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
239 version = copyFrom.version;
240 uploadPolicy = copyFrom.uploadPolicy;
241 downloadPolicy = copyFrom.downloadPolicy;
242 isReadOnly.set(copyFrom.isReadOnly.get());
243 } finally {
244 copyFrom.getReadLock().unlock();
245 }
246 }
247
248 /**
249 * Constructs a new {@code DataSet} initially filled with the given primitives.
250 * @param osmPrimitives primitives to add to this data set
251 * @since 12726
252 */
253 public DataSet(OsmPrimitive... osmPrimitives) {
254 this();
255 update(() -> {
256 for (OsmPrimitive o : osmPrimitives) {
257 addPrimitive(o);
258 }
259 });
260 }
261
262 /**
263 * Adds a new data source.
264 * @param source data source to add
265 * @return {@code true} if the collection changed as a result of the call
266 * @since 11626
267 */
268 public synchronized boolean addDataSource(DataSource source) {
269 return addDataSources(Collections.singleton(source));
270 }
271
272 /**
273 * Adds new data sources.
274 * @param sources data sources to add
275 * @return {@code true} if the collection changed as a result of the call
276 * @since 11626
277 */
278 public synchronized boolean addDataSources(Collection<DataSource> sources) {
279 DataSourceAddedEvent addedEvent = new DataSourceAddedEvent(this,
280 new LinkedHashSet<>(dataSources), sources.stream());
281 boolean changed = dataSources.addAll(sources);
282 if (changed) {
283 cachedDataSourceArea = null;
284 cachedDataSourceBounds = null;
285 }
286 dataSourceListeners.fireEvent(d -> d.dataSourceChange(addedEvent));
287 return changed;
288 }
289
290 @Override
291 public Lock getReadLock() {
292 return lock.readLock();
293 }
294
295 /**
296 * History of selections - shared by plugins and SelectionListDialog
297 */
298 private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>();
299
300 /**
301 * Replies the history of JOSM selections
302 *
303 * @return list of history entries
304 */
305 public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() {
306 return selectionHistory;
307 }
308
309 /**
310 * Clears selection history list
311 */
312 public void clearSelectionHistory() {
313 selectionHistory.clear();
314 }
315
316 /**
317 * The API version that created this data set, if any.
318 */
319 private String version;
320
321 @Override
322 public String getVersion() {
323 return version;
324 }
325
326 /**
327 * Sets the API version this dataset was created from.
328 *
329 * @param version the API version, i.e. "0.6"
330 * @throws IllegalStateException if the dataset is read-only
331 */
332 public void setVersion(String version) {
333 checkModifiable();
334 this.version = version;
335 }
336
337 @Override
338 public DownloadPolicy getDownloadPolicy() {
339 return this.downloadPolicy;
340 }
341
342 @Override
343 public void setDownloadPolicy(DownloadPolicy downloadPolicy) {
344 this.downloadPolicy = Objects.requireNonNull(downloadPolicy);
345 }
346
347 @Override
348 public UploadPolicy getUploadPolicy() {
349 return this.uploadPolicy;
350 }
351
352 @Override
353 public void setUploadPolicy(UploadPolicy uploadPolicy) {
354 this.uploadPolicy = Objects.requireNonNull(uploadPolicy);
355 }
356
357 /**
358 * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
359 */
360 private final Map<String, String> changeSetTags = new HashMap<>();
361
362 /**
363 * Replies the set of changeset tags to be applied when or if this is ever uploaded.
364 * @return the set of changeset tags
365 * @see #addChangeSetTag
366 */
367 public Map<String, String> getChangeSetTags() {
368 return changeSetTags;
369 }
370
371 /**
372 * Adds a new changeset tag.
373 * @param k Key
374 * @param v Value
375 * @see #getChangeSetTags
376 */
377 public void addChangeSetTag(String k, String v) {
378 this.changeSetTags.put(k, v);
379 }
380
381 @Override
382 public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) {
383 return new SubclassFilteredCollection<>(allPrimitives, predicate);
384 }
385
386 @Override
387 public Collection<Node> getNodes() {
388 return getPrimitives(Node.class::isInstance);
389 }
390
391 @Override
392 public List<Node> searchNodes(BBox bbox) {
393 lock.readLock().lock();
394 try {
395 return store.searchNodes(bbox);
396 } finally {
397 lock.readLock().unlock();
398 }
399 }
400
401 @Override
402 public Collection<Way> getWays() {
403 return getPrimitives(Way.class::isInstance);
404 }
405
406 @Override
407 public List<Way> searchWays(BBox bbox) {
408 lock.readLock().lock();
409 try {
410 return store.searchWays(bbox);
411 } finally {
412 lock.readLock().unlock();
413 }
414 }
415
416 @Override
417 public List<Relation> searchRelations(BBox bbox) {
418 lock.readLock().lock();
419 try {
420 return store.searchRelations(bbox);
421 } finally {
422 lock.readLock().unlock();
423 }
424 }
425
426 /**
427 * Searches for all primitives in the given bounding box
428 *
429 * @param bbox the bounding box
430 * @return List of primitives in the given bbox. Can be empty but not null
431 * @since 15891
432 */
433 public List<OsmPrimitive> searchPrimitives(BBox bbox) {
434 List<OsmPrimitive> primitiveList = new ArrayList<>();
435 primitiveList.addAll(searchNodes(bbox));
436 primitiveList.addAll(searchWays(bbox));
437 primitiveList.addAll(searchRelations(bbox));
438 return primitiveList;
439 }
440
441 @Override
442 public Collection<Relation> getRelations() {
443 return getPrimitives(Relation.class::isInstance);
444 }
445
446 /**
447 * Determines if the given node can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
448 * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
449 *
450 * @param n The node to search
451 * @return {@code true} if {@code n} can be retrieved in this data set, {@code false} otherwise
452 * @since 7501
453 */
454 @Override
455 public boolean containsNode(Node n) {
456 return store.containsNode(n);
457 }
458
459 /**
460 * Determines if the given way can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
461 * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
462 *
463 * @param w The way to search
464 * @return {@code true} if {@code w} can be retrieved in this data set, {@code false} otherwise
465 * @since 7501
466 */
467 @Override
468 public boolean containsWay(Way w) {
469 return store.containsWay(w);
470 }
471
472 /**
473 * Determines if the given relation can be retrieved in the data set through its bounding box. Useful for dataset consistency test.
474 * For efficiency reasons this method does not lock the dataset, you have to lock it manually.
475 *
476 * @param r The relation to search
477 * @return {@code true} if {@code r} can be retrieved in this data set, {@code false} otherwise
478 * @since 7501
479 */
480 @Override
481 public boolean containsRelation(Relation r) {
482 return store.containsRelation(r);
483 }
484
485 /**
486 * Adds a primitive to the dataset.
487 *
488 * @param primitive the primitive.
489 * @throws IllegalStateException if the dataset is read-only
490 */
491 @Override
492 public void addPrimitive(OsmPrimitive primitive) {
493 Objects.requireNonNull(primitive, "primitive");
494 checkModifiable();
495 update(() -> {
496 if (getPrimitiveById(primitive) != null)
497 throw new DataIntegrityProblemException(
498 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()),
499 null, primitive);
500
501 allPrimitives.add(primitive);
502 primitive.setDataset(this);
503 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly)
504 store.addPrimitive(primitive);
505 firePrimitivesAdded(Collections.singletonList(primitive), false);
506 });
507 }
508
509 /**
510 * Removes a primitive from the dataset. This method only removes the
511 * primitive form the respective collection of primitives managed
512 * by this dataset, i.e. from {@code store.nodes}, {@code store.ways}, or
513 * {@code store.relations}. References from other primitives to this
514 * primitive are left unchanged.
515 *
516 * @param primitiveId the id of the primitive
517 * @throws IllegalStateException if the dataset is read-only
518 */
519 public void removePrimitive(PrimitiveId primitiveId) {
520 checkModifiable();
521 update(() -> {
522 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
523 if (primitive == null)
524 return;
525 removePrimitiveImpl(primitive);
526 firePrimitivesRemoved(Collections.singletonList(primitive), false);
527 });
528 }
529
530 private void removePrimitiveImpl(OsmPrimitive primitive) {
531 clearSelection(primitive.getPrimitiveId());
532 if (primitive.isSelected()) {
533 throw new DataIntegrityProblemException("Primitive was re-selected by a selection listener: " + primitive);
534 }
535 store.removePrimitive(primitive);
536 allPrimitives.remove(primitive);
537 primitive.setDataset(null);
538 }
539
540 void removePrimitive(OsmPrimitive primitive) {
541 checkModifiable();
542 update(() -> {
543 removePrimitiveImpl(primitive);
544 firePrimitivesRemoved(Collections.singletonList(primitive), false);
545 });
546 }
547
548 /*---------------------------------------------------
549 * SELECTION HANDLING
550 *---------------------------------------------------*/
551
552 @Override
553 public void addSelectionListener(DataSelectionListener listener) {
554 selectionListeners.addListener(listener);
555 }
556
557 @Override
558 public void removeSelectionListener(DataSelectionListener listener) {
559 selectionListeners.removeListener(listener);
560 }
561
562 /**
563 * Returns selected nodes and ways.
564 * @return selected nodes and ways
565 */
566 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
567 return new SubclassFilteredCollection<>(getSelected(),
568 primitive -> primitive instanceof Node || primitive instanceof Way);
569 }
570
571 @Override
572 public Collection<WaySegment> getHighlightedVirtualNodes() {
573 return Collections.unmodifiableCollection(highlightedVirtualNodes);
574 }
575
576 @Override
577 public Collection<WaySegment> getHighlightedWaySegments() {
578 return Collections.unmodifiableCollection(highlightedWaySegments);
579 }
580
581 @Override
582 public void addHighlightUpdateListener(HighlightUpdateListener listener) {
583 highlightUpdateListeners.addListener(listener);
584 }
585
586 @Override
587 public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
588 highlightUpdateListeners.removeListener(listener);
589 }
590
591 /**
592 * Adds a listener that gets notified whenever the data sources change
593 *
594 * @param listener The listener
595 * @see #removeDataSourceListener
596 * @see #getDataSources
597 * @since 15609
598 */
599 public void addDataSourceListener(DataSourceListener listener) {
600 dataSourceListeners.addListener(listener);
601 }
602
603 /**
604 * Removes a listener that gets notified whenever the data sources change
605 *
606 * @param listener The listener
607 * @see #addDataSourceListener
608 * @see #getDataSources
609 * @since 15609
610 */
611 public void removeDataSourceListener(DataSourceListener listener) {
612 dataSourceListeners.removeListener(listener);
613 }
614
615 @Override
616 public Collection<OsmPrimitive> getAllSelected() {
617 return currentSelectedPrimitives;
618 }
619
620 @Override
621 public boolean selectionEmpty() {
622 return currentSelectedPrimitives.isEmpty();
623 }
624
625 @Override
626 public boolean isSelected(OsmPrimitive osm) {
627 return currentSelectedPrimitives.contains(osm);
628 }
629
630 @Override
631 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
632 if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
633 return;
634
635 highlightedVirtualNodes = waySegments;
636 fireHighlightingChanged();
637 }
638
639 @Override
640 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
641 if (highlightedWaySegments.isEmpty() && waySegments.isEmpty())
642 return;
643
644 highlightedWaySegments = waySegments;
645 fireHighlightingChanged();
646 }
647
648 @Override
649 public void setSelected(Collection<? extends PrimitiveId> selection) {
650 setSelected(selection.stream());
651 }
652
653 @Override
654 public void setSelected(PrimitiveId... osm) {
655 setSelected(Stream.of(osm).filter(Objects::nonNull));
656 }
657
658 private void setSelected(Stream<? extends PrimitiveId> stream) {
659 doSelectionChange(old -> new SelectionReplaceEvent(this, old,
660 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
661 }
662
663 @Override
664 public void addSelected(Collection<? extends PrimitiveId> selection) {
665 addSelected(selection.stream());
666 }
667
668 @Override
669 public void addSelected(PrimitiveId... osm) {
670 addSelected(Stream.of(osm));
671 }
672
673 private void addSelected(Stream<? extends PrimitiveId> stream) {
674 doSelectionChange(old -> new SelectionAddEvent(this, old,
675 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
676 }
677
678 @Override
679 public void clearSelection(PrimitiveId... osm) {
680 clearSelection(Stream.of(osm));
681 }
682
683 @Override
684 public void clearSelection(Collection<? extends PrimitiveId> list) {
685 clearSelection(list.stream());
686 }
687
688 @Override
689 public void clearSelection() {
690 setSelected(Stream.empty());
691 }
692
693 private void clearSelection(Stream<? extends PrimitiveId> stream) {
694 doSelectionChange(old -> new SelectionRemoveEvent(this, old,
695 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
696 }
697
698 @Override
699 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
700 toggleSelected(osm.stream());
701 }
702
703 @Override
704 public void toggleSelected(PrimitiveId... osm) {
705 toggleSelected(Stream.of(osm));
706 }
707
708 private void toggleSelected(Stream<? extends PrimitiveId> stream) {
709 doSelectionChange(old -> new SelectionToggleEvent(this, old,
710 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
711 }
712
713 /**
714 * Do a selection change.
715 * <p>
716 * This is the only method that changes the current selection state.
717 * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
718 * @return true iff the command did change the selection.
719 * @since 12048
720 */
721 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
722 synchronized (selectionLock) {
723 SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
724 if (event.isNop()) {
725 return false;
726 }
727 currentSelectedPrimitives = event.getSelection();
728 selectionListeners.fireEvent(l -> l.selectionChanged(event));
729 return true;
730 }
731 }
732
733 @Override
734 public synchronized Area getDataSourceArea() {
735 if (cachedDataSourceArea == null) {
736 cachedDataSourceArea = OsmData.super.getDataSourceArea();
737 }
738 return cachedDataSourceArea;
739 }
740
741 @Override
742 public synchronized List<Bounds> getDataSourceBounds() {
743 if (cachedDataSourceBounds == null) {
744 cachedDataSourceBounds = OsmData.super.getDataSourceBounds();
745 }
746 return Collections.unmodifiableList(cachedDataSourceBounds);
747 }
748
749 @Override
750 public synchronized Collection<DataSource> getDataSources() {
751 return Collections.unmodifiableCollection(dataSources);
752 }
753
754 @Override
755 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
756 return primitiveId != null ? primitivesMap.get(primitiveId) : null;
757 }
758
759 /**
760 * Show message and stack trace in log in case primitive is not found
761 * @param primitiveId primitive id to look for
762 * @return Primitive by id.
763 */
764 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
765 OsmPrimitive result = getPrimitiveById(primitiveId);
766 if (result == null && primitiveId != null) {
767 Logging.warn(tr(
768 "JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
769 + "at {2}. This is not a critical error, it should be safe to continue in your work.",
770 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Config.getUrls().getJOSMWebsite()));
771 Logging.error(new Exception());
772 }
773
774 return result;
775 }
776
777 private static void deleteWay(Way way) {
778 way.setNodes(null);
779 way.setDeleted(true);
780 }
781
782 /**
783 * Removes all references from ways in this dataset to a particular node.
784 *
785 * @param node the node
786 * @return The set of ways that have been modified
787 * @throws IllegalStateException if the dataset is read-only
788 */
789 public Set<Way> unlinkNodeFromWays(Node node) {
790 checkModifiable();
791 return update(() -> {
792 Set<Way> result = new HashSet<>();
793 for (Way way : node.getParentWays()) {
794 List<Node> wayNodes = way.getNodes();
795 if (wayNodes.remove(node)) {
796 if (wayNodes.size() < 2) {
797 deleteWay(way);
798 } else {
799 way.setNodes(wayNodes);
800 }
801 result.add(way);
802 }
803 }
804 return result;
805 });
806 }
807
808 /**
809 * removes all references from relations in this dataset to this primitive
810 *
811 * @param primitive the primitive
812 * @return The set of relations that have been modified
813 * @throws IllegalStateException if the dataset is read-only
814 */
815 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
816 checkModifiable();
817 return update(() -> {
818 Set<Relation> result = new HashSet<>();
819 for (Relation relation : getRelations()) {
820 List<RelationMember> members = relation.getMembers();
821 boolean removed = members.removeIf(member -> member.getMember().equals(primitive));
822 if (removed) {
823 relation.setMembers(members);
824 result.add(relation);
825 }
826 }
827 return result;
828 });
829 }
830
831 /**
832 * Removes all references from other primitives to the referenced primitive.
833 *
834 * @param referencedPrimitive the referenced primitive
835 * @return The set of primitives that have been modified
836 * @throws IllegalStateException if the dataset is read-only
837 */
838 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
839 checkModifiable();
840 return update(() -> {
841 Set<OsmPrimitive> result = new HashSet<>();
842 if (referencedPrimitive instanceof Node) {
843 result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
844 }
845 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
846 return result;
847 });
848 }
849
850 @Override
851 public boolean isModified() {
852 return allPrimitives.parallelStream().anyMatch(OsmPrimitive::isModified);
853 }
854
855 /**
856 * Replies true if there is at least one primitive in this dataset which requires to be uploaded to server.
857 * @return true if there is at least one primitive in this dataset which requires to be uploaded to server
858 * @since 13161
859 */
860 public boolean requiresUploadToServer() {
861 return allPrimitives.parallelStream().anyMatch(p -> APIOperation.of(p) != null);
862 }
863
864 /**
865 * Adds a new data set listener.
866 * @param dsl The data set listener to add
867 */
868 public void addDataSetListener(DataSetListener dsl) {
869 listeners.addIfAbsent(dsl);
870 }
871
872 /**
873 * Removes a data set listener.
874 * @param dsl The data set listener to remove
875 */
876 public void removeDataSetListener(DataSetListener dsl) {
877 listeners.remove(dsl);
878 }
879
880 /**
881 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
882 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
883 * <br>
884 * Typical usecase should look like this:
885 * <pre>
886 * ds.beginUpdate();
887 * try {
888 * ...
889 * } finally {
890 * ds.endUpdate();
891 * }
892 * </pre>
893 * @see #endUpdate()
894 */
895 public void beginUpdate() {
896 lock.writeLock().lock();
897 updateCount++;
898 }
899
900 /**
901 * Must be called after a previous call to {@link #beginUpdate()} to fire change events.
902 * <br>
903 * Typical usecase should look like this:
904 * <pre>
905 * ds.beginUpdate();
906 * try {
907 * ...
908 * } finally {
909 * ds.endUpdate();
910 * }
911 * </pre>
912 * @see DataSet#beginUpdate()
913 */
914 public void endUpdate() {
915 if (updateCount > 0) {
916 updateCount--;
917 List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList();
918 if (updateCount == 0) {
919 eventsToFire = new ArrayList<>(cachedEvents);
920 cachedEvents.clear();
921 }
922
923 if (!eventsToFire.isEmpty()) {
924 lock.readLock().lock();
925 try {
926 lock.writeLock().unlock();
927 if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
928 for (AbstractDatasetChangedEvent event : eventsToFire) {
929 fireEventToListeners(event);
930 }
931 } else if (eventsToFire.size() == MAX_EVENTS) {
932 fireEventToListeners(new DataChangedEvent(this));
933 } else {
934 fireEventToListeners(new DataChangedEvent(this, eventsToFire));
935 }
936 } finally {
937 lock.readLock().unlock();
938 }
939 } else {
940 lock.writeLock().unlock();
941 }
942
943 } else
944 throw new AssertionError("endUpdate called without beginUpdate");
945 }
946
947 /**
948 * Performs the update runnable between {@link #beginUpdate()} / {@link #endUpdate()} calls.
949 * @param runnable update action
950 * @since 16187
951 */
952 public void update(Runnable runnable) {
953 beginUpdate();
954 try {
955 runnable.run();
956 } finally {
957 endUpdate();
958 }
959 }
960
961 /**
962 * Performs the update function between {@link #beginUpdate()} / {@link #endUpdate()} calls.
963 * @param function update function
964 * @param t function argument
965 * @param <T> argument type
966 * @param <R> result type
967 * @return function result
968 * @since 16187
969 */
970 public <T, R> R update(Function<T, R> function, T t) {
971 beginUpdate();
972 try {
973 return function.apply(t);
974 } finally {
975 endUpdate();
976 }
977 }
978
979 /**
980 * Performs the update supplier between {@link #beginUpdate()} / {@link #endUpdate()} calls.
981 * @param supplier update supplier
982 * @param <R> result type
983 * @return supplier result
984 * @since 16187
985 */
986 public <R> R update(Supplier<R> supplier) {
987 beginUpdate();
988 try {
989 return supplier.get();
990 } finally {
991 endUpdate();
992 }
993 }
994
995 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
996 for (DataSetListener listener : listeners) {
997 Logging.trace("Firing {0} to {1} (dataset)", event, listener);
998 event.fire(listener);
999 }
1000 }
1001
1002 private void fireEvent(AbstractDatasetChangedEvent event) {
1003 if (updateCount == 0)
1004 throw new AssertionError("dataset events can be fired only when dataset is locked");
1005 if (cachedEvents.size() < MAX_EVENTS) {
1006 cachedEvents.add(event);
1007 }
1008 }
1009
1010 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1011 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1012 }
1013
1014 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1015 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1016 }
1017
1018 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1019 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1020 }
1021
1022 void fireRelationMembersChanged(Relation r) {
1023 store.reindexRelation(r, Relation::updatePosition);
1024 fireEvent(new RelationMembersChangedEvent(this, r));
1025 }
1026
1027 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1028 store.reindexNode(node, n -> n.setCoorInternal(newCoor, eastNorth), Way::updatePosition, Relation::updatePosition);
1029 fireEvent(new NodeMovedEvent(this, node));
1030 }
1031
1032 void fireWayNodesChanged(Way way) {
1033 if (!way.isEmpty()) {
1034 store.reindexWay(way, Way::updatePosition, Relation::updatePosition);
1035 }
1036 fireEvent(new WayNodesChangedEvent(this, way));
1037 }
1038
1039 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1040 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId,
1041 newChangesetId));
1042 }
1043
1044 void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1045 fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1046 }
1047
1048 void fireFilterChanged() {
1049 fireEvent(new FilterChangedEvent(this));
1050 }
1051
1052 void fireHighlightingChanged() {
1053 HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this);
1054 highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e));
1055 }
1056
1057 /**
1058 * Invalidates the internal cache of projected east/north coordinates.
1059 *
1060 * This method can be invoked after the globally configured projection method changed.
1061 */
1062 public void invalidateEastNorthCache() {
1063 if (ProjectionRegistry.getProjection() == null)
1064 return; // sanity check
1065 update(() -> getNodes().forEach(Node::invalidateEastNorthCache));
1066 }
1067
1068 /**
1069 * Cleanups all deleted primitives (really delete them from the dataset).
1070 */
1071 public void cleanupDeletedPrimitives() {
1072 update(() -> {
1073 Collection<OsmPrimitive> toCleanUp = getPrimitives(
1074 primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()));
1075 if (!toCleanUp.isEmpty()) {
1076 // We unselect them in advance to not fire a selection change for every primitive
1077 clearSelection(toCleanUp.stream().map(OsmPrimitive::getPrimitiveId));
1078 for (OsmPrimitive primitive : toCleanUp) {
1079 removePrimitiveImpl(primitive);
1080 }
1081 firePrimitivesRemoved(toCleanUp, false);
1082 }
1083 });
1084 }
1085
1086 /**
1087 * Removes all primitives from the dataset and resets the currently selected primitives
1088 * to the empty collection. Also notifies selection change listeners if necessary.
1089 * @throws IllegalStateException if the dataset is read-only
1090 */
1091 @Override
1092 public void clear() {
1093 //TODO: Why can't we clear a dataset that is locked?
1094 //TODO: Report listeners that are still active (should be none)
1095 checkModifiable();
1096 update(() -> {
1097 clearSelection();
1098 for (OsmPrimitive primitive : allPrimitives) {
1099 primitive.setDataset(null);
1100 }
1101 store.clear();
1102 allPrimitives.clear();
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 * Determines if this Dataset contains no primitives.
1281 * @return true if this Dataset contains no primitives
1282 * @since 14835
1283 */
1284 public boolean isEmpty() {
1285 return allPrimitives.isEmpty();
1286 }
1287}
Note: See TracBrowser for help on using the repository browser.