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

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

fix #16051, see #8039, see #10456 - more fixes for download/upload policies and locked status (merge of layers)

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