1 | package org.openstreetmap.josm.data.osm.visitor;
|
---|
2 |
|
---|
3 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
4 |
|
---|
5 | import java.util.Collection;
|
---|
6 | import java.util.HashMap;
|
---|
7 | import java.util.LinkedList;
|
---|
8 | import java.util.List;
|
---|
9 | import java.util.Map;
|
---|
10 | import java.util.logging.Logger;
|
---|
11 |
|
---|
12 | import org.openstreetmap.josm.data.conflict.ConflictCollection;
|
---|
13 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
14 | import org.openstreetmap.josm.data.osm.Node;
|
---|
15 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
16 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
17 | import org.openstreetmap.josm.data.osm.RelationMember;
|
---|
18 | import 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 | */
|
---|
25 | public 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 | }
|
---|