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

Last change on this file since 4310 was 4310, checked in by stoecker, 13 years ago

fix #6680, fix #6677 - i18n issues

  • Property svn:eol-style set to native
File size: 38.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
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.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.concurrent.CopyOnWriteArrayList;
18import java.util.concurrent.locks.Lock;
19import java.util.concurrent.locks.ReadWriteLock;
20import java.util.concurrent.locks.ReentrantReadWriteLock;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.Bounds;
24import org.openstreetmap.josm.data.SelectionChangedListener;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
28import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
29import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
30import org.openstreetmap.josm.data.osm.event.DataSetListener;
31import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
32import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
33import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
34import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
35import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
36import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
37import org.openstreetmap.josm.data.projection.Projection;
38import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
39import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
40import org.openstreetmap.josm.tools.FilteredCollection;
41import org.openstreetmap.josm.tools.Predicate;
42import org.openstreetmap.josm.tools.SubclassFilteredCollection;
43import org.openstreetmap.josm.tools.Utils;
44
45/**
46 * DataSet is the data behind the application. It can consists of only a few points up to the whole
47 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
48 *
49 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
50 * store some information.
51 *
52 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
53 * lead to data corruption or ConccurentModificationException. However when for example one thread
54 * removes primitive and other thread try to add another primitive reffering to the removed primitive,
55 * DataIntegrityException will occur.
56 *
57 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
58 * Dataset will not change. Sample usage:
59 * <code>
60 * ds.getReadLock().lock();
61 * try {
62 * // .. do something with dataset
63 * } finally {
64 * ds.getReadLock().unlock();
65 * }
66 * </code>
67 *
68 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
69 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
70 * reasons - GUI can be updated after all changes are done.
71 * Sample usage:
72 * <code>
73 * ds.beginUpdate()
74 * try {
75 * // .. do modifications
76 * } finally {
77 * ds.endUpdate();
78 * }
79 * </code>
80 *
81 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
82 * automatically.
83 *
84 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
85 * sample ticket
86 *
87 * @author imi
88 */
89public class DataSet implements Cloneable, ProjectionChangeListener {
90
91 /**
92 * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
93 */
94 private static final int MAX_SINGLE_EVENTS = 30;
95
96 /**
97 * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
98 */
99 private static final int MAX_EVENTS = 1000;
100
101 private static class IdHash implements Hash<PrimitiveId,OsmPrimitive> {
102
103 public int getHashCode(PrimitiveId k) {
104 return (int)k.getUniqueId() ^ k.getType().hashCode();
105 }
106
107 public boolean equals(PrimitiveId key, OsmPrimitive value) {
108 if (key == null || value == null) return false;
109 return key.getUniqueId() == value.getUniqueId() && key.getType() == value.getType();
110 }
111 }
112
113 private Storage<OsmPrimitive> allPrimitives = new Storage<OsmPrimitive>(new IdHash(), true);
114 private Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new IdHash());
115 private CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<DataSetListener>();
116
117 // Number of open calls to beginUpdate
118 private int updateCount;
119 // Events that occurred while dataset was locked but should be fired after write lock is released
120 private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<AbstractDatasetChangedEvent>();
121
122 private int highlightUpdateCount;
123
124 private final ReadWriteLock lock = new ReentrantReadWriteLock();
125 private final Object selectionLock = new Object();
126
127 public DataSet() {
128 /*
129 * Transparently register as projection change lister. No need to explicitly remove the
130 * the listener, projection change listeners are managed as WeakReferences.
131 */
132 Main.addProjectionChangeListener(this);
133 }
134
135 public Lock getReadLock() {
136 return lock.readLock();
137 }
138
139 /**
140 * This method can be used to detect changes in highlight state of primitives. If highlighting was changed
141 * then the method will return different number.
142 * @return
143 */
144 public int getHighlightUpdateCount() {
145 return highlightUpdateCount;
146 }
147
148 /**
149 * History of selections - shared by plugins and SelectionListDialog
150 */
151 private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<Collection<? extends OsmPrimitive>>();
152
153 /**
154 * Replies the history of JOSM selections
155 *
156 * @return
157 */
158 public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() {
159 return selectionHistory;
160 }
161
162 /**
163 * Clears selection history list
164 */
165 public void clearSelectionHistory() {
166 selectionHistory.clear();
167 }
168
169 /**
170 * Maintain a list of used tags for autocompletion
171 */
172 private AutoCompletionManager autocomplete;
173
174 public AutoCompletionManager getAutoCompletionManager() {
175 if (autocomplete == null) {
176 autocomplete = new AutoCompletionManager(this);
177 addDataSetListener(autocomplete);
178 }
179 return autocomplete;
180 }
181
182 /**
183 * The API version that created this data set, if any.
184 */
185 private String version;
186
187 /**
188 * Replies the API version this dataset was created from. May be null.
189 *
190 * @return the API version this dataset was created from. May be null.
191 */
192 public String getVersion() {
193 return version;
194 }
195
196 /**
197 * Sets the API version this dataset was created from.
198 *
199 * @param version the API version, i.e. "0.5" or "0.6"
200 */
201 public void setVersion(String version) {
202 this.version = version;
203 }
204
205 /**
206 * All nodes goes here, even when included in other data (ways etc). This enables the instant
207 * conversion of the whole DataSet by iterating over this data structure.
208 */
209 private QuadBuckets<Node> nodes = new QuadBuckets<Node>();
210
211 private <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<OsmPrimitive> predicate) {
212 return new SubclassFilteredCollection<OsmPrimitive, T>(allPrimitives, predicate);
213 }
214
215 /**
216 * Replies an unmodifiable collection of nodes in this dataset
217 *
218 * @return an unmodifiable collection of nodes in this dataset
219 */
220 public Collection<Node> getNodes() {
221 return getPrimitives(OsmPrimitive.nodePredicate);
222 }
223
224 public List<Node> searchNodes(BBox bbox) {
225 lock.readLock().lock();
226 try {
227 return nodes.search(bbox);
228 } finally {
229 lock.readLock().unlock();
230 }
231 }
232
233 /**
234 * All ways (Streets etc.) in the DataSet.
235 *
236 * The way nodes are stored only in the way list.
237 */
238 private QuadBuckets<Way> ways = new QuadBuckets<Way>();
239
240 /**
241 * Replies an unmodifiable collection of ways in this dataset
242 *
243 * @return an unmodifiable collection of ways in this dataset
244 */
245 public Collection<Way> getWays() {
246 return getPrimitives(OsmPrimitive.wayPredicate);
247 }
248
249 public List<Way> searchWays(BBox bbox) {
250 lock.readLock().lock();
251 try {
252 return ways.search(bbox);
253 } finally {
254 lock.readLock().unlock();
255 }
256 }
257
258 /**
259 * All relations/relationships
260 */
261 private Collection<Relation> relations = new ArrayList<Relation>();
262
263 /**
264 * Replies an unmodifiable collection of relations in this dataset
265 *
266 * @return an unmodifiable collection of relations in this dataset
267 */
268 public Collection<Relation> getRelations() {
269 return getPrimitives(OsmPrimitive.relationPredicate);
270 }
271
272 public List<Relation> searchRelations(BBox bbox) {
273 lock.readLock().lock();
274 try {
275 // QuadBuckets might be useful here (don't forget to do reindexing after some of rm is changed)
276 List<Relation> result = new ArrayList<Relation>();
277 for (Relation r: relations) {
278 if (r.getBBox().intersects(bbox)) {
279 result.add(r);
280 }
281 }
282 return result;
283 } finally {
284 lock.readLock().unlock();
285 }
286 }
287
288 /**
289 * All data sources of this DataSet.
290 */
291 public Collection<DataSource> dataSources = new LinkedList<DataSource>();
292
293 /**
294 * @return A collection containing all primitives of the dataset. Data are not ordered
295 */
296 public Collection<OsmPrimitive> allPrimitives() {
297 return getPrimitives(OsmPrimitive.allPredicate);
298 }
299
300 /**
301 * @return A collection containing all not-deleted primitives (except keys).
302 */
303 public Collection<OsmPrimitive> allNonDeletedPrimitives() {
304 return getPrimitives(OsmPrimitive.nonDeletedPredicate);
305 }
306
307 public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() {
308 return getPrimitives(OsmPrimitive.nonDeletedCompletePredicate);
309 }
310
311 public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() {
312 return getPrimitives(OsmPrimitive.nonDeletedPhysicalPredicate);
313 }
314
315 public Collection<OsmPrimitive> allModifiedPrimitives() {
316 return getPrimitives(OsmPrimitive.modifiedPredicate);
317 }
318
319 /**
320 * Adds a primitive to the dataset
321 *
322 * @param primitive the primitive.
323 */
324 public void addPrimitive(OsmPrimitive primitive) {
325 beginUpdate();
326 try {
327 if (getPrimitiveById(primitive) != null)
328 throw new DataIntegrityProblemException(
329 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()));
330
331 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reinexRelation to work properly)
332 boolean success = false;
333 if (primitive instanceof Node) {
334 success = nodes.add((Node) primitive);
335 } else if (primitive instanceof Way) {
336 success = ways.add((Way) primitive);
337 } else if (primitive instanceof Relation) {
338 success = relations.add((Relation) primitive);
339 }
340 if (!success)
341 throw new RuntimeException("failed to add primitive: "+primitive);
342 allPrimitives.add(primitive);
343 primitive.setDataset(this);
344 firePrimitivesAdded(Collections.singletonList(primitive), false);
345 } finally {
346 endUpdate();
347 }
348 }
349
350 /**
351 * Removes a primitive from the dataset. This method only removes the
352 * primitive form the respective collection of primitives managed
353 * by this dataset, i.e. from {@see #nodes}, {@see #ways}, or
354 * {@see #relations}. References from other primitives to this
355 * primitive are left unchanged.
356 *
357 * @param primitive the primitive
358 */
359 public void removePrimitive(PrimitiveId primitiveId) {
360 beginUpdate();
361 try {
362 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
363 if (primitive == null)
364 return;
365 boolean success = false;
366 if (primitive instanceof Node) {
367 success = nodes.remove(primitive);
368 } else if (primitive instanceof Way) {
369 success = ways.remove(primitive);
370 } else if (primitive instanceof Relation) {
371 success = relations.remove(primitive);
372 }
373 if (!success)
374 throw new RuntimeException("failed to remove primitive: "+primitive);
375 synchronized (selectionLock) {
376 selectedPrimitives.remove(primitive);
377 selectionSnapshot = null;
378 }
379 allPrimitives.remove(primitive);
380 primitive.setDataset(null);
381 firePrimitivesRemoved(Collections.singletonList(primitive), false);
382 } finally {
383 endUpdate();
384 }
385 }
386
387 /*---------------------------------------------------
388 * SELECTION HANDLING
389 *---------------------------------------------------*/
390
391 /**
392 * A list of listeners to selection changed events. The list is static, as listeners register
393 * themselves for any dataset selection changes that occur, regardless of the current active
394 * dataset. (However, the selection does only change in the active layer)
395 * @deprecated Use addSelectionListener/removeSelectionListener instead
396 */
397 @Deprecated
398 public static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<SelectionChangedListener>();
399
400 public static void addSelectionListener(SelectionChangedListener listener) {
401 ((CopyOnWriteArrayList<SelectionChangedListener>)selListeners).addIfAbsent(listener);
402 }
403
404 public static void removeSelectionListener(SelectionChangedListener listener) {
405 selListeners.remove(listener);
406 }
407
408 /**
409 * Notifies all registered {@see SelectionChangedListener} about the current selection in
410 * this dataset.
411 *
412 */
413 public void fireSelectionChanged(){
414 Collection<? extends OsmPrimitive> currentSelection = getSelected();
415 for (SelectionChangedListener l : selListeners) {
416 l.selectionChanged(currentSelection);
417 }
418 }
419
420 private LinkedHashSet<OsmPrimitive> selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
421 private Collection<OsmPrimitive> selectionSnapshot;
422
423 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
424 return new FilteredCollection<OsmPrimitive>(getSelected(), new Predicate<OsmPrimitive>() {
425 @Override
426 public boolean evaluate(OsmPrimitive primitive) {
427 return primitive instanceof Node || primitive instanceof Way;
428 }
429 });
430 }
431
432 /**
433 * Replies an unmodifiable collection of primitives currently selected
434 * in this dataset. May be empty, but not null.
435 *
436 * @return unmodifiable collection of primitives
437 */
438 public Collection<OsmPrimitive> getSelected() {
439 Collection<OsmPrimitive> currentList;
440 synchronized (selectionLock) {
441 if (selectionSnapshot == null) {
442 selectionSnapshot = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(selectedPrimitives));
443 }
444 currentList = selectionSnapshot;
445 }
446 return currentList;
447 }
448
449 /**
450 * Return selected nodes.
451 */
452 public Collection<Node> getSelectedNodes() {
453 return new SubclassFilteredCollection<OsmPrimitive, Node>(getSelected(), OsmPrimitive.nodePredicate);
454 }
455
456 /**
457 * Return selected ways.
458 */
459 public Collection<Way> getSelectedWays() {
460 return new SubclassFilteredCollection<OsmPrimitive, Way>(getSelected(), OsmPrimitive.wayPredicate);
461 }
462
463 /**
464 * Return selected relations.
465 */
466 public Collection<Relation> getSelectedRelations() {
467 return new SubclassFilteredCollection<OsmPrimitive, Relation>(getSelected(), OsmPrimitive.relationPredicate);
468 }
469
470 /**
471 * @return whether the selection is empty or not
472 */
473 public boolean selectionEmpty() {
474 return selectedPrimitives.isEmpty();
475 }
476
477 public boolean isSelected(OsmPrimitive osm) {
478 return selectedPrimitives.contains(osm);
479 }
480
481 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
482 boolean changed = false;
483 synchronized (selectionLock) {
484 for (PrimitiveId o : osm) {
485 changed = changed | this.__toggleSelected(o);
486 }
487 if (changed) {
488 selectionSnapshot = null;
489 }
490 }
491 if (changed) {
492 fireSelectionChanged();
493 }
494 }
495 public void toggleSelected(PrimitiveId... osm) {
496 toggleSelected(Arrays.asList(osm));
497 }
498 private boolean __toggleSelected(PrimitiveId primitiveId) {
499 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
500 if (primitive == null)
501 return false;
502 if (!selectedPrimitives.remove(primitive)) {
503 selectedPrimitives.add(primitive);
504 }
505 selectionSnapshot = null;
506 return true;
507 }
508
509 /**
510 * Sets the current selection to the primitives in <code>selection</code>.
511 * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
512 *
513 * @param selection the selection
514 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
515 */
516 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
517 boolean changed;
518 synchronized (selectionLock) {
519 boolean wasEmpty = selectedPrimitives.isEmpty();
520 selectedPrimitives = new LinkedHashSet<OsmPrimitive>();
521 changed = addSelected(selection, false)
522 || (!wasEmpty && selectedPrimitives.isEmpty());
523 if (changed) {
524 selectionSnapshot = null;
525 }
526 }
527
528 if (changed && fireSelectionChangeEvent) {
529 // If selection is not empty then event was already fired in addSelecteds
530 fireSelectionChanged();
531 }
532 }
533
534 /**
535 * Sets the current selection to the primitives in <code>selection</code>
536 * and notifies all {@see SelectionChangedListener}.
537 *
538 * @param selection the selection
539 */
540 public void setSelected(Collection<? extends PrimitiveId> selection) {
541 setSelected(selection, true /* fire selection change event */);
542 }
543
544 public void setSelected(PrimitiveId... osm) {
545 if (osm.length == 1 && osm[0] == null) {
546 setSelected();
547 return;
548 }
549 List<PrimitiveId> list = Arrays.asList(osm);
550 setSelected(list);
551 }
552
553 /**
554 * Adds the primitives in <code>selection</code> to the current selection
555 * and notifies all {@see SelectionChangedListener}.
556 *
557 * @param selection the selection
558 */
559 public void addSelected(Collection<? extends PrimitiveId> selection) {
560 addSelected(selection, true /* fire selection change event */);
561 }
562
563 public void addSelected(PrimitiveId... osm) {
564 addSelected(Arrays.asList(osm));
565 }
566
567 /**
568 * Adds the primitives in <code>selection</code> to the current selection.
569 * Notifies all {@see SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
570 *
571 * @param selection the selection
572 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
573 * @return if the selection was changed in the process
574 */
575 private boolean addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
576 boolean changed = false;
577 synchronized (selectionLock) {
578 for (PrimitiveId id: selection) {
579 OsmPrimitive primitive = getPrimitiveByIdChecked(id);
580 if (primitive != null) {
581 changed = changed | selectedPrimitives.add(primitive);
582 }
583 }
584 if (changed) {
585 selectionSnapshot = null;
586 }
587 }
588 if (fireSelectionChangeEvent && changed) {
589 fireSelectionChanged();
590 }
591 return changed;
592 }
593
594 /**
595 * Remove the selection from every value in the collection.
596 * @param list The collection to remove the selection from.
597 */
598 public void clearSelection(PrimitiveId... osm) {
599 clearSelection(Arrays.asList(osm));
600 }
601 public void clearSelection(Collection<? extends PrimitiveId> list) {
602 boolean changed = false;
603 synchronized (selectionLock) {
604 for (PrimitiveId id:list) {
605 OsmPrimitive primitive = getPrimitiveById(id);
606 if (primitive != null) {
607 changed = changed | selectedPrimitives.remove(primitive);
608 }
609 }
610 if (changed) {
611 selectionSnapshot = null;
612 }
613 }
614 if (changed) {
615 fireSelectionChanged();
616 }
617 }
618 public void clearSelection() {
619 if (!selectedPrimitives.isEmpty()) {
620 synchronized (selectionLock) {
621 selectedPrimitives.clear();
622 selectionSnapshot = null;
623 }
624 fireSelectionChanged();
625 }
626 }
627
628 @Override public DataSet clone() {
629 getReadLock().lock();
630 try {
631 DataSet ds = new DataSet();
632 HashMap<OsmPrimitive, OsmPrimitive> primMap = new HashMap<OsmPrimitive, OsmPrimitive>();
633 for (Node n : nodes) {
634 Node newNode = new Node(n);
635 primMap.put(n, newNode);
636 ds.addPrimitive(newNode);
637 }
638 for (Way w : ways) {
639 Way newWay = new Way(w);
640 primMap.put(w, newWay);
641 List<Node> newNodes = new ArrayList<Node>();
642 for (Node n: w.getNodes()) {
643 newNodes.add((Node)primMap.get(n));
644 }
645 newWay.setNodes(newNodes);
646 ds.addPrimitive(newWay);
647 }
648 // Because relations can have other relations as members we first clone all relations
649 // and then get the cloned members
650 for (Relation r : relations) {
651 Relation newRelation = new Relation(r, r.isNew());
652 newRelation.setMembers(null);
653 primMap.put(r, newRelation);
654 ds.addPrimitive(newRelation);
655 }
656 for (Relation r : relations) {
657 Relation newRelation = (Relation)primMap.get(r);
658 List<RelationMember> newMembers = new ArrayList<RelationMember>();
659 for (RelationMember rm: r.getMembers()) {
660 newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
661 }
662 newRelation.setMembers(newMembers);
663 }
664 for (DataSource source : dataSources) {
665 ds.dataSources.add(new DataSource(source.bounds, source.origin));
666 }
667 ds.version = version;
668 return ds;
669 } finally {
670 getReadLock().unlock();
671 }
672 }
673
674 /**
675 * Returns the total area of downloaded data (the "yellow rectangles").
676 * @return Area object encompassing downloaded data.
677 */
678 public Area getDataSourceArea() {
679 if (dataSources.isEmpty()) return null;
680 Area a = new Area();
681 for (DataSource source : dataSources) {
682 // create area from data bounds
683 a.add(new Area(source.bounds.asRect()));
684 }
685 return a;
686 }
687
688 /**
689 * returns a primitive with a given id from the data set. null, if no such primitive
690 * exists
691 *
692 * @param id uniqueId of the primitive. Might be < 0 for newly created primitives
693 * @param type the type of the primitive. Must not be null.
694 * @return the primitive
695 * @exception NullPointerException thrown, if type is null
696 */
697 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
698 return getPrimitiveById(new SimplePrimitiveId(id, type));
699 }
700
701 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
702 return primitivesMap.get(primitiveId);
703 }
704
705 /**
706 *
707 * @param primitiveId
708 * @param createNew
709 * @return
710 * @deprecated This method can created inconsistent dataset when called for node with id < 0 and createNew=true. That will add
711 * complete node without coordinates to dataset which is not allowed.
712 */
713 @Deprecated
714 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId, boolean createNew) {
715 OsmPrimitive result = primitivesMap.get(primitiveId);
716
717 if (result == null && createNew) {
718 switch (primitiveId.getType()) {
719 case NODE: result = new Node(primitiveId.getUniqueId(), true); break;
720 case WAY: result = new Way(primitiveId.getUniqueId(), true); break;
721 case RELATION: result = new Relation(primitiveId.getUniqueId(), true); break;
722 }
723 addPrimitive(result);
724 }
725
726 return result;
727 }
728
729 /**
730 * Show message and stack trace in log in case primitive is not found
731 * @param primitiveId
732 * @return Primitive by id.
733 */
734 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
735 OsmPrimitive result = getPrimitiveById(primitiveId);
736 if (result == null) {
737 System.out.println(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
738 + "at http://josm.openstreetmap.de/. This is not a critical error, it should be safe to continue in your work.",
739 primitiveId.getType(), Long.toString(primitiveId.getUniqueId())));
740 new Exception().printStackTrace();
741 }
742
743 return result;
744 }
745
746 private void deleteWay(Way way) {
747 way.setNodes(null);
748 way.setDeleted(true);
749 }
750
751 /**
752 * removes all references from ways in this dataset to a particular node
753 *
754 * @param node the node
755 */
756 public void unlinkNodeFromWays(Node node) {
757 beginUpdate();
758 try {
759 for (Way way: ways) {
760 List<Node> wayNodes = way.getNodes();
761 if (wayNodes.remove(node)) {
762 if (wayNodes.size() < 2) {
763 deleteWay(way);
764 } else {
765 way.setNodes(wayNodes);
766 }
767 }
768 }
769 } finally {
770 endUpdate();
771 }
772 }
773
774 /**
775 * removes all references from relations in this dataset to this primitive
776 *
777 * @param primitive the primitive
778 */
779 public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
780 beginUpdate();
781 try {
782 for (Relation relation : relations) {
783 List<RelationMember> members = relation.getMembers();
784
785 Iterator<RelationMember> it = members.iterator();
786 boolean removed = false;
787 while(it.hasNext()) {
788 RelationMember member = it.next();
789 if (member.getMember().equals(primitive)) {
790 it.remove();
791 removed = true;
792 }
793 }
794
795 if (removed) {
796 relation.setMembers(members);
797 }
798 }
799 } finally {
800 endUpdate();
801 }
802 }
803
804 /**
805 * removes all references from other primitives to the
806 * referenced primitive
807 *
808 * @param referencedPrimitive the referenced primitive
809 */
810 public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
811 beginUpdate();
812 try {
813 if (referencedPrimitive instanceof Node) {
814 unlinkNodeFromWays((Node)referencedPrimitive);
815 unlinkPrimitiveFromRelations(referencedPrimitive);
816 } else {
817 unlinkPrimitiveFromRelations(referencedPrimitive);
818 }
819 } finally {
820 endUpdate();
821 }
822 }
823
824 /**
825 * Replies true if there is at least one primitive in this dataset with
826 * {@see OsmPrimitive#isModified()} == <code>true</code>.
827 *
828 * @return true if there is at least one primitive in this dataset with
829 * {@see OsmPrimitive#isModified()} == <code>true</code>.
830 */
831 public boolean isModified() {
832 for (OsmPrimitive p: allPrimitives) {
833 if (p.isModified())
834 return true;
835 }
836 return false;
837 }
838
839 private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
840 if (!nodes.remove(node))
841 throw new RuntimeException("Reindexing node failed to remove");
842 node.setCoorInternal(newCoor, eastNorth);
843 if (!nodes.add(node))
844 throw new RuntimeException("Reindexing node failed to add");
845 for (OsmPrimitive primitive: node.getReferrers()) {
846 if (primitive instanceof Way) {
847 reindexWay((Way)primitive);
848 } else {
849 reindexRelation((Relation) primitive);
850 }
851 }
852 }
853
854 private void reindexWay(Way way) {
855 BBox before = way.getBBox();
856 if (!ways.remove(way))
857 throw new RuntimeException("Reindexing way failed to remove");
858 way.updatePosition();
859 if (!ways.add(way))
860 throw new RuntimeException("Reindexing way failed to add");
861 if (!way.getBBox().equals(before)) {
862 for (OsmPrimitive primitive: way.getReferrers()) {
863 reindexRelation((Relation)primitive);
864 }
865 }
866 }
867
868 private void reindexRelation(Relation relation) {
869 BBox before = relation.getBBox();
870 relation.updatePosition();
871 if (!before.equals(relation.getBBox())) {
872 for (OsmPrimitive primitive: relation.getReferrers()) {
873 reindexRelation((Relation) primitive);
874 }
875 }
876 }
877
878 public void addDataSetListener(DataSetListener dsl) {
879 listeners.addIfAbsent(dsl);
880 }
881
882 public void removeDataSetListener(DataSetListener dsl) {
883 listeners.remove(dsl);
884 }
885
886 /**
887 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
888 * {@link DataSetListener#dataChanged()} event is triggered after end of changes
889 * <br>
890 * Typical usecase should look like this:
891 * <pre>
892 * ds.beginUpdate();
893 * try {
894 * ...
895 * } finally {
896 * ds.endUpdate();
897 * }
898 * </pre>
899 */
900 public void beginUpdate() {
901 lock.writeLock().lock();
902 updateCount++;
903 }
904
905 /**
906 * @see DataSet#beginUpdate()
907 */
908 public void endUpdate() {
909 if (updateCount > 0) {
910 updateCount--;
911 if (updateCount == 0) {
912 List<AbstractDatasetChangedEvent> eventsCopy = new ArrayList<AbstractDatasetChangedEvent>(cachedEvents);
913 cachedEvents.clear();
914 lock.writeLock().unlock();
915
916 if (!eventsCopy.isEmpty()) {
917 lock.readLock().lock();
918 try {
919 if (eventsCopy.size() < MAX_SINGLE_EVENTS) {
920 for (AbstractDatasetChangedEvent event: eventsCopy) {
921 fireEventToListeners(event);
922 }
923 } else if (eventsCopy.size() == MAX_EVENTS) {
924 fireEventToListeners(new DataChangedEvent(this));
925 } else {
926 fireEventToListeners(new DataChangedEvent(this, eventsCopy));
927 }
928 } finally {
929 lock.readLock().unlock();
930 }
931 }
932 } else {
933 lock.writeLock().unlock();
934 }
935
936 } else
937 throw new AssertionError("endUpdate called without beginUpdate");
938 }
939
940 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
941 for (DataSetListener listener: listeners) {
942 event.fire(listener);
943 }
944 }
945
946 private void fireEvent(AbstractDatasetChangedEvent event) {
947 if (updateCount == 0)
948 throw new AssertionError("dataset events can be fired only when dataset is locked");
949 if (cachedEvents.size() < MAX_EVENTS) {
950 cachedEvents.add(event);
951 }
952 }
953
954 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
955 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
956 }
957
958 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
959 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
960 }
961
962 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
963 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
964 }
965
966 void fireRelationMembersChanged(Relation r) {
967 reindexRelation(r);
968 fireEvent(new RelationMembersChangedEvent(this, r));
969 }
970
971 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
972 reindexNode(node, newCoor, eastNorth);
973 fireEvent(new NodeMovedEvent(this, node));
974 }
975
976 void fireWayNodesChanged(Way way) {
977 reindexWay(way);
978 fireEvent(new WayNodesChangedEvent(this, way));
979 }
980
981 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
982 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
983 }
984
985 void fireHighlightingChanged(OsmPrimitive primitive) {
986 highlightUpdateCount++;
987 }
988
989 /**
990 * Invalidates the internal cache of projected east/north coordinates.
991 *
992 * This method can be invoked after the globally configured projection method
993 * changed. In contrast to {@link DataSet#reproject()} it only invalidates the
994 * cache and doesn't reproject the coordinates.
995 */
996 public void invalidateEastNorthCache() {
997 if (Main.getProjection() == null) return; // sanity check
998 try {
999 beginUpdate();
1000 for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) {
1001 n.invalidateEastNorthCache();
1002 }
1003 } finally {
1004 endUpdate();
1005 }
1006 }
1007
1008 public void cleanupDeletedPrimitives() {
1009 beginUpdate();
1010 try {
1011 if (cleanupDeleted(nodes.iterator())
1012 | cleanupDeleted(ways.iterator())
1013 | cleanupDeleted(relations.iterator())) {
1014 fireSelectionChanged();
1015 }
1016 } finally {
1017 endUpdate();
1018 }
1019 }
1020
1021 private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
1022 boolean changed = false;
1023 synchronized (selectionLock) {
1024 while (it.hasNext()) {
1025 OsmPrimitive primitive = it.next();
1026 if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) {
1027 selectedPrimitives.remove(primitive);
1028 selectionSnapshot = null;
1029 allPrimitives.remove(primitive);
1030 primitive.setDataset(null);
1031 changed = true;
1032 it.remove();
1033 }
1034 }
1035 if (changed) {
1036 selectionSnapshot = null;
1037 }
1038 }
1039 return changed;
1040 }
1041
1042 /**
1043 * Removes all primitives from the dataset and resets the currently selected primitives
1044 * to the empty collection. Also notifies selection change listeners if necessary.
1045 *
1046 */
1047 public void clear() {
1048 beginUpdate();
1049 try {
1050 clearSelection();
1051 for (OsmPrimitive primitive:allPrimitives) {
1052 primitive.setDataset(null);
1053 }
1054 nodes.clear();
1055 ways.clear();
1056 relations.clear();
1057 allPrimitives.clear();
1058 } finally {
1059 endUpdate();
1060 }
1061 }
1062
1063 /**
1064 * Marks all "invisible" objects as deleted. These objects should be always marked as
1065 * deleted when downloaded from the server. They can be undeleted later if necessary.
1066 *
1067 */
1068 public void deleteInvisible() {
1069 for (OsmPrimitive primitive:allPrimitives) {
1070 if (!primitive.isVisible()) {
1071 primitive.setDeleted(true);
1072 }
1073 }
1074 }
1075
1076 /**
1077 * <p>Replies the list of data source bounds.</p>
1078 *
1079 * <p>Dataset maintains a list of data sources which have been merged into the
1080 * data set. Each of these sources can optionally declare a bounding box of the
1081 * data it supplied to the dataset.</p>
1082 *
1083 * <p>This method replies the list of defined (non {@code null}) bounding boxes.</p>
1084 *
1085 * @return the list of data source bounds. An empty list, if no non-null data source
1086 * bounds are defined.
1087 */
1088 public List<Bounds> getDataSourceBounds() {
1089 List<Bounds> ret = new ArrayList<Bounds>(dataSources.size());
1090 for (DataSource ds : dataSources) {
1091 if (ds.bounds != null) {
1092 ret.add(ds.bounds);
1093 }
1094 }
1095 return ret;
1096 }
1097
1098 /* --------------------------------------------------------------------------------- */
1099 /* interface ProjectionChangeListner */
1100 /* --------------------------------------------------------------------------------- */
1101 @Override
1102 public void projectionChanged(Projection oldValue, Projection newValue) {
1103 invalidateEastNorthCache();
1104 }
1105}
Note: See TracBrowser for help on using the repository browser.