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

Last change on this file since 2381 was 2381, checked in by jttt, 14 years ago

Change most occurrences of Dataset.nodes/ways/relations with getNodes()/../.. or addPrimitive

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