source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java@ 1750

Last change on this file since 1750 was 1750, checked in by Gubaer, 15 years ago

new: replaced global conflict list by conflict list per layer, similar to datasets

  • Property svn:eol-style set to native
File size: 12.8 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm.visitor;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.Collection;
7import java.util.HashMap;
8import java.util.LinkedList;
9import java.util.Map;
10import java.util.logging.Logger;
11
12import org.openstreetmap.josm.data.conflict.ConflictCollection;
13import org.openstreetmap.josm.data.osm.DataSet;
14import org.openstreetmap.josm.data.osm.Node;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.Relation;
17import org.openstreetmap.josm.data.osm.RelationMember;
18import org.openstreetmap.josm.data.osm.Way;
19
20/**
21 * A visitor that gets a data set at construction time and merges every visited object
22 * into it.
23 *
24 * @author imi
25 * @author Gubaer
26 */
27public class MergeVisitor extends AbstractVisitor {
28 private static Logger logger = Logger.getLogger(MergeVisitor.class.getName());
29
30 /**
31 * Map from primitives in the database to visited primitives. (Attention: The other way
32 * round than merged)
33 */
34 private ConflictCollection conflicts;
35
36
37 private final DataSet myDataSet;
38 private final DataSet theirDataSet;
39
40 private final HashMap<Long, Node> nodeshash = new HashMap<Long, Node>();
41 private final HashMap<Long, Way> wayshash = new HashMap<Long, Way>();
42 private final HashMap<Long, Relation> relshash = new HashMap<Long, Relation>();
43
44 /**
45 * A list of all primitives that got replaced with other primitives.
46 * Key is the primitives in the other's dataset and the value is the one that is now
47 * in ds.nodes instead.
48 */
49 private Map<OsmPrimitive, OsmPrimitive> merged;
50
51 /**
52 * constructor
53 *
54 * The visitor will merge <code>theirDataSet</code> onto <code>myDataSet</code>
55 *
56 * @param myDataSet dataset with my primitives
57 * @param theirDataSet dataset with their primitives.
58 */
59 public MergeVisitor(DataSet myDataSet, DataSet theirDataSet) {
60 this.myDataSet = myDataSet;
61 this.theirDataSet = theirDataSet;
62
63 for (Node n : myDataSet.nodes) if (n.id != 0) {
64 nodeshash.put(n.id, n);
65 }
66 for (Way w : myDataSet.ways) if (w.id != 0) {
67 wayshash.put(w.id, w);
68 }
69 for (Relation r : myDataSet.relations) if (r.id != 0) {
70 relshash.put(r.id, r);
71 }
72 conflicts = new ConflictCollection();
73 merged = new HashMap<OsmPrimitive, OsmPrimitive>();
74 }
75
76 /**
77 * Merges a primitive <code>other</code> of type <P> onto my primitives.
78 *
79 * If other.id != 0 it tries to merge it with an corresponding primitive from
80 * my dataset with the same id. If this is not possible a conflict is remembered
81 * in {@see #conflicts}.
82 *
83 * If other.id == 0 it tries to find a primitive in my dataset with id == 0 which
84 * is semantically equal. If it finds one it merges its technical attributes onto
85 * my primitive.
86 *
87 * @param <P> the type of the other primitive
88 * @param other the other primitive
89 * @param myPrimitives the collection of my relevant primitives (i.e. only my
90 * primitives of the same type)
91 * @param otherPrimitives the collection of the other primitives
92 * @param primitivesWithDefinedIds the collection of my primitives with an
93 * assigned id (i.e. id != 0)
94 */
95 protected <P extends OsmPrimitive> void mergePrimitive(P other,
96 Collection<P> myPrimitives, Collection<P> otherPrimitives,
97 HashMap<Long, P> primitivesWithDefinedIds) {
98
99 if (other.id > 0 ) {
100 // try to merge onto a matching primitive with the same
101 // defined id
102 //
103 if (mergeById(myPrimitives, primitivesWithDefinedIds, other))
104 return;
105 } else {
106 // try to merge onto a primitive which has no id assigned
107 // yet but which is equal in its semantic attributes
108 //
109 for (P my : myPrimitives) {
110 if (my.id >0 ) {
111 continue;
112 }
113 if (my.hasEqualSemanticAttributes(other)) {
114 if (my.deleted != other.deleted) {
115 // differences in deleted state have to be merged manually
116 //
117 conflicts.add(my, other);
118 } else {
119 // copy the technical attributes from other
120 // version
121 my.visible = other.visible;
122 my.user = other.user;
123 my.setTimestamp(other.getTimestamp());
124 my.modified = other.modified;
125 merged.put(other, my);
126 }
127 return;
128 }
129 }
130 }
131 // If we get here we didn't find a suitable primitive in
132 // my dataset. Just add other to my dataset.
133 //
134 myPrimitives.add(other);
135 }
136
137 public void visit(Node other) {
138 mergePrimitive(other, myDataSet.nodes, theirDataSet.nodes, nodeshash);
139 }
140
141 public void visit(Way other) {
142 fixWay(other);
143 mergePrimitive(other, myDataSet.ways, theirDataSet.ways, wayshash);
144 }
145
146 public void visit(Relation other) {
147 fixRelation(other);
148 mergePrimitive(other, myDataSet.relations, theirDataSet.relations, relshash);
149 }
150
151 /**
152 * Postprocess the dataset and fix all merged references to point to the actual
153 * data.
154 */
155 public void fixReferences() {
156 for (Way w : myDataSet.ways) {
157 fixWay(w);
158 }
159 for (Relation r : myDataSet.relations) {
160 fixRelation(r);
161 }
162 for (OsmPrimitive osm : conflicts.getMyConflictParties())
163 if (osm instanceof Way) {
164 fixWay((Way)osm);
165 } else if (osm instanceof Relation) {
166 fixRelation((Relation) osm);
167 }
168 }
169
170 private void fixWay(Way w) {
171 boolean replacedSomething = false;
172 LinkedList<Node> newNodes = new LinkedList<Node>();
173 for (Node n : w.nodes) {
174 Node otherN = (Node) merged.get(n);
175 newNodes.add(otherN == null ? n : otherN);
176 if (otherN != null) {
177 replacedSomething = true;
178 }
179 }
180 if (replacedSomething) {
181 w.nodes.clear();
182 w.nodes.addAll(newNodes);
183 }
184 }
185
186 private void fixRelation(Relation r) {
187 boolean replacedSomething = false;
188 LinkedList<RelationMember> newMembers = new LinkedList<RelationMember>();
189 for (RelationMember m : r.members) {
190 OsmPrimitive otherP = merged.get(m.member);
191 if (otherP == null) {
192 newMembers.add(m);
193 } else {
194 RelationMember mnew = new RelationMember(m);
195 mnew.member = otherP;
196 newMembers.add(mnew);
197 replacedSomething = true;
198 }
199 }
200 if (replacedSomething) {
201 r.members.clear();
202 r.members.addAll(newMembers);
203 }
204 }
205
206 /**
207 * Tries to merge a primitive <code>other</code> into an existing primitive with the same id.
208 *
209 * @param myPrimitives the complete set of my primitives (potential merge targets)
210 * @param myPrimitivesWithDefinedIds the map of primitives (potential merge targets) with an id <> 0, for faster lookup
211 * by id. Key is the id, value the primitive with the given value. myPrimitives.valueSet() is a
212 * subset of primitives.
213 * @param other the other primitive which is to be merged onto a primitive in my primitives
214 * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise
215 */
216 private <P extends OsmPrimitive> boolean mergeById(
217 Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWithDefinedIds, P other) {
218
219 // merge other into an existing primitive with the same id, if possible
220 //
221 if (myPrimitivesWithDefinedIds.containsKey(other.id)) {
222 P my = myPrimitivesWithDefinedIds.get(other.id);
223 if (my.version <= other.version) {
224 if (! my.visible && other.visible) {
225 // should not happen
226 //
227 logger.warning(tr("My primitive with id {0} and version {1} is visible although "
228 + "their primitive with lower version {2} is not visible. "
229 + "Can't deal with this inconsistency. Keeping my primitive. ",
230 Long.toString(my.id),Long.toString(my.version), Long.toString(other.version)
231 ));
232 merged.put(other, my);
233 } else if (my.visible && ! other.visible) {
234 // this is always a conflict because the user has to decide whether
235 // he wants to create a clone of its local primitive or whether he
236 // wants to purge my from the local dataset. He can't keep it unchanged
237 // because it was deleted on the server.
238 //
239 conflicts.add(my,other);
240 } else if (my.incomplete) {
241 // my is incomplete, other completes it
242 // => merge other onto my
243 //
244 my.incomplete = false;
245 my.cloneFrom(other);
246 merged.put(other, my);
247 } else if (my.deleted && ! other.deleted && my.version == other.version) {
248 // same version, but my is deleted. Assume mine takes precedence
249 // otherwise too many conflicts when refreshing from the server
250 merged.put(other, my);
251 } else if (my.deleted != other.deleted) {
252 // differences in deleted state have to be resolved manually
253 //
254 conflicts.add(my,other);
255 } else if (! my.modified && other.modified) {
256 // my not modified. We can assume that other is the most recent version.
257 // clone it onto my. But check first, whether other is deleted. if so,
258 // make sure that my is not references anymore in myDataSet.
259 //
260 if (other.deleted) {
261 myDataSet.unlinkReferencesToPrimitive(my);
262 }
263 my.cloneFrom(other);
264 merged.put(other, my);
265 } else if (! my.modified && !other.modified && my.version == other.version) {
266 // both not modified. Keep mine
267 //
268 merged.put(other,my);
269 } else if (! my.modified && !other.modified && my.version < other.version) {
270 // my not modified but other is newer. clone other onto mine.
271 //
272 my.cloneFrom(other);
273 merged.put(other,my);
274 } else if (my.modified && ! other.modified && my.version == other.version) {
275 // my is same as other but mine is modified
276 // => keep mine
277 merged.put(other, my);
278 } else if (! my.hasEqualSemanticAttributes(other)) {
279 // my is modified and is not semantically equal with other. Can't automatically
280 // resolve the differences
281 // => create a conflict
282 conflicts.add(my,other);
283 } else {
284 // clone from other, but keep the modified flag. Clone will mainly copy
285 // technical attributes like timestamp or user information. Semantic
286 // attributes should already be equal if we get here.
287 //
288 my.cloneFrom(other);
289 my.modified = true;
290 merged.put(other, my);
291 }
292 } else {
293 // my.version > other.version => keep my version
294 merged.put(other, my);
295 }
296 return true;
297 }
298 return false;
299 }
300
301
302 /**
303 * Runs the merge operation. Successfully merged {@see OsmPrimitive}s are in
304 * {@see #getMyDataSet()}.
305 *
306 * See {@see #getConflicts()} for a map of conflicts after the merge operation.
307 */
308 public void merge() {
309 for (final OsmPrimitive primitive : theirDataSet.allPrimitives()) {
310 primitive.visit(this);
311 }
312 fixReferences();
313 }
314
315 /**
316 * replies my dataset
317 *
318 * @return
319 */
320 public DataSet getMyDataSet() {
321 return myDataSet;
322 }
323
324
325 /**
326 * replies the map of conflicts
327 *
328 * @return the map of conflicts
329 */
330 public ConflictCollection getConflicts() {
331 return conflicts;
332 }
333}
Note: See TracBrowser for help on using the repository browser.