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

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

fix #16698, see #15670 - make sure filters are executed (costly operation) only when necessary:

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