Changeset 1690 in josm for trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java
- Timestamp:
- 23.06.2009 22:03:37 (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMergeModel.java
r1669 r1690 3 3 4 4 import static org.openstreetmap.josm.gui.conflict.MergeDecisionType.UNDECIDED; 5 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 6 7 import java.beans.PropertyChangeListener; 7 8 import java.beans.PropertyChangeSupport; 9 import java.io.IOException; 10 import java.net.HttpURLConnection; 8 11 import java.util.ArrayList; 12 import java.util.HashMap; 9 13 import java.util.List; 10 14 import java.util.Observable; 11 15 16 import javax.swing.JOptionPane; 17 import javax.swing.text.html.HTML; 18 19 import org.openstreetmap.josm.Main; 12 20 import org.openstreetmap.josm.command.Command; 13 21 import org.openstreetmap.josm.command.CoordinateConflictResolveCommand; 14 22 import org.openstreetmap.josm.command.DeletedStateConflictResolveCommand; 23 import org.openstreetmap.josm.command.PurgePrimitivesCommand; 24 import org.openstreetmap.josm.command.UndeletePrimitivesCommand; 15 25 import org.openstreetmap.josm.data.coor.LatLon; 26 import org.openstreetmap.josm.data.osm.DataSet; 16 27 import org.openstreetmap.josm.data.osm.Node; 17 28 import org.openstreetmap.josm.data.osm.OsmPrimitive; 29 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 30 import org.openstreetmap.josm.data.osm.Relation; 31 import org.openstreetmap.josm.data.osm.RelationMember; 32 import org.openstreetmap.josm.data.osm.Way; 33 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 18 34 import org.openstreetmap.josm.gui.conflict.MergeDecisionType; 35 import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger.KeepMyVisibleStateAction; 36 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 37 import org.openstreetmap.josm.io.OsmApi; 38 import org.openstreetmap.josm.io.OsmApiException; 39 import org.openstreetmap.josm.io.OsmServerObjectReader; 40 import org.openstreetmap.josm.io.OsmTransferException; 41 import org.xml.sax.SAXException; 19 42 20 43 /** 21 44 * This is the model for resolving conflicts in the properties of the 22 45 * {@see OsmPrimitive}s. In particular, it represents conflicts in the coordiates of {@see Node}s and 23 * the deleted state of {@see OsmPrimitive}s.46 * the deleted or visible state of {@see OsmPrimitive}s. 24 47 * 25 48 * This model is an {@see Observable}. It notifies registered {@see Observer}s whenever the … … 31 54 * @see Node#getCoor() 32 55 * @see OsmPrimitive#deleted 56 * @see OsmPrimitive#visible 33 57 * 34 58 */ … … 37 61 static public final String RESOLVED_COMPLETELY_PROP = PropertiesMergeModel.class.getName() + ".resolvedCompletely"; 38 62 63 private OsmPrimitive my; 64 39 65 private LatLon myCoords; 40 66 private LatLon theirCoords; … … 43 69 private boolean myDeletedState; 44 70 private boolean theirDeletedState; 71 private boolean myVisibleState; 72 private boolean theirVisibleState; 45 73 private MergeDecisionType deletedMergeDecision; 74 private MergeDecisionType visibleMergeDecision; 46 75 private final PropertyChangeSupport support; 47 76 private boolean resolvedCompletely; … … 91 120 92 121 /** 122 * replies true if there is a conflict in the visible state and if this conflict is 123 * resolved 124 * 125 * @return true if there is a conflict in the visible state and if this conflict is 126 * resolved; false, otherwise 127 */ 128 public boolean isDecidedVisibleState() { 129 return ! visibleMergeDecision.equals(UNDECIDED); 130 } 131 132 /** 93 133 * replies true if the current decision for the coordinate conflict is <code>decision</code> 94 134 * … … 111 151 112 152 /** 153 * replies true if the current decision for the visible state conflict is <code>decision</code> 154 * 155 * @return true if the current decision for the visible state conflict is <code>decision</code>; 156 * false, otherwise 157 */ 158 public boolean isVisibleStateDecision(MergeDecisionType decision) { 159 return visibleMergeDecision.equals(decision); 160 } 161 /** 113 162 * populates the model with the differences between my and their version 114 163 * … … 117 166 */ 118 167 public void populate(OsmPrimitive my, OsmPrimitive their) { 168 this.my = my; 119 169 if (my instanceof Node) { 120 170 myCoords = ((Node)my).getCoor(); … … 128 178 theirDeletedState = their.deleted; 129 179 180 myVisibleState = my.visible; 181 theirVisibleState = their.visible; 182 130 183 coordMergeDecision = UNDECIDED; 131 184 deletedMergeDecision = UNDECIDED; 185 visibleMergeDecision = UNDECIDED; 132 186 setChanged(); 133 187 notifyObservers(); … … 209 263 } 210 264 211 public void decideDeletedStateConflict(MergeDecisionType decision) { 265 266 /** 267 * replies my visible state, 268 * @return my visible state 269 */ 270 public Boolean getMyVisibleState() { 271 return myVisibleState; 272 } 273 274 /** 275 * replies their visible state, 276 * @return their visible state 277 */ 278 public Boolean getTheirVisibleState() { 279 return theirVisibleState; 280 } 281 282 /** 283 * replies the merged visible state; null, if the merge decision is 284 * {@see MergeDecisionType#UNDECIDED}. 285 * 286 * @return the merged visible state 287 */ 288 public Boolean getMergedVisibleState() { 289 switch(visibleMergeDecision) { 290 case KEEP_MINE: return myVisibleState; 291 case KEEP_THEIR: return theirVisibleState; 292 case UNDECIDED: return null; 293 } 294 // should not happen 295 return null; 296 } 297 298 /** 299 * decides the conflict between two deleted states 300 * @param decision the decision (must not be null) 301 * 302 * @throws IllegalArgumentException thrown, if decision is null 303 */ 304 public void decideDeletedStateConflict(MergeDecisionType decision) throws IllegalArgumentException{ 305 if (decision == null) 306 throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision")); 212 307 this.deletedMergeDecision = decision; 308 setChanged(); 309 notifyObservers(); 310 fireCompletelyResolved(); 311 } 312 313 /** 314 * decides the conflict between two visible states 315 * @param decision the decision (must not be null) 316 * 317 * @throws IllegalArgumentException thrown, if decision is null 318 */ 319 public void decideVisibleStateConflict(MergeDecisionType decision) throws IllegalArgumentException { 320 if (decision == null) 321 throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "decision")); 322 this.visibleMergeDecision = decision; 213 323 setChanged(); 214 324 notifyObservers(); … … 242 352 243 353 /** 354 * replies true if my and their primitive have a conflict between 355 * their visible states 356 * 357 * @return true if my and their primitive have a conflict between 358 * their visible states 359 */ 360 public boolean hasVisibleStateConflict() { 361 return myVisibleState != theirVisibleState; 362 } 363 364 /** 244 365 * replies true if all conflict in this model are resolved 245 366 * … … 254 375 ret = ret && ! deletedMergeDecision.equals(UNDECIDED); 255 376 } 377 if (hasVisibleStateConflict()) { 378 ret = ret && ! visibleMergeDecision.equals(UNDECIDED); 379 } 256 380 return ret; 257 381 } … … 264 388 * @return the list of commands 265 389 */ 266 public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) {390 public List<Command> buildResolveCommand(OsmPrimitive my, OsmPrimitive their) throws OperationCancelledException{ 267 391 ArrayList<Command> cmds = new ArrayList<Command>(); 392 if (hasVisibleStateConflict() && isDecidedVisibleState()) { 393 if (isVisibleStateDecision(MergeDecisionType.KEEP_MINE)) { 394 try { 395 UndeletePrimitivesCommand cmd = createUndeletePrimitiveCommand(my); 396 if (cmd == null) 397 throw new OperationCancelledException(); 398 cmds.add(cmd); 399 } catch(OsmTransferException e) { 400 handleExceptionWhileBuildingCommand(e); 401 throw new OperationCancelledException(e); 402 } 403 } else if (isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)) { 404 cmds.add(new PurgePrimitivesCommand(my)); 405 } 406 } 268 407 if (hasCoordConflict() && isDecidedCoord()) { 269 408 cmds.add(new CoordinateConflictResolveCommand((Node)my, (Node)their, coordMergeDecision)); … … 274 413 return cmds; 275 414 } 415 416 public OsmPrimitive getMyPrimitive() { 417 return my; 418 } 419 420 /** 421 * 422 * @param id 423 */ 424 protected void handleExceptionWhileBuildingCommand(Exception e) { 425 e.printStackTrace(); 426 String msg = e.getMessage() != null ? e.getMessage() : e.toString(); 427 msg = msg.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); 428 JOptionPane.showMessageDialog( 429 Main.parent, 430 tr("<html>An error occurred while communicating with the server<br>" 431 + "Details: {0}</html>", 432 msg 433 ), 434 tr("Communication with server failed"), 435 JOptionPane.ERROR_MESSAGE 436 ); 437 } 438 439 /** 440 * User has decided to keep his local version of a primitive which had been deleted 441 * on the server 442 * 443 * @param id the primitive id 444 */ 445 protected UndeletePrimitivesCommand createUndeletePrimitiveCommand(OsmPrimitive my) throws OsmTransferException { 446 if (my instanceof Node) 447 return createUndeleteNodeCommand((Node)my); 448 else if (my instanceof Way) 449 return createUndeleteWayCommand((Way)my); 450 else if (my instanceof Relation) 451 return createUndeleteRelationCommand((Relation)my); 452 return null; 453 } 454 /** 455 * Undelete a node which is already deleted on the server. The API 456 * doesn't offer a call for "undeleting" a node. We therefore create 457 * a clone of the node which we flag as new. On the next upload the 458 * server will assign the node a new id. 459 * 460 * @param node the node to undelete 461 */ 462 protected UndeletePrimitivesCommand createUndeleteNodeCommand(Node node) { 463 return new UndeletePrimitivesCommand(node); 464 } 465 466 /** 467 * displays a confirmation message. The user has to confirm that additional dependent 468 * nodes should be undeleted too. 469 * 470 * @param way the way 471 * @param dependent a list of dependent nodes which have to be undelete too 472 * @return true, if the user confirms; false, otherwise 473 */ 474 protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) { 475 String [] options = { 476 tr("Yes, undelete them too"), 477 tr("No, cancel operation") 478 }; 479 int ret = JOptionPane.showOptionDialog( 480 Main.parent, 481 tr("<html>There are {0} additional nodes used by way {1}<br>" 482 + "which are deleted on the server.<br>" 483 + "<br>" 484 + "Do you want to undelete these nodes too?</html>", 485 Long.toString(dependent.size()), Long.toString(way.id)), 486 tr("Undelete additional nodes?"), 487 JOptionPane.YES_NO_OPTION, 488 JOptionPane.QUESTION_MESSAGE, 489 null, 490 options, 491 options[0] 492 ); 493 494 switch(ret) { 495 case JOptionPane.CLOSED_OPTION: return false; 496 case JOptionPane.YES_OPTION: return true; 497 case JOptionPane.NO_OPTION: return false; 498 } 499 return false; 500 501 } 502 503 protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) { 504 String [] options = { 505 tr("Yes, undelete them too"), 506 tr("No, cancel operation") 507 }; 508 int ret = JOptionPane.showOptionDialog( 509 Main.parent, 510 tr("<html>There are {0} additional primitives referred to by relation {1}<br>" 511 + "which are deleted on the server.<br>" 512 + "<br>" 513 + "Do you want to undelete them too?</html>", 514 Long.toString(dependent.size()), Long.toString(r.id)), 515 tr("Undelete dependent primitives?"), 516 JOptionPane.YES_NO_OPTION, 517 JOptionPane.QUESTION_MESSAGE, 518 null, 519 options, 520 options[0] 521 ); 522 523 switch(ret) { 524 case JOptionPane.CLOSED_OPTION: return false; 525 case JOptionPane.YES_OPTION: return true; 526 case JOptionPane.NO_OPTION: return false; 527 } 528 return false; 529 530 } 531 532 /** 533 * Creates the undelete command for a way which is already deleted on the server. 534 * 535 * This method also checks whether there are additional nodes referred to by 536 * this way which are deleted on the server too. 537 * 538 * @param way the way to undelete 539 * @return the undelete command 540 * @see #createUndeleteNodeCommand(Node) 541 */ 542 protected UndeletePrimitivesCommand createUndeleteWayCommand(final Way way) throws OsmTransferException { 543 544 HashMap<Long,OsmPrimitive> candidates = new HashMap<Long,OsmPrimitive>(); 545 for (Node n : way.nodes) { 546 if (n.id > 0 && ! candidates.values().contains(n)) { 547 candidates.put(n.id, n); 548 } 549 } 550 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 551 reader.append(candidates.values()); 552 DataSet ds = reader.parseOsm(); 553 554 ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>(); 555 for (OsmPrimitive their : ds.allPrimitives()) { 556 if (candidates.keySet().contains(their.id) && ! their.visible) { 557 toDelete.add(candidates.get(their.id)); 558 } 559 } 560 if (!toDelete.isEmpty()) { 561 if (! confirmUndeleteDependentPrimitives(way, toDelete)) 562 // FIXME: throw exception ? 563 return null; 564 } 565 toDelete.add(way); 566 return new UndeletePrimitivesCommand(toDelete); 567 } 568 569 /** 570 * Creates an undelete command for a relation which is already deleted on the server. 571 * 572 * This method checks whether there are additional primitives referred to by 573 * this relation which are already deleted on the server. 574 * 575 * @param r the relation 576 * @return the undelete command 577 * @see #createUndeleteNodeCommand(Node) 578 */ 579 protected UndeletePrimitivesCommand createUndeleteRelationCommand(final Relation r) throws OsmTransferException { 580 581 HashMap<Long,OsmPrimitive> candidates = new HashMap<Long, OsmPrimitive>(); 582 for (RelationMember m : r.members) { 583 if (m.member.id > 0 && !candidates.values().contains(m.member)) { 584 candidates.put(m.member.id,m.member); 585 } 586 } 587 588 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 589 reader.append(candidates.values()); 590 DataSet ds = reader.parseOsm(); 591 592 ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>(); 593 for (OsmPrimitive their : ds.allPrimitives()) { 594 if (candidates.keySet().contains(their.id) && ! their.visible) { 595 toDelete.add(candidates.get(their.id)); 596 } 597 } 598 if (!toDelete.isEmpty()) { 599 if (! confirmUndeleteDependentPrimitives(r, toDelete)) 600 // FIXME: throw exception ? 601 return null; 602 } 603 toDelete.add(r); 604 return new UndeletePrimitivesCommand(toDelete); 605 } 606 276 607 }
Note: See TracChangeset
for help on using the changeset viewer.
