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

Last change on this file since 11462 was 11462, checked in by Don-vip, 7 years ago

sonar - squid:S1871 - Two branches in the same conditional structure should not have exactly the same implementation

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