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

Last change on this file since 2273 was 2273, checked in by jttt, 15 years ago

Replace testing for id <= 0 with isNew() method

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