source: josm/trunk/src/org/openstreetmap/josm/data/osm/DataSetMerger.java@ 8338

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

fix squid:S1319 - Declarations should use Java collection interfaces rather than specific implementation classes

  • Property svn:eol-style set to native
File size: 19.2 KB
RevLine 
[3719]1// License: GPL. For details, see LICENSE file.
[2433]2package org.openstreetmap.josm.data.osm;
[35]3
[1690]4import static org.openstreetmap.josm.tools.I18n.tr;
5
[3033]6import java.util.ArrayList;
[86]7import java.util.Collection;
[57]8import java.util.HashMap;
[2418]9import java.util.HashSet;
[3423]10import java.util.Iterator;
[57]11import java.util.LinkedList;
[1862]12import java.util.List;
[57]13import java.util.Map;
[2418]14import java.util.Set;
[35]15
[3034]16import org.openstreetmap.josm.data.conflict.Conflict;
[1750]17import org.openstreetmap.josm.data.conflict.ConflictCollection;
[4684]18import org.openstreetmap.josm.gui.progress.ProgressMonitor;
[2845]19import org.openstreetmap.josm.tools.CheckParameterUtil;
[35]20
21/**
[2433]22 * A dataset merger which takes a target and a source dataset and merges the source data set
23 * onto the target dataset.
[2512]24 *
[35]25 */
[2433]26public class DataSetMerger {
[35]27
[2417]28 /** the collection of conflicts created during merging */
[2985]29 private final ConflictCollection conflicts;
[86]30
[2417]31 /** the target dataset for merging */
[2443]32 private final DataSet targetDataSet;
[2417]33 /** the source dataset where primitives are merged from */
[2443]34 private final DataSet sourceDataSet;
[86]35
[1169]36 /**
[2417]37 * A map of all primitives that got replaced with other primitives.
[3040]38 * Key is the PrimitiveId in their dataset, the value is the PrimitiveId in my dataset
[1169]39 */
[3040]40 private final Map<PrimitiveId, PrimitiveId> mergedMap;
[2418]41 /** a set of primitive ids for which we have to fix references (to nodes and
42 * to relation members) after the first phase of merging
43 */
[2985]44 private final Set<PrimitiveId> objectsWithChildrenToMerge;
[3423]45 private final Set<OsmPrimitive> objectsToDelete;
[2443]46
[1690]47 /**
48 * constructor
[1862]49 *
[5881]50 * The visitor will merge <code>sourceDataSet</code> onto <code>targetDataSet</code>
[1862]51 *
[5881]52 * @param targetDataSet dataset with my primitives. Must not be null.
[2443]53 * @param sourceDataSet dataset with their primitives. Ignored, if null.
[8291]54 * @throws IllegalArgumentException if myDataSet is null
[1690]55 */
[8291]56 public DataSetMerger(DataSet targetDataSet, DataSet sourceDataSet) {
[2845]57 CheckParameterUtil.ensureParameterNotNull(targetDataSet, "targetDataSet");
[2443]58 this.targetDataSet = targetDataSet;
59 this.sourceDataSet = sourceDataSet;
[1750]60 conflicts = new ConflictCollection();
[7005]61 mergedMap = new HashMap<>();
62 objectsWithChildrenToMerge = new HashSet<>();
63 objectsToDelete = new HashSet<>();
[1169]64 }
[35]65
[1690]66 /**
[5881]67 * Merges a primitive onto primitives dataset.
[1862]68 *
[1690]69 * If other.id != 0 it tries to merge it with an corresponding primitive from
70 * my dataset with the same id. If this is not possible a conflict is remembered
[5266]71 * in {@link #conflicts}.
[1862]72 *
[5881]73 * If other.id == 0 (new primitive) it tries to find a primitive in my dataset with id == 0 which
[1690]74 * is semantically equal. If it finds one it merges its technical attributes onto
75 * my primitive.
[1862]76 *
[5881]77 * @param source the primitive to merge
78 * @param candidates a set of possible candidates for a new primitive
[1690]79 */
[4684]80 protected void mergePrimitive(OsmPrimitive source, Collection<? extends OsmPrimitive> candidates) {
[2443]81 if (!source.isNew() ) {
[6296]82 // try to merge onto a matching primitive with the same defined id
[1690]83 //
[2443]84 if (mergeById(source))
[1169]85 return;
[1690]86 } else {
[3039]87 // ignore deleted primitives from source
88 if (source.isDeleted()) return;
89
[1694]90 // try to merge onto a primitive which has no id assigned
[1690]91 // yet but which is equal in its semantic attributes
92 //
[2443]93 for (OsmPrimitive target : candidates) {
[3039]94 if (!target.isNew() || target.isDeleted()) {
[1690]95 continue;
96 }
[2443]97 if (target.hasEqualSemanticAttributes(source)) {
[3040]98 mergedMap.put(source.getPrimitiveId(), target.getPrimitiveId());
[6296]99 // copy the technical attributes from other version
[3039]100 target.setVisible(source.isVisible());
101 target.setUser(source.getUser());
102 target.setTimestamp(source.getTimestamp());
103 target.setModified(source.isModified());
104 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[1690]105 return;
106 }
[1169]107 }
108 }
[2433]109
[1690]110 // If we get here we didn't find a suitable primitive in
[2443]111 // the target dataset. Create a clone and add it to the target dataset.
[1690]112 //
[2443]113 OsmPrimitive target = null;
114 switch(source.getType()) {
115 case NODE: target = source.isNew() ? new Node() : new Node(source.getId()); break;
116 case WAY: target = source.isNew() ? new Way() : new Way(source.getId()); break;
117 case RELATION: target = source.isNew() ? new Relation() : new Relation(source.getId()); break;
[2990]118 default: throw new AssertionError();
[2417]119 }
[2443]120 target.mergeFrom(source);
121 targetDataSet.addPrimitive(target);
[3040]122 mergedMap.put(source.getPrimitiveId(), target.getPrimitiveId());
[2563]123 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[1169]124 }
[35]125
[8291]126 protected OsmPrimitive getMergeTarget(OsmPrimitive mergeSource) {
[3040]127 PrimitiveId targetId = mergedMap.get(mergeSource.getPrimitiveId());
[2417]128 if (targetId == null)
[2444]129 return null;
[3040]130 return targetDataSet.getPrimitiveById(targetId);
[2417]131 }
[6069]132
[5298]133 protected void addConflict(Conflict<?> c) {
134 c.setMergedMap(mergedMap);
135 conflicts.add(c);
136 }
[2417]137
[5298]138 protected void addConflict(OsmPrimitive my, OsmPrimitive their) {
[7005]139 addConflict(new Conflict<>(my, their));
[5298]140 }
141
[2417]142 protected void fixIncomplete(Way other) {
143 Way myWay = (Way)getMergeTarget(other);
144 if (myWay == null)
145 throw new RuntimeException(tr("Missing merge target for way with id {0}", other.getUniqueId()));
[1948]146 }
147
[1169]148 /**
149 * Postprocess the dataset and fix all merged references to point to the actual
150 * data.
151 */
152 public void fixReferences() {
[2443]153 for (Way w : sourceDataSet.getWays()) {
[2563]154 if (!conflicts.hasConflictForTheir(w) && objectsWithChildrenToMerge.contains(w.getPrimitiveId())) {
[2417]155 mergeNodeList(w);
156 fixIncomplete(w);
157 }
[1690]158 }
[2443]159 for (Relation r : sourceDataSet.getRelations()) {
[2563]160 if (!conflicts.hasConflictForTheir(r) && objectsWithChildrenToMerge.contains(r.getPrimitiveId())) {
[2417]161 mergeRelationMembers(r);
162 }
[1690]163 }
[3423]164
165 deleteMarkedObjects();
166 }
167
168 /**
169 * Deleted objects in objectsToDelete set and create conflicts for objects that cannot
170 * be deleted because they're referenced in the target dataset.
171 */
172 protected void deleteMarkedObjects() {
173 boolean flag;
174 do {
175 flag = false;
176 for (Iterator<OsmPrimitive> it = objectsToDelete.iterator();it.hasNext();) {
177 OsmPrimitive target = it.next();
178 OsmPrimitive source = sourceDataSet.getPrimitiveById(target.getPrimitiveId());
179 if (source == null)
[4253]180 throw new RuntimeException(tr("Object of type {0} with id {1} was marked to be deleted, but it''s missing in the source dataset",
[3423]181 target.getType(), target.getUniqueId()));
182
183 List<OsmPrimitive> referrers = target.getReferrers();
184 if (referrers.isEmpty()) {
[5350]185 resetPrimitive(target);
186 target.mergeFrom(source);
[3423]187 target.setDeleted(true);
188 it.remove();
189 flag = true;
190 } else {
191 for (OsmPrimitive referrer : referrers) {
192 // If one of object referrers isn't going to be deleted,
193 // add a conflict and don't delete the object
194 if (!objectsToDelete.contains(referrer)) {
[5298]195 addConflict(target, source);
[3423]196 it.remove();
197 flag = true;
198 break;
199 }
200 }
201 }
202
203 }
204 } while (flag);
205
206 if (!objectsToDelete.isEmpty()) {
207 // There are some more objects rest in the objectsToDelete set
208 // This can be because of cross-referenced relations.
209 for (OsmPrimitive osm: objectsToDelete) {
[5350]210 resetPrimitive(osm);
[3423]211 }
212 for (OsmPrimitive osm: objectsToDelete) {
213 osm.setDeleted(true);
214 osm.mergeFrom(sourceDataSet.getPrimitiveById(osm.getPrimitiveId()));
215 }
[2443]216 }
[1169]217 }
[6069]218
[5350]219 private final void resetPrimitive(OsmPrimitive osm) {
220 if (osm instanceof Way) {
221 ((Way) osm).setNodes(null);
222 } else if (osm instanceof Relation) {
223 ((Relation) osm).setMembers(null);
224 }
225 }
[86]226
[2443]227 /**
228 * Merges the node list of a source way onto its target way.
[2512]229 *
[2443]230 * @param source the source way
[8291]231 * @throws IllegalStateException if no target way can be found for the source way
232 * @throws IllegalStateException if there isn't a target node for one of the nodes in the source way
[2512]233 *
[2443]234 */
[8291]235 private void mergeNodeList(Way source) {
[2443]236 Way target = (Way)getMergeTarget(source);
237 if (target == null)
238 throw new IllegalStateException(tr("Missing merge target for way with id {0}", source.getUniqueId()));
[1753]239
[7005]240 List<Node> newNodes = new ArrayList<>(source.getNodesCount());
[2443]241 for (Node sourceNode : source.getNodes()) {
242 Node targetNode = (Node)getMergeTarget(sourceNode);
243 if (targetNode != null) {
[3336]244 newNodes.add(targetNode);
245 if (targetNode.isDeleted() && !conflicts.hasConflictForMy(targetNode)) {
[5298]246 addConflict(new Conflict<OsmPrimitive>(targetNode, sourceNode, true));
[3336]247 targetNode.setDeleted(false);
[1753]248 }
[2417]249 } else
[2443]250 throw new IllegalStateException(tr("Missing merge target for node with id {0}", sourceNode.getUniqueId()));
[2417]251 }
[2443]252 target.setNodes(newNodes);
[86]253 }
254
[2443]255 /**
256 * Merges the relation members of a source relation onto the corresponding target relation.
257 * @param source the source relation
[8291]258 * @throws IllegalStateException if there is no corresponding target relation
259 * @throws IllegalStateException if there isn't a corresponding target object for one of the relation
[2443]260 * members in source
261 */
[8291]262 private void mergeRelationMembers(Relation source) {
[2443]263 Relation target = (Relation) getMergeTarget(source);
264 if (target == null)
265 throw new IllegalStateException(tr("Missing merge target for relation with id {0}", source.getUniqueId()));
[8338]266 List<RelationMember> newMembers = new LinkedList<>();
[2443]267 for (RelationMember sourceMember : source.getMembers()) {
268 OsmPrimitive targetMember = getMergeTarget(sourceMember.getMember());
269 if (targetMember == null)
[2626]270 throw new IllegalStateException(tr("Missing merge target of type {0} with id {1}", sourceMember.getType(), sourceMember.getUniqueId()));
[3336]271 RelationMember newMember = new RelationMember(sourceMember.getRole(), targetMember);
272 newMembers.add(newMember);
273 if (targetMember.isDeleted() && !conflicts.hasConflictForMy(targetMember)) {
[7005]274 addConflict(new Conflict<>(targetMember, sourceMember.getMember(), true));
[3336]275 targetMember.setDeleted(false);
[2417]276 }
277 }
[2443]278 target.setMembers(newMembers);
[1169]279 }
[366]280
[1169]281 /**
[2443]282 * Tries to merge a primitive <code>source</code> into an existing primitive with the same id.
[1677]283 *
[2471]284 * @param source the source primitive which is to be merged into a target primitive
[2443]285 * @return true, if this method was able to merge <code>source</code> into a target object; false, otherwise
[1169]286 */
[2443]287 private boolean mergeById(OsmPrimitive source) {
288 OsmPrimitive target = targetDataSet.getPrimitiveById(source.getId(), source.getType());
[1567]289 // merge other into an existing primitive with the same id, if possible
290 //
[2443]291 if (target == null)
[2418]292 return false;
[2443]293 // found a corresponding target, remember it
[3040]294 mergedMap.put(source.getPrimitiveId(), target.getPrimitiveId());
[2443]295
296 if (target.getVersion() > source.getVersion())
297 // target.version > source.version => keep target version
[2418]298 return true;
[3362]299
300 if (target.isIncomplete() && !source.isIncomplete()) {
[2443]301 // target is incomplete, source completes it
302 // => merge source into target
[2418]303 //
[2443]304 target.mergeFrom(source);
[2563]305 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[2578]306 } else if (!target.isIncomplete() && source.isIncomplete()) {
[2443]307 // target is complete and source is incomplete
308 // => keep target, it has more information already
[2418]309 //
[2578]310 } else if (target.isIncomplete() && source.isIncomplete()) {
[2443]311 // target and source are incomplete. Doesn't matter which one to
312 // take. We take target.
[2418]313 //
[3965]314 } else if (!target.isModified() && !source.isModified() && target.isVisible() != source.isVisible() && target.getVersion() == source.getVersion())
315 // Same version, but different "visible" attribute and neither of them are modified.
316 // It indicates a serious problem in datasets.
[3423]317 // For example, datasets can be fetched from different OSM servers or badly hand-modified.
318 // We shouldn't merge that datasets.
[4253]319 throw new DataIntegrityProblemException(tr("Conflict in ''visible'' attribute for object of type {0} with id {1}",
[3423]320 target.getType(), target.getId()));
321 else if (target.isDeleted() && ! source.isDeleted() && target.getVersion() == source.getVersion()) {
[2443]322 // same version, but target is deleted. Assume target takes precedence
[2418]323 // otherwise too many conflicts when refreshing from the server
[2935]324 // but, if source has a referrer that is not in the target dataset there is a conflict
[3033]325 // If target dataset refers to the deleted primitive, conflict will be added in fixReferences method
[2935]326 for (OsmPrimitive referrer: source.getReferrers()) {
327 if (targetDataSet.getPrimitiveById(referrer.getPrimitiveId()) == null) {
[7005]328 addConflict(new Conflict<>(target, source, true));
[3034]329 target.setDeleted(false);
[2935]330 break;
331 }
[2933]332 }
[3423]333 } else if (! target.isModified() && source.isDeleted()) {
334 // target not modified. We can assume that source is the most recent version,
335 // so mark it to be deleted.
[2418]336 //
[3423]337 objectsToDelete.add(target);
[2443]338 } else if (! target.isModified() && source.isModified()) {
339 // target not modified. We can assume that source is the most recent version.
[3423]340 // clone it into target.
341 target.mergeFrom(source);
342 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[2443]343 } else if (! target.isModified() && !source.isModified() && target.getVersion() == source.getVersion()) {
[2753]344 // both not modified. Merge nevertheless.
345 // This helps when updating "empty" relations, see #4295
346 target.mergeFrom(source);
347 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[2443]348 } else if (! target.isModified() && !source.isModified() && target.getVersion() < source.getVersion()) {
[2418]349 // my not modified but other is newer. clone other onto mine.
350 //
[2443]351 target.mergeFrom(source);
[2563]352 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[2443]353 } else if (target.isModified() && ! source.isModified() && target.getVersion() == source.getVersion()) {
[2444]354 // target is same as source but target is modified
[2634]355 // => keep target and reset modified flag if target and source are semantically equal
356 if (target.hasEqualSemanticAttributes(source)) {
357 target.setModified(false);
358 }
[3423]359 } else if (source.isDeleted() != target.isDeleted()) {
360 // target is modified and deleted state differs.
361 // this have to be resolved manually.
362 //
[5298]363 addConflict(target,source);
[2443]364 } else if (! target.hasEqualSemanticAttributes(source)) {
[2444]365 // target is modified and is not semantically equal with source. Can't automatically
[2418]366 // resolve the differences
367 // => create a conflict
[5298]368 addConflict(target,source);
[2418]369 } else {
[3377]370 // clone from other. mergeFrom will mainly copy
[2418]371 // technical attributes like timestamp or user information. Semantic
372 // attributes should already be equal if we get here.
373 //
[2443]374 target.mergeFrom(source);
[2563]375 objectsWithChildrenToMerge.add(source.getPrimitiveId());
[1169]376 }
[2418]377 return true;
[1169]378 }
[1690]379
380 /**
[5266]381 * Runs the merge operation. Successfully merged {@link OsmPrimitive}s are in
[5881]382 * {@link #getTargetDataSet()}.
[1862]383 *
[5266]384 * See {@link #getConflicts()} for a map of conflicts after the merge operation.
[1690]385 */
386 public void merge() {
[4684]387 merge(null);
388 }
389
390 /**
[5266]391 * Runs the merge operation. Successfully merged {@link OsmPrimitive}s are in
[5881]392 * {@link #getTargetDataSet()}.
[4684]393 *
[5266]394 * See {@link #getConflicts()} for a map of conflicts after the merge operation.
[6881]395 * @param progressMonitor The progress monitor
[4684]396 */
397 public void merge(ProgressMonitor progressMonitor) {
[2443]398 if (sourceDataSet == null)
[2433]399 return;
[4684]400 if (progressMonitor != null) {
401 progressMonitor.beginTask(tr("Merging data..."), sourceDataSet.allPrimitives().size());
402 }
[2497]403 targetDataSet.beginUpdate();
404 try {
[7005]405 List<? extends OsmPrimitive> candidates = new ArrayList<>(targetDataSet.getNodes());
[2497]406 for (Node node: sourceDataSet.getNodes()) {
[4684]407 mergePrimitive(node, candidates);
408 if (progressMonitor != null) {
409 progressMonitor.worked(1);
410 }
[2497]411 }
[4684]412 candidates.clear();
[7005]413 candidates = new ArrayList<>(targetDataSet.getWays());
[2497]414 for (Way way: sourceDataSet.getWays()) {
[4684]415 mergePrimitive(way, candidates);
416 if (progressMonitor != null) {
417 progressMonitor.worked(1);
418 }
[2497]419 }
[4684]420 candidates.clear();
[7005]421 candidates = new ArrayList<>(targetDataSet.getRelations());
[2497]422 for (Relation relation: sourceDataSet.getRelations()) {
[4684]423 mergePrimitive(relation, candidates);
424 if (progressMonitor != null) {
425 progressMonitor.worked(1);
426 }
[2497]427 }
[4684]428 candidates.clear();
[2497]429 fixReferences();
430 } finally {
431 targetDataSet.endUpdate();
[1690]432 }
[4684]433 if (progressMonitor != null) {
434 progressMonitor.finishTask();
435 }
[1690]436 }
437
438 /**
439 * replies my dataset
[1862]440 *
[5881]441 * @return the own (target) data set
[1690]442 */
[2443]443 public DataSet getTargetDataSet() {
444 return targetDataSet;
[1690]445 }
446
447 /**
448 * replies the map of conflicts
[1862]449 *
[1690]450 * @return the map of conflicts
451 */
[1750]452 public ConflictCollection getConflicts() {
[1690]453 return conflicts;
454 }
[35]455}
Note: See TracBrowser for help on using the repository browser.