001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.conflict.tags; 003 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyChangeSupport; 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.HashSet; 009import java.util.LinkedList; 010import java.util.List; 011import java.util.Set; 012 013import javax.swing.table.DefaultTableModel; 014 015import org.openstreetmap.josm.command.ChangeCommand; 016import org.openstreetmap.josm.command.Command; 017import org.openstreetmap.josm.data.osm.OsmPrimitive; 018import org.openstreetmap.josm.data.osm.Relation; 019import org.openstreetmap.josm.data.osm.RelationMember; 020import org.openstreetmap.josm.data.osm.RelationToChildReference; 021import org.openstreetmap.josm.gui.util.GuiHelper; 022 023/** 024 * This model manages a list of conflicting relation members. 025 * 026 * It can be used as {@link javax.swing.table.TableModel}. 027 */ 028public class RelationMemberConflictResolverModel extends DefaultTableModel { 029 /** the property name for the number conflicts managed by this model */ 030 static public final String NUM_CONFLICTS_PROP = RelationMemberConflictResolverModel.class.getName() + ".numConflicts"; 031 032 /** the list of conflict decisions */ 033 private List<RelationMemberConflictDecision> decisions; 034 /** the collection of relations for which we manage conflicts */ 035 private Collection<Relation> relations; 036 /** the number of conflicts */ 037 private int numConflicts; 038 private PropertyChangeSupport support; 039 040 /** 041 * Replies the current number of conflicts 042 * 043 * @return the current number of conflicts 044 */ 045 public int getNumConflicts() { 046 return numConflicts; 047 } 048 049 /** 050 * Updates the current number of conflicts from list of decisions and emits 051 * a property change event if necessary. 052 * 053 */ 054 protected void updateNumConflicts() { 055 int count = 0; 056 for (RelationMemberConflictDecision decision: decisions) { 057 if (!decision.isDecided()) { 058 count++; 059 } 060 } 061 int oldValue = numConflicts; 062 numConflicts = count; 063 if (numConflicts != oldValue) { 064 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, numConflicts); 065 } 066 } 067 068 public void addPropertyChangeListener(PropertyChangeListener l) { 069 support.addPropertyChangeListener(l); 070 } 071 072 public void removePropertyChangeListener(PropertyChangeListener l) { 073 support.removePropertyChangeListener(l); 074 } 075 076 public RelationMemberConflictResolverModel() { 077 decisions = new ArrayList<RelationMemberConflictDecision>(); 078 support = new PropertyChangeSupport(this); 079 } 080 081 @Override 082 public int getRowCount() { 083 return getNumDecisions(); 084 } 085 086 @Override 087 public Object getValueAt(int row, int column) { 088 if (decisions == null) return null; 089 090 RelationMemberConflictDecision d = decisions.get(row); 091 switch(column) { 092 case 0: /* relation */ return d.getRelation(); 093 case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1 094 case 2: /* role */ return d.getRole(); 095 case 3: /* original */ return d.getOriginalPrimitive(); 096 case 4: /* decision */ return d.getDecision(); 097 } 098 return null; 099 } 100 101 @Override 102 public void setValueAt(Object value, int row, int column) { 103 RelationMemberConflictDecision d = decisions.get(row); 104 switch(column) { 105 case 2: /* role */ 106 d.setRole((String)value); 107 break; 108 case 4: /* decision */ 109 d.decide((RelationMemberConflictDecisionType)value); 110 refresh(); 111 break; 112 } 113 fireTableDataChanged(); 114 } 115 116 /** 117 * Populates the model with the members of the relation <code>relation</code> 118 * referring to <code>primitive</code>. 119 * 120 * @param relation the parent relation 121 * @param primitive the child primitive 122 */ 123 protected void populate(Relation relation, OsmPrimitive primitive) { 124 for (int i =0; i<relation.getMembersCount();i++) { 125 if (relation.getMember(i).refersTo(primitive)) { 126 decisions.add(new RelationMemberConflictDecision(relation, i)); 127 } 128 } 129 } 130 131 /** 132 * Populates the model with the relation members belonging to one of the relations in <code>relations</code> 133 * and referring to one of the primitives in <code>memberPrimitives</code>. 134 * 135 * @param relations the parent relations. Empty list assumed if null. 136 * @param memberPrimitives the child primitives. Empty list assumed if null. 137 */ 138 public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives) { 139 decisions.clear(); 140 relations = relations == null ? new LinkedList<Relation>() : relations; 141 memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives; 142 for (Relation r : relations) { 143 for (OsmPrimitive p: memberPrimitives) { 144 populate(r,p); 145 } 146 } 147 this.relations = relations; 148 refresh(); 149 } 150 151 /** 152 * Populates the model with the relation members represented as a collection of 153 * {@link RelationToChildReference}s. 154 * 155 * @param references the references. Empty list assumed if null. 156 */ 157 public void populate(Collection<RelationToChildReference> references) { 158 references = references == null ? new LinkedList<RelationToChildReference>() : references; 159 decisions.clear(); 160 this.relations = new HashSet<Relation>(references.size()); 161 for (RelationToChildReference reference: references) { 162 decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition())); 163 relations.add(reference.getParent()); 164 } 165 refresh(); 166 } 167 168 /** 169 * Replies the decision at position <code>row</code> 170 * 171 * @param row 172 * @return the decision at position <code>row</code> 173 */ 174 public RelationMemberConflictDecision getDecision(int row) { 175 return decisions.get(row); 176 } 177 178 /** 179 * Replies the number of decisions managed by this model 180 * 181 * @return the number of decisions managed by this model 182 */ 183 public int getNumDecisions() { 184 return decisions == null ? 0 : decisions.size(); 185 } 186 187 /** 188 * Refreshes the model state. Invoke this method to trigger necessary change 189 * events after an update of the model data. 190 * 191 */ 192 public void refresh() { 193 updateNumConflicts(); 194 GuiHelper.runInEDTAndWait(new Runnable() { 195 @Override public void run() { 196 fireTableDataChanged(); 197 } 198 }); 199 } 200 201 /** 202 * Apply a role to all member managed by this model. 203 * 204 * @param role the role. Empty string assumed if null. 205 */ 206 public void applyRole(String role) { 207 role = role == null ? "" : role; 208 for (RelationMemberConflictDecision decision : decisions) { 209 decision.setRole(role); 210 } 211 refresh(); 212 } 213 214 protected RelationMemberConflictDecision getDecision(Relation relation, int pos) { 215 for(RelationMemberConflictDecision decision: decisions) { 216 if (decision.matches(relation, pos)) return decision; 217 } 218 return null; 219 } 220 221 protected Command buildResolveCommand(Relation relation, OsmPrimitive newPrimitive) { 222 Relation modifiedRelation = new Relation(relation); 223 modifiedRelation.setMembers(null); 224 boolean isChanged = false; 225 for (int i=0; i < relation.getMembersCount(); i++) { 226 RelationMember rm = relation.getMember(i); 227 RelationMember rmNew; 228 RelationMemberConflictDecision decision = getDecision(relation, i); 229 if (decision == null) { 230 modifiedRelation.addMember(rm); 231 } else { 232 switch(decision.getDecision()) { 233 case KEEP: 234 rmNew = new RelationMember(decision.getRole(),newPrimitive); 235 modifiedRelation.addMember(rmNew); 236 isChanged |= ! rm.equals(rmNew); 237 break; 238 case REMOVE: 239 isChanged = true; 240 // do nothing 241 break; 242 case UNDECIDED: 243 // FIXME: this is an error 244 break; 245 } 246 } 247 } 248 if (isChanged) 249 return new ChangeCommand(relation, modifiedRelation); 250 return null; 251 } 252 253 /** 254 * Builds a collection of commands executing the decisions made in this model. 255 * 256 * @param newPrimitive the primitive which members shall refer to 257 * @return a list of commands 258 */ 259 public List<Command> buildResolutionCommands(OsmPrimitive newPrimitive) { 260 List<Command> command = new LinkedList<Command>(); 261 for (Relation relation : relations) { 262 Command cmd = buildResolveCommand(relation, newPrimitive); 263 if (cmd != null) { 264 command.add(cmd); 265 } 266 } 267 return command; 268 } 269 270 protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) { 271 for (int i=0; i < relation.getMembersCount(); i++) { 272 RelationMemberConflictDecision decision = getDecision(relation, i); 273 if (decision == null) { 274 continue; 275 } 276 switch(decision.getDecision()) { 277 case REMOVE: return true; 278 case KEEP: 279 if (!relation.getMember(i).getRole().equals(decision.getRole())) 280 return true; 281 if (relation.getMember(i).getMember() != newPrimitive) 282 return true; 283 case UNDECIDED: 284 // FIXME: handle error 285 } 286 } 287 return false; 288 } 289 290 /** 291 * Replies the set of relations which have to be modified according 292 * to the decisions managed by this model. 293 * 294 * @param newPrimitive the primitive which members shall refer to 295 * 296 * @return the set of relations which have to be modified according 297 * to the decisions managed by this model 298 */ 299 public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) { 300 HashSet<Relation> ret = new HashSet<Relation>(); 301 for (Relation relation: relations) { 302 if (isChanged(relation, newPrimitive)) { 303 ret.add(relation); 304 } 305 } 306 return ret; 307 } 308}