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

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

Fix for #3863 InvocationTargetException downloading additional data

  • Property svn:eol-style set to native
File size: 14.3 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 DataSet myDataset, Collection<P> myPrimitives, HashMap<Long, P> primitivesWithDefinedIds) {
101
102 if (!other.isNew() ) {
103 // try to merge onto a matching primitive with the same
104 // defined id
105 //
106 if (mergeById(primitivesWithDefinedIds, other))
107 return;
108 } else {
109 // try to merge onto a primitive which has no id assigned
110 // yet but which is equal in its semantic attributes
111 //
112 for (P my : myPrimitives) {
113 if (!my.isNew()) {
114 continue;
115 }
116 if (my.hasEqualSemanticAttributes(other)) {
117 if (my.isDeleted() != other.isDeleted()) {
118 // differences in deleted state have to be merged manually
119 //
120 conflicts.add(my, other);
121 } else {
122 // copy the technical attributes from other
123 // version
124 my.setVisible(other.isVisible());
125 my.setUser(other.getUser());
126 my.setTimestamp(other.getTimestamp());
127 my.setModified(other.isModified());
128 merged.put(other, my);
129 }
130 return;
131 }
132 }
133 }
134 // If we get here we didn't find a suitable primitive in
135 // my dataset. Just add other to my dataset.
136 //
137
138 //TODO primitive must be only in one dataset at one time, so remove it from theirDataset. This obviously
139 // breaks theirDataset and we can only hope that nobody will try to use theirDataset after merging is finished
140 theirDataSet.removePrimitive(other);
141 myDataset.addPrimitive(other);
142 }
143
144 public void visit(Node other) {
145 mergePrimitive(other, myDataSet, myDataSet.getNodes(), nodeshash);
146 }
147
148 public void visit(Way other) {
149 fixWay(other);
150 mergePrimitive(other, myDataSet, myDataSet.getWays(), wayshash);
151 }
152
153 public void visit(Relation other) {
154 fixRelation(other);
155 mergePrimitive(other, myDataSet, myDataSet.getRelations(), relshash);
156 }
157
158 protected void fixIncomplete(Way w) {
159 if (!w.incomplete)return;
160 if (w.incomplete && w.getNodesCount() == 0) return;
161 for (Node n: w.getNodes()) {
162 if (n.incomplete) return;
163 }
164 w.incomplete = false;
165 }
166
167 /**
168 * Postprocess the dataset and fix all merged references to point to the actual
169 * data.
170 */
171 public void fixReferences() {
172 for (Way w : myDataSet.getWays()) {
173 fixWay(w);
174 fixIncomplete(w);
175 }
176 for (Relation r : myDataSet.getRelations()) {
177 fixRelation(r);
178 }
179 for (OsmPrimitive osm : conflicts.getMyConflictParties())
180 if (osm instanceof Way) {
181 fixWay((Way) osm);
182 } else if (osm instanceof Relation) {
183 fixRelation((Relation) osm);
184 }
185 }
186
187
188 private void fixWay(Way w) {
189 boolean replacedSomething = false;
190 List<Node> newNodes = new LinkedList<Node>();
191 for (Node myNode : w.getNodes()) {
192 Node mergedNode = (Node) merged.get(myNode);
193 if (mergedNode != null) {
194 if (!mergedNode.isDeleted()) {
195 newNodes.add(mergedNode);
196 } else {
197 // we've removed a node from a way during merging.
198 // Flag the way as being modified.
199 //
200 w.setModified(true);
201 }
202 replacedSomething = true;
203 } else {
204 newNodes.add(myNode);
205 }
206 }
207 if (replacedSomething) {
208 w.setNodes(newNodes);
209 }
210 }
211
212 private void fixRelation(Relation r) {
213 boolean replacedSomething = false;
214 LinkedList<RelationMember> newMembers = new LinkedList<RelationMember>();
215 for (RelationMember myMember : r.getMembers()) {
216 OsmPrimitive mergedMember = merged.get(myMember.getMember());
217 if (mergedMember == null) {
218 newMembers.add(myMember);
219 } else {
220 if (! mergedMember.isDeleted()) {
221 RelationMember newMember = new RelationMember(myMember.getRole(), mergedMember);
222 newMembers.add(newMember);
223 }
224 replacedSomething = true;
225 }
226 }
227 if (replacedSomething) {
228 r.setMembers(newMembers);
229 }
230 }
231
232 /**
233 * Tries to merge a primitive <code>other</code> into an existing primitive with the same id.
234 *
235 * @param myPrimitivesWithDefinedIds the map of primitives (potential merge targets) with an id <> 0, for faster lookup
236 * by id. Key is the id, value the primitive with the given value. myPrimitives.valueSet() is a
237 * subset of primitives.
238 * @param other the other primitive which is to be merged onto a primitive in my primitives
239 * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise
240 */
241 private <P extends OsmPrimitive> boolean mergeById(HashMap<Long, P> myPrimitivesWithDefinedIds, P other) {
242
243 // merge other into an existing primitive with the same id, if possible
244 //
245 if (myPrimitivesWithDefinedIds.containsKey(other.getId())) {
246 P my = myPrimitivesWithDefinedIds.get(other.getId());
247 if (my.getVersion() <= other.getVersion()) {
248 if (! my.isVisible() && other.isVisible()) {
249 // should not happen
250 //
251 logger.warning(tr("My primitive with id {0} and version {1} is visible although "
252 + "their primitive with lower version {2} is not visible. "
253 + "Can't deal with this inconsistency. Keeping my primitive. ",
254 Long.toString(my.getId()),Long.toString(my.getVersion()), Long.toString(other.getVersion())
255 ));
256 merged.put(other, my);
257 } else if (my.isVisible() && ! other.isVisible()) {
258 // this is always a conflict because the user has to decide whether
259 // he wants to create a clone of its local primitive or whether he
260 // wants to purge my from the local dataset. He can't keep it unchanged
261 // because it was deleted on the server.
262 //
263 conflicts.add(my,other);
264 } else if (my.incomplete && !other.incomplete) {
265 // my is incomplete, other completes it
266 // => merge other onto my
267 //
268 my.incomplete = false;
269 my.cloneFrom(other);
270 merged.put(other, my);
271 } else if (!my.incomplete && other.incomplete) {
272 // my is complete and the other is incomplete
273 // => keep mine, we have more information already
274 //
275 merged.put(other, my);
276 } else if (my.incomplete && other.incomplete) {
277 // my and other are incomplete. Doesn't matter which one to
278 // take. We take mine.
279 //
280 merged.put(other, my);
281 } else if (my.isDeleted() && ! other.isDeleted() && my.getVersion() == other.getVersion()) {
282 // same version, but my is deleted. Assume mine takes precedence
283 // otherwise too many conflicts when refreshing from the server
284 merged.put(other, my);
285 } else if (my.isDeleted() != other.isDeleted()) {
286 // differences in deleted state have to be resolved manually
287 //
288 conflicts.add(my,other);
289 } else if (! my.isModified() && other.isModified()) {
290 // my not modified. We can assume that other is the most recent version.
291 // clone it onto my. But check first, whether other is deleted. if so,
292 // make sure that my is not references anymore in myDataSet.
293 //
294 if (other.isDeleted()) {
295 myDataSet.unlinkReferencesToPrimitive(my);
296 }
297 my.cloneFrom(other);
298 merged.put(other, my);
299 } else if (! my.isModified() && !other.isModified() && my.getVersion() == other.getVersion()) {
300 // both not modified. Keep mine
301 //
302 merged.put(other,my);
303 } else if (! my.isModified() && !other.isModified() && my.getVersion() < other.getVersion()) {
304 // my not modified but other is newer. clone other onto mine.
305 //
306 my.cloneFrom(other);
307 merged.put(other,my);
308 } else if (my.isModified() && ! other.isModified() && my.getVersion() == other.getVersion()) {
309 // my is same as other but mine is modified
310 // => keep mine
311 merged.put(other, my);
312 } else if (! my.hasEqualSemanticAttributes(other)) {
313 // my is modified and is not semantically equal with other. Can't automatically
314 // resolve the differences
315 // => create a conflict
316 conflicts.add(my,other);
317 } else {
318 // clone from other, but keep the modified flag. Clone will mainly copy
319 // technical attributes like timestamp or user information. Semantic
320 // attributes should already be equal if we get here.
321 //
322 my.cloneFrom(other);
323 my.setModified(true);
324 merged.put(other, my);
325 }
326 } else {
327 // my.version > other.version => keep my version
328 merged.put(other, my);
329 }
330 return true;
331 }
332 return false;
333 }
334
335
336 /**
337 * Runs the merge operation. Successfully merged {@see OsmPrimitive}s are in
338 * {@see #getMyDataSet()}.
339 *
340 * See {@see #getConflicts()} for a map of conflicts after the merge operation.
341 */
342 public void merge() {
343 for (final OsmPrimitive primitive : theirDataSet.allPrimitives()) {
344 primitive.visit(this);
345 }
346 fixReferences();
347 }
348
349 /**
350 * replies my dataset
351 *
352 * @return
353 */
354 public DataSet getMyDataSet() {
355 return myDataSet;
356 }
357
358
359 /**
360 * replies the map of conflicts
361 *
362 * @return the map of conflicts
363 */
364 public ConflictCollection getConflicts() {
365 return conflicts;
366 }
367}
Note: See TracBrowser for help on using the repository browser.