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

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

see #15008, see #15421 - add toString() methods to allow to report potentially incorrect selection events

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