source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java@ 4115

Last change on this file since 4115 was 4115, checked in by stoecker, 13 years ago

fix #4564 - conflict manager: highlight conflict on properties

  • Property svn:eol-style set to native
File size: 11.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.pair.properties;
3
4import static org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType.UNDECIDED;
5
6import java.beans.PropertyChangeListener;
7import java.beans.PropertyChangeSupport;
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.List;
11import java.util.Observable;
12
13import org.openstreetmap.josm.command.Command;
14import org.openstreetmap.josm.command.CoordinateConflictResolveCommand;
15import org.openstreetmap.josm.command.DeletedStateConflictResolveCommand;
16import org.openstreetmap.josm.data.conflict.Conflict;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType;
21import org.openstreetmap.josm.tools.CheckParameterUtil;
22
23/**
24 * This is the model for resolving conflicts in the properties of the
25 * {@see OsmPrimitive}s. In particular, it represents conflicts in the coordiates of {@see Node}s and
26 * the deleted or visible state of {@see OsmPrimitive}s.
27 *
28 * This model is an {@see Observable}. It notifies registered {@see Observer}s whenever the
29 * internal state changes.
30 *
31 * This model also emits property changes for {@see #RESOLVED_COMPLETELY_PROP}. Property change
32 * listeners may register themselves using {@see #addPropertyChangeListener(PropertyChangeListener)}.
33 *
34 * @see Node#getCoor()
35 * @see OsmPrimitive#deleted
36 * @see OsmPrimitive#visible
37 *
38 */
39public class PropertiesMergeModel extends Observable {
40
41 static public final String RESOLVED_COMPLETELY_PROP = PropertiesMergeModel.class.getName() + ".resolvedCompletely";
42 static public final String DELETE_PRIMITIVE_PROP = PropertiesMergeModel.class.getName() + ".deletePrimitive";
43
44 private OsmPrimitive my;
45
46 private LatLon myCoords;
47 private LatLon theirCoords;
48 private MergeDecisionType coordMergeDecision;
49
50 private boolean myDeletedState;
51 private boolean theirDeletedState;
52 private List<OsmPrimitive> myReferrers;
53 private List<OsmPrimitive> theirReferrers;
54 private MergeDecisionType deletedMergeDecision;
55 private final PropertyChangeSupport support;
56 private boolean resolvedCompletely;
57
58 public void addPropertyChangeListener(PropertyChangeListener listener) {
59 support.addPropertyChangeListener(listener);
60 }
61
62 public void removePropertyChangeListener(PropertyChangeListener listener) {
63 support.removePropertyChangeListener(listener);
64 }
65
66 public void fireCompletelyResolved() {
67 boolean oldValue = resolvedCompletely;
68 resolvedCompletely = isResolvedCompletely();
69 support.firePropertyChange(RESOLVED_COMPLETELY_PROP, oldValue, resolvedCompletely);
70 }
71
72 public PropertiesMergeModel() {
73 coordMergeDecision = UNDECIDED;
74 deletedMergeDecision = UNDECIDED;
75 support = new PropertyChangeSupport(this);
76 resolvedCompletely = false;
77 }
78
79 /**
80 * replies true if there is a coordinate conflict and if this conflict is
81 * resolved
82 *
83 * @return true if there is a coordinate conflict and if this conflict is
84 * resolved; false, otherwise
85 */
86 public boolean isDecidedCoord() {
87 return ! coordMergeDecision.equals(UNDECIDED);
88 }
89
90 /**
91 * replies true if there is a conflict in the deleted state and if this conflict is
92 * resolved
93 *
94 * @return true if there is a conflict in the deleted state and if this conflict is
95 * resolved; false, otherwise
96 */
97 public boolean isDecidedDeletedState() {
98 return ! deletedMergeDecision.equals(UNDECIDED);
99 }
100
101 /**
102 * replies true if the current decision for the coordinate conflict is <code>decision</code>
103 *
104 * @return true if the current decision for the coordinate conflict is <code>decision</code>;
105 * false, otherwise
106 */
107 public boolean isCoordMergeDecision(MergeDecisionType decision) {
108 return coordMergeDecision.equals(decision);
109 }
110
111 /**
112 * replies true if the current decision for the deleted state conflict is <code>decision</code>
113 *
114 * @return true if the current decision for the deleted state conflict is <code>decision</code>;
115 * false, otherwise
116 */
117 public boolean isDeletedStateDecision(MergeDecisionType decision) {
118 return deletedMergeDecision.equals(decision);
119 }
120
121 /**
122 * populates the model with the differences between my and their version
123 *
124 * @param my my version of the primitive
125 * @param their their version of the primitive
126 */
127 public void populate(Conflict<? extends OsmPrimitive> conflict) {
128 this.my = conflict.getMy();
129 OsmPrimitive their = conflict.getTheir();
130 if (my instanceof Node) {
131 myCoords = ((Node)my).getCoor();
132 theirCoords = ((Node)their).getCoor();
133 } else {
134 myCoords = null;
135 theirCoords = null;
136 }
137
138 myDeletedState = conflict.isMyDeleted() || my.isDeleted();
139 theirDeletedState = their.isDeleted();
140
141 myReferrers = my.getDataSet() == null?Collections.<OsmPrimitive>emptyList():my.getReferrers();
142 theirReferrers = their.getDataSet() == null?Collections.<OsmPrimitive>emptyList():their.getReferrers();
143
144 coordMergeDecision = UNDECIDED;
145 deletedMergeDecision = UNDECIDED;
146 setChanged();
147 notifyObservers();
148 /* call fire directly, to allow null as old value, otherwise the call can be
149 optimized away when resolvedCompletely is false. */
150 support.firePropertyChange(RESOLVED_COMPLETELY_PROP, null, resolvedCompletely);
151 //fireCompletelyResolved();
152 }
153
154 /**
155 * replies the coordinates of my {@see OsmPrimitive}. null, if my primitive hasn't
156 * coordinates (i.e. because it is a {@see Way}).
157 *
158 * @return the coordinates of my {@see OsmPrimitive}. null, if my primitive hasn't
159 * coordinates (i.e. because it is a {@see Way}).
160 */
161 public LatLon getMyCoords() {
162 return myCoords;
163 }
164
165 /**
166 * replies the coordinates of their {@see OsmPrimitive}. null, if their primitive hasn't
167 * coordinates (i.e. because it is a {@see Way}).
168 *
169 * @return the coordinates of my {@see OsmPrimitive}. null, if my primitive hasn't
170 * coordinates (i.e. because it is a {@see Way}).
171 */
172 public LatLon getTheirCoords() {
173 return theirCoords;
174 }
175
176 /**
177 * replies the coordinates of the merged {@see OsmPrimitive}. null, if the current primitives
178 * have no coordinates or if the conflict is yet {@see MergeDecisionType#UNDECIDED}
179 *
180 * @return the coordinates of the merged {@see OsmPrimitive}. null, if the current primitives
181 * have no coordinates or if the conflict is yet {@see MergeDecisionType#UNDECIDED}
182 */
183 public LatLon getMergedCoords() {
184 switch(coordMergeDecision) {
185 case KEEP_MINE: return myCoords;
186 case KEEP_THEIR: return theirCoords;
187 case UNDECIDED: return null;
188 }
189 // should not happen
190 return null;
191 }
192
193 /**
194 * decides a conflict between my and their coordinates
195 *
196 * @param decision the decision
197 */
198 public void decideCoordsConflict(MergeDecisionType decision) {
199 coordMergeDecision = decision;
200 setChanged();
201 notifyObservers();
202 fireCompletelyResolved();
203 }
204
205 /**
206 * replies my deleted state,
207 * @return
208 */
209 public Boolean getMyDeletedState() {
210 return myDeletedState;
211 }
212
213 public Boolean getTheirDeletedState() {
214 return theirDeletedState;
215 }
216
217 public Boolean getMergedDeletedState() {
218 switch(deletedMergeDecision) {
219 case KEEP_MINE: return myDeletedState;
220 case KEEP_THEIR: return theirDeletedState;
221 case UNDECIDED: return null;
222 }
223 // should not happen
224 return null;
225 }
226
227 /**
228 * returns my referrers,
229 * @return my referrers
230 */
231 public List<OsmPrimitive> getMyReferrers() {
232 return myReferrers;
233 }
234
235 /**
236 * returns their referrers,
237 * @return their referrers
238 */
239 public List<OsmPrimitive> getTheirReferrers() {
240 return theirReferrers;
241 }
242
243 private boolean getMergedDeletedState(MergeDecisionType decision) {
244 switch (decision) {
245 case KEEP_MINE:
246 return myDeletedState;
247 case KEEP_THEIR:
248 return theirDeletedState;
249 default:
250 return false;
251 }
252 }
253
254 /**
255 * decides the conflict between two deleted states
256 * @param decision the decision (must not be null)
257 *
258 * @throws IllegalArgumentException thrown, if decision is null
259 */
260 public void decideDeletedStateConflict(MergeDecisionType decision) throws IllegalArgumentException{
261 CheckParameterUtil.ensureParameterNotNull(decision, "decision");
262
263 boolean oldMergedDeletedState = getMergedDeletedState(this.deletedMergeDecision);
264 boolean newMergedDeletedState = getMergedDeletedState(decision);
265
266 this.deletedMergeDecision = decision;
267 setChanged();
268 notifyObservers();
269 fireCompletelyResolved();
270
271 if (oldMergedDeletedState != newMergedDeletedState) {
272 support.firePropertyChange(DELETE_PRIMITIVE_PROP, oldMergedDeletedState, newMergedDeletedState);
273 }
274 }
275
276 /**
277 * replies true if my and their primitive have a conflict between
278 * their coordinate values
279 *
280 * @return true if my and their primitive have a conflict between
281 * their coordinate values; false otherwise
282 */
283 public boolean hasCoordConflict() {
284 if (myCoords == null && theirCoords != null) return true;
285 if (myCoords != null && theirCoords == null) return true;
286 if (myCoords == null && theirCoords == null) return false;
287 return !myCoords.equalsEpsilon(theirCoords);
288 }
289
290 /**
291 * replies true if my and their primitive have a conflict between
292 * their deleted states
293 *
294 * @return true if my and their primitive have a conflict between
295 * their deleted states
296 */
297 public boolean hasDeletedStateConflict() {
298 return myDeletedState != theirDeletedState;
299 }
300
301 /**
302 * replies true if all conflict in this model are resolved
303 *
304 * @return true if all conflict in this model are resolved; false otherwise
305 */
306 public boolean isResolvedCompletely() {
307 boolean ret = true;
308 if (hasCoordConflict()) {
309 ret = ret && ! coordMergeDecision.equals(UNDECIDED);
310 }
311 if (hasDeletedStateConflict()) {
312 ret = ret && ! deletedMergeDecision.equals(UNDECIDED);
313 }
314 return ret;
315 }
316
317 /**
318 * builds the command(s) to apply the conflict resolutions to my primitive
319 *
320 * @param my my primitive
321 * @param their their primitive
322 * @return the list of commands
323 */
324 public List<Command> buildResolveCommand(Conflict<? extends OsmPrimitive> conflict) {
325 List<Command> cmds = new ArrayList<Command>();
326 if (hasCoordConflict() && isDecidedCoord()) {
327 cmds.add(new CoordinateConflictResolveCommand(conflict, coordMergeDecision));
328 }
329 if (hasDeletedStateConflict() && isDecidedDeletedState()) {
330 cmds.add(new DeletedStateConflictResolveCommand(conflict, deletedMergeDecision));
331 }
332 return cmds;
333 }
334
335 public OsmPrimitive getMyPrimitive() {
336 return my;
337 }
338
339}
Note: See TracBrowser for help on using the repository browser.