source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java@ 10378

Last change on this file since 10378 was 10217, checked in by Don-vip, 8 years ago

findbugs - SF_SWITCH_NO_DEFAULT + various sonar fixes

  • Property svn:eol-style set to native
File size: 15.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.tags;
3
4import java.beans.PropertyChangeListener;
5import java.beans.PropertyChangeSupport;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.Iterator;
11import java.util.LinkedHashMap;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Map;
15import java.util.Set;
16import java.util.TreeSet;
17
18import javax.swing.table.DefaultTableModel;
19
20import org.openstreetmap.josm.command.ChangeCommand;
21import org.openstreetmap.josm.command.Command;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.RelationMember;
25import org.openstreetmap.josm.data.osm.RelationToChildReference;
26import org.openstreetmap.josm.gui.util.GuiHelper;
27import org.openstreetmap.josm.tools.Predicate;
28import org.openstreetmap.josm.tools.Utils;
29
30/**
31 * This model manages a list of conflicting relation members.
32 *
33 * It can be used as {@link javax.swing.table.TableModel}.
34 */
35public class RelationMemberConflictResolverModel extends DefaultTableModel {
36 /** the property name for the number conflicts managed by this model */
37 public static final String NUM_CONFLICTS_PROP = RelationMemberConflictResolverModel.class.getName() + ".numConflicts";
38
39 /** the list of conflict decisions */
40 protected final transient List<RelationMemberConflictDecision> decisions;
41 /** the collection of relations for which we manage conflicts */
42 protected transient Collection<Relation> relations;
43 /** the collection of primitives for which we manage conflicts */
44 protected transient Collection<? extends OsmPrimitive> primitives;
45 /** the number of conflicts */
46 private int numConflicts;
47 private final PropertyChangeSupport support;
48
49 /**
50 * Replies true if each {@link MultiValueResolutionDecision} is decided.
51 *
52 * @return true if each {@link MultiValueResolutionDecision} is decided; false otherwise
53 */
54 public boolean isResolvedCompletely() {
55 return numConflicts == 0;
56 }
57
58 /**
59 * Replies the current number of conflicts
60 *
61 * @return the current number of conflicts
62 */
63 public int getNumConflicts() {
64 return numConflicts;
65 }
66
67 /**
68 * Updates the current number of conflicts from list of decisions and emits
69 * a property change event if necessary.
70 *
71 */
72 protected void updateNumConflicts() {
73 int count = 0;
74 for (RelationMemberConflictDecision decision: decisions) {
75 if (!decision.isDecided()) {
76 count++;
77 }
78 }
79 int oldValue = numConflicts;
80 numConflicts = count;
81 if (numConflicts != oldValue) {
82 support.firePropertyChange(getProperty(), oldValue, numConflicts);
83 }
84 }
85
86 protected String getProperty() {
87 return NUM_CONFLICTS_PROP;
88 }
89
90 public void addPropertyChangeListener(PropertyChangeListener l) {
91 support.addPropertyChangeListener(l);
92 }
93
94 public void removePropertyChangeListener(PropertyChangeListener l) {
95 support.removePropertyChangeListener(l);
96 }
97
98 public RelationMemberConflictResolverModel() {
99 decisions = new ArrayList<>();
100 support = new PropertyChangeSupport(this);
101 }
102
103 @Override
104 public int getRowCount() {
105 return getNumDecisions();
106 }
107
108 @Override
109 public Object getValueAt(int row, int column) {
110 if (decisions == null) return null;
111
112 RelationMemberConflictDecision d = decisions.get(row);
113 switch(column) {
114 case 0: /* relation */ return d.getRelation();
115 case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
116 case 2: /* role */ return d.getRole();
117 case 3: /* original */ return d.getOriginalPrimitive();
118 case 4: /* decision */ return d.getDecision();
119 }
120 return null;
121 }
122
123 @Override
124 public void setValueAt(Object value, int row, int column) {
125 RelationMemberConflictDecision d = decisions.get(row);
126 switch(column) {
127 case 2: /* role */
128 d.setRole((String) value);
129 break;
130 case 4: /* decision */
131 d.decide((RelationMemberConflictDecisionType) value);
132 refresh();
133 break;
134 default: // Do nothing
135 }
136 fireTableDataChanged();
137 }
138
139 /**
140 * Populates the model with the members of the relation <code>relation</code>
141 * referring to <code>primitive</code>.
142 *
143 * @param relation the parent relation
144 * @param primitive the child primitive
145 */
146 protected void populate(Relation relation, OsmPrimitive primitive) {
147 for (int i = 0; i < relation.getMembersCount(); i++) {
148 if (relation.getMember(i).refersTo(primitive)) {
149 decisions.add(new RelationMemberConflictDecision(relation, i));
150 }
151 }
152 }
153
154 /**
155 * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
156 * and referring to one of the primitives in <code>memberPrimitives</code>.
157 *
158 * @param relations the parent relations. Empty list assumed if null.
159 * @param memberPrimitives the child primitives. Empty list assumed if null.
160 */
161 public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives) {
162 decisions.clear();
163 relations = relations == null ? Collections.<Relation>emptyList() : relations;
164 memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives;
165 for (Relation r : relations) {
166 for (OsmPrimitive p: memberPrimitives) {
167 populate(r, p);
168 }
169 }
170 this.relations = relations;
171 this.primitives = memberPrimitives;
172 refresh();
173 }
174
175 /**
176 * Populates the model with the relation members represented as a collection of
177 * {@link RelationToChildReference}s.
178 *
179 * @param references the references. Empty list assumed if null.
180 */
181 public void populate(Collection<RelationToChildReference> references) {
182 references = references == null ? new LinkedList<RelationToChildReference>() : references;
183 decisions.clear();
184 this.relations = new HashSet<>(references.size());
185 final Collection<OsmPrimitive> primitives = new HashSet<>();
186 for (RelationToChildReference reference: references) {
187 decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition()));
188 relations.add(reference.getParent());
189 primitives.add(reference.getChild());
190 }
191 this.primitives = primitives;
192 refresh();
193 }
194
195 /**
196 * Prepare the default decisions for the current model.
197 *
198 * Keep/delete decisions are made if every member has the same role and the members are in consecutive order within the relation.
199 * For multiple occurrences those conditions are tested stepwise for each occurrence.
200 */
201 public void prepareDefaultRelationDecisions() {
202
203 if (Utils.forAll(primitives, OsmPrimitive.nodePredicate)) {
204 final Collection<OsmPrimitive> primitivesInDecisions = new HashSet<>();
205 for (final RelationMemberConflictDecision i : decisions) {
206 primitivesInDecisions.add(i.getOriginalPrimitive());
207 }
208 if (primitivesInDecisions.size() == 1) {
209 for (final RelationMemberConflictDecision i : decisions) {
210 i.decide(RelationMemberConflictDecisionType.KEEP);
211 }
212 refresh();
213 return;
214 }
215 }
216
217 for (final Relation relation : relations) {
218 final Map<OsmPrimitive, List<RelationMemberConflictDecision>> decisionsByPrimitive = new LinkedHashMap<>(primitives.size(), 1);
219 for (final RelationMemberConflictDecision decision : decisions) {
220 if (decision.getRelation() == relation) {
221 final OsmPrimitive primitive = decision.getOriginalPrimitive();
222 if (!decisionsByPrimitive.containsKey(primitive)) {
223 decisionsByPrimitive.put(primitive, new ArrayList<RelationMemberConflictDecision>());
224 }
225 decisionsByPrimitive.get(primitive).add(decision);
226 }
227 }
228
229 //noinspection StatementWithEmptyBody
230 if (!decisionsByPrimitive.keySet().containsAll(primitives)) {
231 // some primitives are not part of the relation, leave undecided
232 } else {
233 final Collection<Iterator<RelationMemberConflictDecision>> iterators = new ArrayList<>(primitives.size());
234 for (final Collection<RelationMemberConflictDecision> i : decisionsByPrimitive.values()) {
235 iterators.add(i.iterator());
236 }
237 while (Utils.forAll(iterators, new Predicate<Iterator<RelationMemberConflictDecision>>() {
238 @Override
239 public boolean evaluate(Iterator<RelationMemberConflictDecision> it) {
240 return it.hasNext();
241 }
242 })) {
243 final List<RelationMemberConflictDecision> decisions = new ArrayList<>();
244 final Collection<String> roles = new HashSet<>();
245 final Collection<Integer> indices = new TreeSet<>();
246 for (Iterator<RelationMemberConflictDecision> it : iterators) {
247 final RelationMemberConflictDecision decision = it.next();
248 decisions.add(decision);
249 roles.add(decision.getRole());
250 indices.add(decision.getPos());
251 }
252 if (roles.size() != 1) {
253 // roles to not patch, leave undecided
254 continue;
255 } else if (!isCollectionOfConsecutiveNumbers(indices)) {
256 // not consecutive members in relation, leave undecided
257 continue;
258 }
259 decisions.get(0).decide(RelationMemberConflictDecisionType.KEEP);
260 for (RelationMemberConflictDecision decision : decisions.subList(1, decisions.size())) {
261 decision.decide(RelationMemberConflictDecisionType.REMOVE);
262 }
263 }
264 }
265 }
266
267 refresh();
268 }
269
270 static boolean isCollectionOfConsecutiveNumbers(Collection<Integer> numbers) {
271 if (numbers.isEmpty()) {
272 return true;
273 }
274 final Iterator<Integer> it = numbers.iterator();
275 Integer previousValue = it.next();
276 while (it.hasNext()) {
277 final Integer i = it.next();
278 if (previousValue + 1 != i) {
279 return false;
280 }
281 previousValue = i;
282 }
283 return true;
284 }
285
286 /**
287 * Replies the decision at position <code>row</code>
288 *
289 * @param row position
290 * @return the decision at position <code>row</code>
291 */
292 public RelationMemberConflictDecision getDecision(int row) {
293 return decisions.get(row);
294 }
295
296 /**
297 * Replies the number of decisions managed by this model
298 *
299 * @return the number of decisions managed by this model
300 */
301 public int getNumDecisions() {
302 return decisions == null /* accessed via super constructor */ ? 0 : decisions.size();
303 }
304
305 /**
306 * Refreshes the model state. Invoke this method to trigger necessary change
307 * events after an update of the model data.
308 *
309 */
310 public void refresh() {
311 updateNumConflicts();
312 GuiHelper.runInEDTAndWait(new Runnable() {
313 @Override public void run() {
314 fireTableDataChanged();
315 }
316 });
317 }
318
319 /**
320 * Apply a role to all member managed by this model.
321 *
322 * @param role the role. Empty string assumed if null.
323 */
324 public void applyRole(String role) {
325 role = role == null ? "" : role;
326 for (RelationMemberConflictDecision decision : decisions) {
327 decision.setRole(role);
328 }
329 refresh();
330 }
331
332 protected RelationMemberConflictDecision getDecision(Relation relation, int pos) {
333 for (RelationMemberConflictDecision decision: decisions) {
334 if (decision.matches(relation, pos)) return decision;
335 }
336 return null;
337 }
338
339 protected Command buildResolveCommand(Relation relation, OsmPrimitive newPrimitive) {
340 final Relation modifiedRelation = new Relation(relation);
341 modifiedRelation.setMembers(null);
342 boolean isChanged = false;
343 for (int i = 0; i < relation.getMembersCount(); i++) {
344 final RelationMember member = relation.getMember(i);
345 RelationMemberConflictDecision decision = getDecision(relation, i);
346 if (decision == null) {
347 modifiedRelation.addMember(member);
348 } else {
349 switch(decision.getDecision()) {
350 case KEEP:
351 final RelationMember newMember = new RelationMember(decision.getRole(), newPrimitive);
352 modifiedRelation.addMember(newMember);
353 isChanged |= !member.equals(newMember);
354 break;
355 case REMOVE:
356 isChanged = true;
357 // do nothing
358 break;
359 case UNDECIDED:
360 // FIXME: this is an error
361 break;
362 }
363 }
364 }
365 if (isChanged)
366 return new ChangeCommand(relation, modifiedRelation);
367 return null;
368 }
369
370 /**
371 * Builds a collection of commands executing the decisions made in this model.
372 *
373 * @param newPrimitive the primitive which members shall refer to
374 * @return a list of commands
375 */
376 public List<Command> buildResolutionCommands(OsmPrimitive newPrimitive) {
377 List<Command> command = new LinkedList<>();
378 for (Relation relation : relations) {
379 Command cmd = buildResolveCommand(relation, newPrimitive);
380 if (cmd != null) {
381 command.add(cmd);
382 }
383 }
384 return command;
385 }
386
387 protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) {
388 for (int i = 0; i < relation.getMembersCount(); i++) {
389 RelationMemberConflictDecision decision = getDecision(relation, i);
390 if (decision == null) {
391 continue;
392 }
393 switch(decision.getDecision()) {
394 case REMOVE: return true;
395 case KEEP:
396 if (!relation.getMember(i).getRole().equals(decision.getRole()))
397 return true;
398 if (relation.getMember(i).getMember() != newPrimitive)
399 return true;
400 case UNDECIDED:
401 // FIXME: handle error
402 }
403 }
404 return false;
405 }
406
407 /**
408 * Replies the set of relations which have to be modified according
409 * to the decisions managed by this model.
410 *
411 * @param newPrimitive the primitive which members shall refer to
412 *
413 * @return the set of relations which have to be modified according
414 * to the decisions managed by this model
415 */
416 public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
417 Set<Relation> ret = new HashSet<>();
418 for (Relation relation: relations) {
419 if (isChanged(relation, newPrimitive)) {
420 ret.add(relation);
421 }
422 }
423 return ret;
424 }
425}
Note: See TracBrowser for help on using the repository browser.