Changeset 1690 in josm for trunk/src/org/openstreetmap/josm
- Timestamp:
- 2009-06-23T22:03:37+02:00 (16 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 2 added
- 21 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
r1670 r1690 6 6 import java.awt.event.ActionEvent; 7 7 import java.awt.event.KeyEvent; 8 import java.io.IOException;9 import java.net.HttpURLConnection;10 import java.util.ArrayList;11 8 import java.util.Collection; 9 import java.util.HashSet; 10 import java.util.Set; 12 11 13 12 import javax.swing.JOptionPane; … … 15 14 import org.openstreetmap.josm.Main; 16 15 import org.openstreetmap.josm.command.PurgePrimitivesCommand; 17 import org.openstreetmap.josm.command.UndeletePrimitivesCommand;18 16 import org.openstreetmap.josm.data.osm.DataSet; 19 import org.openstreetmap.josm.data.osm.Node;20 17 import org.openstreetmap.josm.data.osm.OsmPrimitive; 21 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 22 import org.openstreetmap.josm.data.osm.Relation; 23 import org.openstreetmap.josm.data.osm.RelationMember; 24 import org.openstreetmap.josm.data.osm.Way; 25 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 26 import org.openstreetmap.josm.io.OsmApi; 27 import org.openstreetmap.josm.io.OsmApiException; 28 import org.openstreetmap.josm.io.OsmServerObjectReader; 29 import org.openstreetmap.josm.io.OsmTransferException; 18 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 30 19 import org.openstreetmap.josm.tools.Shortcut; 31 import org.xml.sax.SAXException;32 20 33 21 /** … … 41 29 42 30 /** 43 * Undelete a node which is already deleted on the server. The API44 * doesn't offer a call for "undeleting" a node. We therefore create45 * a clone of the node which we flag as new. On the next upload the46 * server will assign the node a new id.47 *48 * @param node the node to undelete49 */50 protected void undeleteNode(Node node) {51 UndeletePrimitivesCommand cmd = new UndeletePrimitivesCommand(node);52 Main.main.undoRedo.add(cmd);53 }54 55 /**56 * Undelete a way which is already deleted on the server.57 *58 * This method also checks whether there are additional nodes referred to by59 * this way which are deleted on the server too.60 *61 * @param way the way to undelete62 * @see #undeleteNode(Node)63 */64 protected void undeleteWay(final Way way) {65 class NodeGoneChecker extends PleaseWaitRunnable {66 67 UndeletePrimitivesCommand cmd = null;68 69 public NodeGoneChecker() {70 super(tr("Undeleting Way..."), false);71 }72 73 @Override74 protected void cancel() {75 OsmApi.getOsmApi().cancel();76 }77 78 @Override79 protected void finish() {80 if (cmd != null) {81 Main.main.undoRedo.add(cmd);82 }83 }84 85 /**86 * replies the subset of the node list which already87 * have an assigned id88 *89 * @param way the way90 * @return the node list91 */92 protected ArrayList<Node> getCandidateNodes(Way way) {93 ArrayList<Node> candidates = new ArrayList<Node>();94 for (Node n : way.nodes) {95 if (n.id > 0 && ! candidates.contains(n)) {96 candidates.add(n);97 }98 }99 return candidates;100 }101 102 /**103 * checks whether a specific node is deleted on the server104 *105 * @param n the node106 * @return true, if the node is deleted; false, otherwise107 * @throws OsmTransferException thrown, if an error occurs while communicating with the API108 */109 protected boolean isGone(Node n) throws OsmTransferException {110 OsmServerObjectReader reader = new OsmServerObjectReader(n.id, OsmPrimitiveType.from(n), true);111 try {112 reader.parseOsm();113 } catch(OsmApiException e) {114 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE)115 return true;116 throw e;117 } catch(OsmTransferException e) {118 throw e;119 }120 return false;121 }122 123 /**124 * displays a confirmation message. The user has to confirm that additional dependent125 * nodes should be undeleted too.126 *127 * @param way the way128 * @param dependent a list of dependent nodes which have to be undelete too129 * @return true, if the user confirms; false, otherwise130 */131 protected boolean confirmUndeleteDependentPrimitives(Way way, ArrayList<OsmPrimitive> dependent) {132 String [] options = {133 tr("Yes, undelete them too"),134 tr("No, cancel operation")135 };136 int ret = JOptionPane.showOptionDialog(137 Main.parent,138 tr("<html>There are {0} additional nodes used by way {1}<br>"139 + "which are deleted on the server.<br>"140 + "<br>"141 + "Do you want to undelete these nodes too?</html>",142 Long.toString(dependent.size()), Long.toString(way.id)),143 tr("Undelete additional nodes?"),144 JOptionPane.YES_NO_OPTION,145 JOptionPane.QUESTION_MESSAGE,146 null,147 options,148 options[0]149 );150 151 switch(ret) {152 case JOptionPane.CLOSED_OPTION: return false;153 case JOptionPane.YES_OPTION: return true;154 case JOptionPane.NO_OPTION: return false;155 }156 return false;157 158 }159 160 @Override161 protected void realRun() throws SAXException, IOException, OsmTransferException {162 ArrayList<Node> candidate = getCandidateNodes(way);163 ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();164 Main.pleaseWaitDlg.progress.setMinimum(0);165 Main.pleaseWaitDlg.progress.setMaximum(candidate.size());166 167 for (int i=0; i<candidate.size();i++) {168 Node n = candidate.get(i);169 Main.pleaseWaitDlg.progress.setValue(i);170 Main.pleaseWaitDlg.currentAction.setText(tr("Checking whether node {0} is gone ...", n.id));171 if (isGone(n)) {172 toDelete.add(n);173 }174 }175 if (toDelete.size() > 0) {176 if (!confirmUndeleteDependentPrimitives(way, toDelete))177 return;178 }179 180 toDelete.add(way);181 cmd = new UndeletePrimitivesCommand(toDelete);182 }183 }184 185 Main.worker.submit(new NodeGoneChecker());186 }187 188 /**189 * Undelete a relation which is already deleted on the server.190 *191 * This method checks whether there are additional primitives referred to by192 * this relation which are already deleted on the server.193 *194 * @param r the relation195 * @see #undeleteNode(Node)196 */197 protected void undeleteRelation(final Relation r) {198 class RelationMemberGoneChecker extends PleaseWaitRunnable {199 200 UndeletePrimitivesCommand cmd = null;201 202 public RelationMemberGoneChecker() {203 super(tr("Undeleting relation..."), false);204 }205 206 @Override207 protected void cancel() {208 OsmApi.getOsmApi().cancel();209 }210 211 @Override212 protected void finish() {213 if (cmd != null) {214 Main.main.undoRedo.add(cmd);215 }216 }217 218 protected ArrayList<OsmPrimitive> getCandidateRelationMembers(Relation r) {219 ArrayList<OsmPrimitive> candidates = new ArrayList<OsmPrimitive>();220 for (RelationMember m : r.members) {221 if (m.member.id > 0 && !candidates.contains(m.member)) {222 candidates.add(m.member);223 }224 }225 return candidates;226 }227 228 protected boolean isGone(OsmPrimitive primitive) throws OsmTransferException {229 OsmServerObjectReader reader = new OsmServerObjectReader(230 primitive.id,231 OsmPrimitiveType.from(primitive),232 true);233 try {234 reader.parseOsm();235 } catch(OsmApiException e) {236 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE)237 return true;238 throw e;239 } catch(OsmTransferException e) {240 throw e;241 }242 return false;243 }244 245 protected boolean confirmUndeleteDependentPrimitives(Relation r, ArrayList<OsmPrimitive> dependent) {246 String [] options = {247 tr("Yes, undelete them too"),248 tr("No, cancel operation")249 };250 int ret = JOptionPane.showOptionDialog(251 Main.parent,252 tr("<html>There are {0} additional primitives referred to by relation {1}<br>"253 + "which are deleted on the server.<br>"254 + "<br>"255 + "Do you want to undelete them too?</html>",256 Long.toString(dependent.size()), Long.toString(r.id)),257 tr("Undelete dependent primitives?"),258 JOptionPane.YES_NO_OPTION,259 JOptionPane.QUESTION_MESSAGE,260 null,261 options,262 options[0]263 );264 265 switch(ret) {266 case JOptionPane.CLOSED_OPTION: return false;267 case JOptionPane.YES_OPTION: return true;268 case JOptionPane.NO_OPTION: return false;269 }270 return false;271 272 }273 274 @Override275 protected void realRun() throws SAXException, IOException, OsmTransferException {276 ArrayList<OsmPrimitive> candidate = getCandidateRelationMembers(r);277 ArrayList<OsmPrimitive> toDelete = new ArrayList<OsmPrimitive>();278 Main.pleaseWaitDlg.progress.setMinimum(0);279 Main.pleaseWaitDlg.progress.setMaximum(candidate.size());280 281 for (int i=0; i<candidate.size();i++) {282 OsmPrimitive primitive = candidate.get(i);283 Main.pleaseWaitDlg.progress.setValue(i);284 Main.pleaseWaitDlg.currentAction.setText(tr("Checking whether primitive {0} is gone ...", primitive.id));285 if (isGone(primitive)) {286 toDelete.add(primitive);287 }288 }289 if (toDelete.size() > 0) {290 if (!confirmUndeleteDependentPrimitives(r, toDelete))291 return;292 }293 294 toDelete.add(r);295 cmd = new UndeletePrimitivesCommand(toDelete);296 }297 }298 299 Main.worker.submit(new RelationMemberGoneChecker());300 }301 302 /**303 * User has decided to keep his local version of a primitive which had been deleted304 * on the server305 *306 * @param id the primitive id307 */308 protected void handlePrimitiveGoneKeepMine(long id) {309 OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);310 if (primitive instanceof Node) {311 undeleteNode((Node)primitive);312 } else if (primitive instanceof Way) {313 undeleteWay((Way)primitive);314 } else if (primitive instanceof Relation) {315 undeleteRelation((Relation)primitive);316 }317 }318 319 /**320 * User has decided to delete his local version of a primitive which had been deleted321 * on the server322 *323 * @param id the primitive id324 */325 protected void handlePrimitiveGoneDeleteMine(long id) {326 OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id);327 PurgePrimitivesCommand cmd = new PurgePrimitivesCommand(primitive);328 Main.main.undoRedo.add(cmd);329 Main.map.mapView.repaint();330 }331 332 /**333 31 * handle an exception thrown because a primitive was deleted on the server 334 32 * … … 336 34 */ 337 35 protected void handlePrimitiveGoneException(long id) { 338 Object[] options = new Object[] { 339 tr("Keep mine"), 340 tr("Delete mine"), 341 tr("Cancel") 342 }; 343 Object defaultOption = options[0]; 344 String msg = tr("<html>The OSM primitive with id <strong>{0}</strong> has been deleted<br>" 345 + "on the server by another mapper.<br>" 346 + "<br>" 347 + "Click <strong>{1}</strong> to keep your primitive and ignore the deleted state.<br>" 348 + "Your primitive will be assigend a new id.<br>" 349 + "Click <strong>{2}</strong> to accept the state on the server and to delete your primitive.<br>" 350 + "Click <strong>{3}</strong> to cancel.<br>", 351 id, options[0], options[1], options[2] 352 ); 353 int ret = JOptionPane.showOptionDialog( 354 null, 355 msg, 356 tr("Primitive deleted on the server"), 357 JOptionPane.YES_NO_CANCEL_OPTION, 358 JOptionPane.ERROR_MESSAGE, 359 null, 360 options, 361 defaultOption 362 ); 363 switch(ret) { 364 case JOptionPane.CLOSED_OPTION: return; 365 case JOptionPane.CANCEL_OPTION: return; 366 case 0: handlePrimitiveGoneKeepMine(id); break; 367 case 1: handlePrimitiveGoneDeleteMine(id); break; 368 default: 369 // should not happen 370 throw new IllegalStateException(tr("unexpected return value. Got {0}", ret)); 36 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 37 reader.append(Main.main.editLayer().data,id); 38 DataSet ds = null; 39 try { 40 ds = reader.parseOsm(); 41 } catch(Exception e) { 42 handleUpdateException(e); 43 return; 371 44 } 45 Main.main.editLayer().mergeFrom(ds); 372 46 } 373 47 … … 378 52 * @param e the exception 379 53 */ 380 protected void handleUpdateException(long id, Exception e) { 381 if (e instanceof OsmApiException) { 382 OsmApiException ex = (OsmApiException)e; 383 // if the primitive was deleted on the server ask the user 384 // whether he wants to undelete it 385 // 386 if (ex.getResponseCode() == HttpURLConnection.HTTP_GONE) { 387 handlePrimitiveGoneException(id); 388 return; 389 } 390 } 391 54 protected void handleUpdateException(Exception e) { 392 55 e.printStackTrace(); 393 56 JOptionPane.showMessageDialog( … … 406 69 JOptionPane.showMessageDialog( 407 70 Main.parent, 408 tr("Could not find primitive with id {0} in the current dataset", id),71 tr("Could not find primitive with id {0} in the current dataset", new Long(id).toString()), 409 72 tr("Missing primitive"), 410 73 JOptionPane.ERROR_MESSAGE … … 413 76 414 77 /** 415 * Updates the primitive with id <code>id</code> with the current state kept on the server.416 78 * 417 * @param id 79 * 80 * 418 81 */ 419 public void updatePrimitive(long id) { 420 OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id); 421 if (primitive == null) { 422 handleMissingPrimitive(id); 423 return; 424 } 425 OsmServerObjectReader reader = new OsmServerObjectReader( 426 id, 427 OsmPrimitiveType.from(primitive), 428 true); 82 public void updatePrimitives(Collection<OsmPrimitive> selection) { 83 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 84 reader.append(selection); 429 85 DataSet ds = null; 430 86 try { 431 87 ds = reader.parseOsm(); 432 88 } catch(Exception e) { 433 handleUpdateException( id,e);89 handleUpdateException(e); 434 90 return; 435 91 } … … 437 93 } 438 94 95 public void updatePrimitive(long id) { 96 OsmPrimitive primitive = Main.main.editLayer().data.getPrimitiveById(id); 97 Set<OsmPrimitive> s = new HashSet<OsmPrimitive>(); 98 s.add(primitive); 99 updatePrimitives(s); 100 } 439 101 440 102 public UpdateSelectionAction() { … … 461 123 return; 462 124 } 463 464 // check whether the current selection has an acceptable range. 465 // We don't want to hammer the API with hundreds of individual 466 // GET requests. 467 // 468 if (selection.size() > DEFAULT_MAX_SIZE_UPDATE_SELECTION) { 469 JOptionPane.showMessageDialog( 470 Main.parent, 471 tr("<html>There are <strong>{0}</strong> primitives <br>" 472 + "selected for individual update. Please reduce the selection<br>" 473 + "to max. {1} primitives.</html>", 474 selection.size(), DEFAULT_MAX_SIZE_UPDATE_SELECTION 475 ), 476 tr("Selection too big"), 477 JOptionPane.WARNING_MESSAGE 478 ); 479 return; 480 } 481 for(OsmPrimitive primitive : selection) { 482 // FIXME: users should be able to abort this loop 483 // 484 updatePrimitive(primitive.id); 485 } 125 updatePrimitives(selection); 486 126 } 487 127 } -
trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTaskList.java
r1682 r1690 128 128 protected void handlePotentiallyDeletedPrimitives(Set<Long> potentiallyDeleted) { 129 129 String [] options = { 130 "Check individually",130 "Check on the server", 131 131 "Ignore" 132 132 }; … … 138 138 + "conflict.<br>" 139 139 + "<br>" 140 + "Click <strong>{1}</strong> to check these primitives individually.<br>" 140 + "Click <strong>{1}</strong> to check the state of these primitives<br>" 141 + "on the server.<br>" 141 142 + "Click <strong>{2}</strong> to ignore.<br>" 142 143 + "</html>", -
trunk/src/org/openstreetmap/josm/command/DeletedStateConflictResolveCommand.java
r1654 r1690 66 66 67 67 if (decision.equals(MergeDecisionType.KEEP_MINE)) { 68 // do nothing 68 if (my.deleted) { 69 // because my was involved in a conflict it my still be referred 70 // to from a way or a relation. Fix this now. 71 // 72 Main.main.editLayer().data.unlinkReferencesToPrimitive(my); 73 } 69 74 } else if (decision.equals(MergeDecisionType.KEEP_THEIR)) { 70 my.deleted = their.deleted; 75 if (their.deleted) { 76 Main.main.editLayer().data.unlinkReferencesToPrimitive(my); 77 my.delete(true); 78 } else { 79 my.deleted = their.deleted; 80 } 71 81 } else 72 82 // should not happen -
trunk/src/org/openstreetmap/josm/command/PurgePrimitivesCommand.java
r1670 r1690 6 6 import java.util.ArrayList; 7 7 import java.util.Collection; 8 import java.util.HashMap; 8 9 import java.util.List; 10 import java.util.Map; 9 11 10 12 import javax.swing.JLabel; … … 33 35 */ 34 36 public class PurgePrimitivesCommand extends Command{ 37 35 38 36 39 /** … … 141 144 private ArrayList<OsmParentChildPair> pairs; 142 145 146 private Map<OsmPrimitive, OsmPrimitive> resolvedConflicts; 147 143 148 /** 144 149 * constructor … … 149 154 purgedPrimitives = new ArrayList<OsmPrimitive>(); 150 155 pairs = new ArrayList<OsmParentChildPair>(); 156 resolvedConflicts = new HashMap<OsmPrimitive, OsmPrimitive>(); 151 157 } 152 158 … … 180 186 if (pair.getParent() instanceof Way) { 181 187 Way w = (Way)pair.getParent(); 182 System.out.println( "removing reference from way " + w.id);188 System.out.println(tr("removing reference from way {0}",w.id)); 183 189 w.nodes.remove(primitive); 184 190 // if a way ends up with less than two node we … … 194 200 } else if (pair.getParent() instanceof Relation) { 195 201 Relation r = (Relation)pair.getParent(); 196 System.out.println( "removing reference from relation " + r.id);202 System.out.println(tr("removing reference from relation {0}",r.id)); 197 203 r.removeMembersFor(primitive); 198 204 } … … 220 226 } 221 227 purgedPrimitives.add(toPurge); 228 if (Main.map.conflictDialog.conflicts.containsKey(toPurge)) { 229 resolvedConflicts.put(toPurge, Main.map.conflictDialog.conflicts.get(toPurge)); 230 Main.map.conflictDialog.removeConflictForPrimitive(toPurge); 231 } 222 232 } 223 233 return super.executeCommand(); … … 236 246 @Override 237 247 public void undoCommand() { 248 249 // restore purged primitives 250 // 238 251 for (OsmPrimitive purged : purgedPrimitives) { 239 252 Main.ds.addPrimitive(purged); 253 } 254 255 // restore conflicts 256 // 257 for (OsmPrimitive primitive : resolvedConflicts.keySet()) { 258 Main.map.conflictDialog.addConflict(primitive, resolvedConflicts.get(primitive)); 240 259 } 241 260 -
trunk/src/org/openstreetmap/josm/command/UndeletePrimitivesCommand.java
r1670 r1690 6 6 import java.util.ArrayList; 7 7 import java.util.Collection; 8 import java.util.HashMap; 9 import java.util.Map; 8 10 9 11 import javax.swing.JLabel; … … 11 13 import javax.swing.tree.MutableTreeNode; 12 14 15 import org.openstreetmap.josm.Main; 13 16 import org.openstreetmap.josm.data.osm.OsmPrimitive; 14 17 import org.openstreetmap.josm.tools.ImageProvider; … … 24 27 /** the node to undelete */ 25 28 private ArrayList<OsmPrimitive> toUndelete; 29 private Map<OsmPrimitive,OsmPrimitive> resolvedConflicts; 26 30 31 protected UndeletePrimitivesCommand() { 32 toUndelete = new ArrayList<OsmPrimitive>(); 33 resolvedConflicts = new HashMap<OsmPrimitive, OsmPrimitive>(); 34 } 27 35 /** 28 36 * constructor … … 30 38 */ 31 39 public UndeletePrimitivesCommand(OsmPrimitive node) { 32 t oUndelete = new ArrayList<OsmPrimitive>();40 this(); 33 41 toUndelete.add(node); 34 42 } … … 39 47 */ 40 48 public UndeletePrimitivesCommand(OsmPrimitive ... toUndelete) { 41 this .toUndelete = new ArrayList<OsmPrimitive>();49 this(); 42 50 for (int i=0; i < toUndelete.length; i++) { 43 51 this.toUndelete.add(toUndelete[i]); … … 50 58 */ 51 59 public UndeletePrimitivesCommand(Collection<OsmPrimitive> toUndelete) { 52 this .toUndelete = new ArrayList<OsmPrimitive>();60 this(); 53 61 this.toUndelete.addAll(toUndelete); 54 62 } … … 70 78 super.executeCommand(); 71 79 for(OsmPrimitive primitive: toUndelete) { 80 if (Main.map.conflictDialog.conflicts.containsKey(primitive)) { 81 resolvedConflicts.put(primitive, Main.map.conflictDialog.conflicts.get(primitive)); 82 Main.map.conflictDialog.removeConflictForPrimitive(primitive); 83 } 72 84 primitive.id = 0; 73 85 } … … 80 92 modified.addAll(toUndelete); 81 93 } 94 @Override 95 public void undoCommand() { 96 super.undoCommand(); 97 98 for (OsmPrimitive my: resolvedConflicts.keySet()) { 99 if (!Main.map.conflictDialog.conflicts.containsKey(my)) { 100 Main.map.conflictDialog.addConflict(my, resolvedConflicts.get(my)); 101 } 102 } 103 } 82 104 } -
trunk/src/org/openstreetmap/josm/command/VersionConflictResolveCommand.java
r1670 r1690 11 11 12 12 import org.openstreetmap.josm.Main; 13 import org.openstreetmap.josm.data.osm.Node;14 13 import org.openstreetmap.josm.data.osm.OsmPrimitive; 15 14 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 16 import org.openstreetmap.josm.data.osm.Relation;17 import org.openstreetmap.josm.data.osm.Way;18 15 import org.openstreetmap.josm.tools.ImageProvider; 19 16 -
trunk/src/org/openstreetmap/josm/data/osm/DataSet.java
r1677 r1690 8 8 import java.util.HashSet; 9 9 import java.util.HashMap; 10 import java.util.Iterator; 10 11 import java.util.LinkedList; 11 12 import java.util.List; … … 80 81 Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>(); 81 82 for (OsmPrimitive osm : allPrimitives()) 82 if ( !osm.deleted) {83 if (osm.visible && !osm.deleted) { 83 84 o.add(osm); 84 85 } … … 89 90 Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>(); 90 91 for (OsmPrimitive osm : allPrimitives()) 91 if ( !osm.deleted && !osm.incomplete) {92 if (osm.visible && !osm.deleted && !osm.incomplete) { 92 93 o.add(osm); 93 94 } … … 98 99 Collection<OsmPrimitive> o = new LinkedList<OsmPrimitive>(); 99 100 for (OsmPrimitive osm : allPrimitives()) 100 if ( !osm.deleted && !osm.incomplete && !(osm instanceof Relation)) {101 if (osm.visible && !osm.deleted && !osm.incomplete && !(osm instanceof Relation)) { 101 102 o.add(osm); 102 103 } … … 311 312 return ret; 312 313 } 314 315 protected void deleteWay(Way way) { 316 way.nodes.clear(); 317 way.delete(true); 318 } 319 320 /** 321 * removes all references from ways in this dataset to a particular node 322 * 323 * @param node the node 324 */ 325 public void unlinkNodeFromWays(Node node) { 326 for (Way way: ways) { 327 if (way.nodes.contains(node)) { 328 way.nodes.remove(node); 329 if (way.nodes.size() < 2) { 330 deleteWay(way); 331 } 332 } 333 } 334 } 335 336 /** 337 * removes all references from relations in this dataset to this primitive 338 * 339 * @param primitive the primitive 340 */ 341 public void unlinkPrimitiveFromRelations(OsmPrimitive primitive) { 342 for (Relation relation : relations) { 343 Iterator<RelationMember> it = relation.members.iterator(); 344 while(it.hasNext()) { 345 RelationMember member = it.next(); 346 if (member.member.equals(primitive)) { 347 it.remove(); 348 } 349 } 350 } 351 } 352 353 /** 354 * removes all references from from other primitives to the 355 * referenced primitive 356 * 357 * @param referencedPrimitive the referenced primitive 358 */ 359 public void unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) { 360 if (referencedPrimitive instanceof Node) { 361 unlinkNodeFromWays((Node)referencedPrimitive); 362 unlinkPrimitiveFromRelations(referencedPrimitive); 363 } else { 364 unlinkPrimitiveFromRelations(referencedPrimitive); 365 } 366 } 313 367 } -
trunk/src/org/openstreetmap/josm/data/osm/Node.java
r1640 r1690 31 31 32 32 public final void setEastNorth(EastNorth eastNorth) { 33 this.eastNorth = eastNorth;34 this.coor = Main.proj.eastNorth2latlon(eastNorth);33 this.eastNorth = eastNorth; 34 this.coor = Main.proj.eastNorth2latlon(eastNorth); 35 35 } 36 36 … … 87 87 } 88 88 89 /** 90 * @deprecated 91 * @see #hasEqualSemanticAttributes(OsmPrimitive) 92 * @see #hasEqualTechnicalAttributes(OsmPrimitive) 93 */ 94 @Deprecated 89 95 @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) { 90 96 if (osm instanceof Node) { … … 99 105 } 100 106 107 @Override 108 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 109 if (other == null || ! (other instanceof Node) ) 110 return false; 111 if (! super.hasEqualSemanticAttributes(other)) 112 return false; 113 Node n = (Node)other; 114 if (coor == null && n.coor == null) 115 return true; 116 else if (coor != null && n.coor != null) 117 return coor.equals(n.coor); 118 else 119 return false; 120 } 121 101 122 public int compareTo(OsmPrimitive o) { 102 123 return o instanceof Node ? Long.valueOf(id).compareTo(o.id) : 1; 103 124 } 104 125 126 @Override 105 127 public String getName() { 106 128 String name; … … 109 131 } else { 110 132 name = get("name"); 111 if (name == null) 133 if (name == null) { 112 134 name = id == 0 ? tr("node") : ""+id; 135 } 113 136 name += " (" + coor.latToString(mCord) + ", " + coor.lonToString(mCord) + ")"; 114 137 } -
trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
r1530 r1690 43 43 public void putError(String text, Boolean isError) 44 44 { 45 if(errors == null) 45 if(errors == null) { 46 46 errors = new ArrayList<String>(); 47 } 47 48 String s = isError ? tr("Error: {0}", text) : tr("Warning: {0}", text); 48 49 errors.add(s); … … 91 92 * Visibility status as specified by the server. The visible attribute was 92 93 * introduced with the 0.4 API to be able to communicate deleted objects 93 * (they will have visible=false). Currently JOSM does never deal with 94 * these, so this is really for future use only. 94 * (they will have visible=false). 95 95 */ 96 96 public boolean visible = true; … … 195 195 @Override public boolean equals(Object obj) { 196 196 if (id == 0) return obj == this; 197 if (obj instanceof OsmPrimitive) { // not null too197 if (obj instanceof OsmPrimitive) 198 198 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass(); 199 }200 199 return false; 201 200 } … … 226 225 */ 227 226 public final void put(String key, String value) { 228 if (value == null) 227 if (value == null) { 229 228 remove(key); 230 else {231 if (keys == null) 229 } else { 230 if (keys == null) { 232 231 keys = new HashMap<String, String>(); 232 } 233 233 keys.put(key, value); 234 234 } … … 241 241 if (keys != null) { 242 242 keys.remove(key); 243 if (keys.isEmpty()) 243 if (keys.isEmpty()) { 244 244 keys = null; 245 } 245 246 } 246 247 mappaintStyle = null; … … 280 281 version = osm.version; 281 282 incomplete = osm.incomplete; 283 visible = osm.visible; 282 284 clearCached(); 283 285 clearErrors(); … … 288 290 * but for the whole object (for conflict resolving) 289 291 * @param semanticOnly if <code>true</code>, modified flag and timestamp are not compared 290 */ 292 * 293 * @deprecated 294 * @see #hasEqualSemanticAttributes(OsmPrimitive) 295 * @see #hasEqualTechnicalAttributes(OsmPrimitive) 296 */ 297 @Deprecated 291 298 public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) { 292 299 return id == osm.id 293 300 && incomplete == osm.incomplete 294 301 && deleted == osm.deleted 295 && (semanticOnly || (modified == osm.modified 296 && timestamp == osm.timestamp 297 && version == osm.version 298 && visible == osm.visible 299 && (user == null ? osm.user==null : user==osm.user))) 302 && (semanticOnly || ( 303 modified == osm.modified 304 && timestamp == osm.timestamp 305 && version == osm.version 306 && visible == osm.visible 307 && (user == null ? osm.user==null : user==osm.user)) 308 ) 300 309 && (keys == null ? osm.keys==null : keys.equals(osm.keys)); 310 } 311 312 /** 313 * Replies true if this primitive and other are equal with respect to their 314 * semantic attributes. 315 * <ol> 316 * <li>equal id</ol> 317 * <li>both are complete or both are incomplete</li> 318 * <li>both have the same tags</li> 319 * </ol> 320 * @param other 321 * @return true if this primitive and other are equal with respect to their 322 * semantic attributes. 323 */ 324 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 325 if (id != other.id) 326 return false; 327 if (incomplete && ! other.incomplete || !incomplete && other.incomplete) 328 return false; 329 return (keys == null ? other.keys==null : keys.equals(other.keys)); 330 } 331 332 /** 333 * Replies true if this primitive and other are equal with respect to their 334 * technical attributes. The attributes: 335 * <ol> 336 * <li>deleted</ol> 337 * <li>modified</ol> 338 * <li>timestamp</ol> 339 * <li>version</ol> 340 * <li>visible</ol> 341 * <li>user</ol> 342 * </ol> 343 * have to be equal 344 * @param other the other primitive 345 * @return true if this primitive and other are equal with respect to their 346 * technical attributes 347 */ 348 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) { 349 if (other == null) return false; 350 351 return 352 deleted == other.deleted 353 && modified == other.modified 354 && timestamp == other.timestamp 355 && version == other.version 356 && visible == other.visible 357 && (user == null ? other.user==null : user==other.user); 301 358 } 302 359 … … 312 369 if (keys != null) { 313 370 for (Entry<String,String> e : keys.entrySet()) { 314 if (!uninteresting.contains(e.getKey())) {371 if (!uninteresting.contains(e.getKey())) 315 372 return true; 316 }317 373 } 318 374 } … … 327 383 if (keys != null) { 328 384 for (Entry<String,String> e : keys.entrySet()) { 329 if (directionKeys.contains(e.getKey())) {385 if (directionKeys.contains(e.getKey())) 330 386 return true; 331 }332 387 } 333 388 } -
trunk/src/org/openstreetmap/josm/data/osm/Relation.java
r1677 r1690 70 70 } 71 71 72 @Deprecated 72 73 @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) { 73 74 return osm instanceof Relation ? super.realEqual(osm, semanticOnly) && members.equals(((Relation)osm).members) : false; 75 } 76 77 @Override 78 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 79 if (other == null || ! (other instanceof Relation) ) 80 return false; 81 if (! super.hasEqualSemanticAttributes(other)) 82 return false; 83 Relation r = (Relation)other; 84 return members.equals(r.members); 74 85 } 75 86 -
trunk/src/org/openstreetmap/josm/data/osm/RelationMember.java
r1169 r1690 33 33 34 34 @Override public boolean equals(Object other) { 35 if ( !(other instanceof RelationMember)) return false;35 if (other == null || !(other instanceof RelationMember)) return false; 36 36 RelationMember otherMember = (RelationMember) other; 37 37 return otherMember.role.equals(role) && otherMember.member.equals(member); -
trunk/src/org/openstreetmap/josm/data/osm/Way.java
r1677 r1690 39 39 public void visitNodes(Visitor v) { 40 40 if (incomplete) return; 41 for (Node n : this.nodes) 41 for (Node n : this.nodes) { 42 42 v.visit(n); 43 } 43 44 } 44 45 … … 100 101 } 101 102 103 @Deprecated 102 104 @Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) { 103 105 return osm instanceof Way ? super.realEqual(osm, semanticOnly) && nodes.equals(((Way)osm).nodes) : false; 106 } 107 108 @Override 109 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 110 if (other == null || ! (other instanceof Way) ) 111 return false; 112 if (! super.hasEqualSemanticAttributes(other)) 113 return false; 114 Way w = (Way)other; 115 return nodes.equals(w.nodes); 104 116 } 105 117 … … 110 122 } 111 123 124 @Override 112 125 public String getName() { 113 126 String name; … … 116 129 } else { 117 130 name = get("name"); 118 if (name == null) name = get("ref"); 131 if (name == null) { 132 name = get("ref"); 133 } 119 134 if (name == null) { 120 135 name = 121 136 (get("highway") != null) ? tr("highway") : 122 (get("railway") != null) ? tr("railway") :123 (get("waterway") != null) ? tr("waterway") :124 (get("landuse") != null) ? tr("landuse") : "";137 (get("railway") != null) ? tr("railway") : 138 (get("waterway") != null) ? tr("waterway") : 139 (get("landuse") != null) ? tr("landuse") : ""; 125 140 } 126 141 … … 128 143 String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo); 129 144 name += (name.length() > 0) ? " ("+nodes+")" : nodes; 130 if(errors != null) 145 if(errors != null) { 131 146 name = "*"+name; 147 } 132 148 } 133 149 return name; … … 138 154 boolean closed = (lastNode() == n && firstNode() == n); 139 155 int i; 140 while ((i = nodes.indexOf(n)) >= 0) 156 while ((i = nodes.indexOf(n)) >= 0) { 141 157 nodes.remove(i); 158 } 142 159 i = nodes.size(); 143 if (closed && i > 2) // close again160 if (closed && i > 2) { 144 161 addNode(firstNode()); 145 // prevent closed ways with less than 3 different nodes 146 else if (i >= 2 && i <= 3 && nodes.get(0) == nodes.get(i-1)) 162 } else if (i >= 2 && i <= 3 && nodes.get(0) == nodes.get(i-1)) { 147 163 nodes.remove(i-1); 164 } 148 165 } 149 166 … … 151 168 if (incomplete) return; 152 169 for(OsmPrimitive p : selection) { 153 if (p instanceof Node) {154 removeNode((Node)p);155 }156 }170 if (p instanceof Node) { 171 removeNode((Node)p); 172 } 173 } 157 174 } 158 175 -
trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java
r1677 r1690 2 2 package org.openstreetmap.josm.data.osm.visitor; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 4 6 import java.util.Collection; 5 import java.util.Date;6 7 import java.util.HashMap; 7 8 import java.util.Iterator; 8 9 import java.util.LinkedList; 9 10 import java.util.Map; 11 import java.util.logging.Logger; 10 12 11 13 import org.openstreetmap.josm.data.osm.DataSet; 14 import org.openstreetmap.josm.data.osm.Node; 15 import org.openstreetmap.josm.data.osm.OsmPrimitive; 12 16 import org.openstreetmap.josm.data.osm.Relation; 13 17 import org.openstreetmap.josm.data.osm.RelationMember; 14 import org.openstreetmap.josm.data.osm.Node;15 import org.openstreetmap.josm.data.osm.OsmPrimitive;16 18 import org.openstreetmap.josm.data.osm.Way; 17 19 18 20 /** 19 * A visitor that get a data set at construction time and mergeevery visited object21 * A visitor that gets a data set at construction time and merges every visited object 20 22 * into it. 21 23 * 22 24 * @author imi 25 * @author Gubaer 23 26 */ 24 27 public class MergeVisitor extends AbstractVisitor { 28 private static Logger logger = Logger.getLogger(MergeVisitor.class.getName()); 25 29 26 30 /** … … 28 32 * round than merged) 29 33 */ 30 p ublic Map<OsmPrimitive, OsmPrimitive> conflicts = new HashMap<OsmPrimitive, OsmPrimitive>();31 32 private final DataSet ds;33 private final DataSet mergeds;34 private Map<OsmPrimitive, OsmPrimitive> conflicts; 35 36 private final DataSet myDataSet; 37 private final DataSet theirDataSet; 34 38 35 39 private final HashMap<Long, Node> nodeshash = new HashMap<Long, Node>(); … … 42 46 * in ds.nodes instead. 43 47 */ 44 private final Map<OsmPrimitive, OsmPrimitive> merged 45 = new HashMap<OsmPrimitive, OsmPrimitive>(); 46 47 public MergeVisitor(DataSet ds, DataSet mergeds) { 48 this.ds = ds; 49 this.mergeds = mergeds; 50 51 for (Node n : ds.nodes) if (n.id != 0) nodeshash.put(n.id, n); 52 for (Way w : ds.ways) if (w.id != 0) wayshash.put(w.id, w); 53 for (Relation r : ds.relations) if (r.id != 0) relshash.put(r.id, r); 54 } 55 56 private <P extends OsmPrimitive> void genMerge(P other, 57 Collection<P> myprims, Collection<P> mergeprims, 58 HashMap<Long, P> primhash) { 59 // 1. Try to find an identical prim with the same id. 60 if (mergeById(myprims, primhash, other)) 61 return; 62 63 // 2. Try to find a prim we can merge with the prim from the other ds. 64 for (P my : myprims) { 65 // LinkedList.contains calls equal, and OsmPrimitive.equal 66 // compares just the id. 67 if (match(my, other) && !mergeprims.contains(my)) { 68 merged.put(other, my); 69 mergeCommon(my, other); 48 private Map<OsmPrimitive, OsmPrimitive> merged; 49 50 /** 51 * constructor 52 * 53 * The visitor will merge <code>theirDataSet</code> onto <code>myDataSet</code> 54 * 55 * @param myDataSet dataset with my primitives 56 * @param theirDataSet dataset with their primitives. 57 */ 58 public MergeVisitor(DataSet myDataSet, DataSet theirDataSet) { 59 this.myDataSet = myDataSet; 60 this.theirDataSet = theirDataSet; 61 62 for (Node n : myDataSet.nodes) if (n.id != 0) { 63 nodeshash.put(n.id, n); 64 } 65 for (Way w : myDataSet.ways) if (w.id != 0) { 66 wayshash.put(w.id, w); 67 } 68 for (Relation r : myDataSet.relations) if (r.id != 0) { 69 relshash.put(r.id, r); 70 } 71 conflicts = new HashMap<OsmPrimitive, OsmPrimitive>(); 72 merged = new HashMap<OsmPrimitive, OsmPrimitive>(); 73 } 74 75 /** 76 * Merges a primitive <code>other</code> of type <P> onto my primitives. 77 * 78 * If other.id != 0 it tries to merge it with an corresponding primitive from 79 * my dataset with the same id. If this is not possible a conflict is remembered 80 * in {@see #conflicts}. 81 * 82 * If other.id == 0 it tries to find a primitive in my dataset with id == 0 which 83 * is semantically equal. If it finds one it merges its technical attributes onto 84 * my primitive. 85 * 86 * @param <P> the type of the other primitive 87 * @param other the other primitive 88 * @param myPrimitives the collection of my relevant primitives (i.e. only my 89 * primitives of the same type) 90 * @param otherPrimitives the collection of the other primitives 91 * @param primitivesWithDefinedIds the collection of my primitives with an 92 * assigned id (i.e. id != 0) 93 */ 94 protected <P extends OsmPrimitive> void mergePrimitive(P other, 95 Collection<P> myPrimitives, Collection<P> otherPrimitives, 96 HashMap<Long, P> primitivesWithDefinedIds) { 97 98 if (other.id > 0 ) { 99 // try to merge onto a matching primitive with the same 100 // defined id 101 // 102 if (mergeById(myPrimitives, primitivesWithDefinedIds, other)) 70 103 return; 71 } 72 } 73 74 // 3. No idea how to merge that. Simply add it unchanged. 75 myprims.add(other); 104 } else { 105 // try to merge onto a primitive with which has no id assigned 106 // yet but which is equal in its semantic attributes 107 // 108 for (P my : myPrimitives) { 109 if (my.id >0 ) { 110 continue; 111 } 112 if (my.hasEqualSemanticAttributes(other)) { 113 // copy the technical attributes from their 114 // version 115 if (other.deleted) { 116 myDataSet.unlinkReferencesToPrimitive(my); 117 my.delete(true); 118 } 119 my.visible = other.visible; 120 my.user = other.user; 121 my.setTimestamp(other.getTimestamp()); 122 my.modified = other.modified; 123 merged.put(other, my); 124 return; 125 } 126 } 127 } 128 // If we get here we didn't find a suitable primitive in 129 // my dataset. Just add other to my dataset. 130 // 131 myPrimitives.add(other); 76 132 } 77 133 78 134 public void visit(Node other) { 79 genMerge(other, ds.nodes, mergeds.nodes, nodeshash);135 mergePrimitive(other, myDataSet.nodes, theirDataSet.nodes, nodeshash); 80 136 } 81 137 82 138 public void visit(Way other) { 83 139 fixWay(other); 84 genMerge(other, ds.ways, mergeds.ways, wayshash);140 mergePrimitive(other, myDataSet.ways, theirDataSet.ways, wayshash); 85 141 } 86 142 87 143 public void visit(Relation other) { 88 144 fixRelation(other); 89 genMerge(other, ds.relations, mergeds.relations, relshash);145 mergePrimitive(other, myDataSet.relations, theirDataSet.relations, relshash); 90 146 } 91 147 … … 95 151 */ 96 152 public void fixReferences() { 97 for (Way w : ds.ways) fixWay(w); 98 for (Relation r : ds.relations) fixRelation(r); 153 for (Way w : myDataSet.ways) { 154 fixWay(w); 155 } 156 for (Relation r : myDataSet.relations) { 157 fixRelation(r); 158 } 99 159 for (OsmPrimitive osm : conflicts.values()) 100 if (osm instanceof Way) 160 if (osm instanceof Way) { 101 161 fixWay((Way)osm); 102 else if (osm instanceof Relation)162 } else if (osm instanceof Relation) { 103 163 fixRelation((Relation) osm); 164 } 104 165 } 105 166 … … 110 171 Node otherN = (Node) merged.get(n); 111 172 newNodes.add(otherN == null ? n : otherN); 112 if (otherN != null) 173 if (otherN != null) { 113 174 replacedSomething = true; 175 } 114 176 } 115 177 if (replacedSomething) { … … 139 201 } 140 202 141 private static <P extends OsmPrimitive> boolean match(P p1, P p2) {142 if ((p1.id == 0 || p2.id == 0) && !p1.incomplete && !p2.incomplete) {143 return realMatch(p1, p2);144 }145 return p1.id == p2.id;146 }147 148 /** @return true if the prims have pretty much the same data, i.e. the149 * same position, the same members, ...150 */151 // Java cannot dispatch on generics...152 private static boolean realMatch(OsmPrimitive p1, OsmPrimitive p2) {153 if (p1 instanceof Node && p2 instanceof Node) {154 return realMatch((Node) p1, (Node) p2);155 } else if (p1 instanceof Way && p2 instanceof Way) {156 return realMatch((Way) p1, (Way) p2);157 } else if (p1 instanceof Relation && p2 instanceof Relation) {158 return realMatch((Relation) p1, (Relation) p2);159 } else {160 throw new RuntimeException("arguments have unknown type");161 }162 }163 164 private static boolean realMatch(Node n1, Node n2) {165 return n1.getCoor().equalsEpsilon(n2.getCoor());166 }167 168 private static boolean realMatch(Way w1, Way w2) {169 if (w1.nodes.size() != w2.nodes.size())170 return false;171 Iterator<Node> it = w1.nodes.iterator();172 for (Node n : w2.nodes)173 if (!match(n, it.next()))174 return false;175 return true;176 }177 178 private static boolean realMatch(Relation w1, Relation w2) {179 // FIXME this is not perfect yet...180 if (w1.members.size() != w2.members.size())181 return false;182 for (RelationMember em : w1.members) {183 if (!w2.members.contains(em)) {184 return false;185 }186 }187 return true;188 }189 190 /**191 * Merge the common parts of an osm primitive.192 * @param my The object, the information gets merged into193 * @param other The object, the information gets merged from194 */195 private void mergeCommon(OsmPrimitive my, OsmPrimitive other) {196 if (other.deleted)197 my.delete(true);198 if (my.id == 0 || !my.modified || other.modified) {199 if (my.id == 0 && other.id != 0) {200 my.id = other.id;201 my.modified = other.modified; // match a new node202 my.version = other.version;203 } else if (my.id != 0 && other.id != 0 && other.modified)204 my.modified = true;205 }206 if (other.keys == null)207 return;208 if (my.keySet().containsAll(other.keys.keySet()))209 return;210 if (my.keys == null)211 my.keys = other.keys;212 else213 my.keys.putAll(other.keys);214 215 my.modified = true;216 }217 218 203 /** 219 204 * Tries to merge a primitive <code>other</code> into an existing primitive with the same id. 220 205 * 221 206 * @param myPrimitives the complete set of my primitives (potential merge targets) 222 * @param myPrimitivesWith IDthe map of primitives (potential merge targets) with an id <> 0, for faster lookup207 * @param myPrimitivesWithDefinedIds the map of primitives (potential merge targets) with an id <> 0, for faster lookup 223 208 * by id. Key is the id, value the primitive with the given value. myPrimitives.valueSet() is a 224 209 * subset of primitives. 225 * @param other the other primitive which is to be merged with a primitive in primitives if possible210 * @param other the other primitive which is to be merged onto a primitive in my primitives 226 211 * @return true, if this method was able to merge <code>other</code> with an existing node; false, otherwise 227 212 */ 228 213 private <P extends OsmPrimitive> boolean mergeById( 229 Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWith ID, P other) {214 Collection<P> myPrimitives, HashMap<Long, P> myPrimitivesWithDefinedIds, P other) { 230 215 231 216 // merge other into an existing primitive with the same id, if possible 232 217 // 233 if (myPrimitivesWithID.containsKey(other.id)) { 234 P my = myPrimitivesWithID.get(other.id); 235 if (my.realEqual(other, true /* compare semantic fields only */)) { 236 // make sure the merge target becomes the higher version number 237 // and the later timestamp 238 // 239 my.version = Math.max(other.version, my.version); 240 if (other.getTimestamp().after(my.getTimestamp())) { 241 my.setTimestamp(other.getTimestamp()); 218 if (myPrimitivesWithDefinedIds.containsKey(other.id)) { 219 P my = myPrimitivesWithDefinedIds.get(other.id); 220 if (my.version <= other.version) { 221 if (! my.visible && other.visible) { 222 // should not happen 223 // 224 logger.warning(tr("My primitive with id {0} and version {1} is visible although " 225 + "their primitive with lower version {2} is not visible. " 226 + "Can't deal with this inconsistency. Keeping my primitive. ", 227 Long.toString(my.id),Long.toString(my.version), Long.toString(other.version) 228 )); 229 merged.put(other, my); 230 } else if (my.visible && ! other.visible) { 231 // this is always a conflict because the user has to decide whether 232 // he wants to create a clone of its local primitive or whether he 233 // wants to purge my from the local dataset. He can't keep it unchanged 234 // because it was deleted on the server. 235 // 236 conflicts.put(my,other); 237 } else if (! my.modified && other.modified) { 238 // my not modified. We can assume that other is the most recent version. 239 // clone it onto my. But check first, whether other is deleted. if so, 240 // make sure that my is not references anymore in myDataSet. 241 // 242 if (other.deleted) { 243 myDataSet.unlinkReferencesToPrimitive(my); 244 } 245 my.cloneFrom(other); 246 merged.put(other, my); 247 } else if (! my.modified && !other.modified) { 248 // nothing to merge 249 // 250 merged.put(other,my); 251 } else if (my.deleted != other.deleted) { 252 // if we get here my is modified. Differences in deleted state 253 // have to be resolved manually 254 // 255 conflicts.put(my,other); 256 } else if (! my.hasEqualSemanticAttributes(other)) { 257 // my is modified and is not semantically equal with other. Can't automatically 258 // resolve the differences 259 // => create a conflict 260 conflicts.put(my,other); 261 } else { 262 // clone from other, but keep the modified flag. Clone will mainly copy 263 // technical attributes like timestamp or user information. Semantic 264 // attributes should already be equal if we get here. 265 // 266 my.cloneFrom(other); 267 my.modified = true; 268 merged.put(other, my); 242 269 } 270 } else { 271 // my.version > other.version => keep my version 243 272 merged.put(other, my); 244 return true; 245 } 246 } 247 248 // try to merge into one of the existing primitives 249 // 250 for (P my : myPrimitives) { 251 if (my.realEqual(other, false /* compare all fields */)) { 252 merged.put(other, my); 253 return true; // no merge needed. 254 } 255 if (my.realEqual(other, true)) { 256 // they differ in modified/version combination only. Auto-resolve it. 257 merged.put(other, my); 258 if (my.version < other.version) { 259 my.version = other.version; 260 my.modified = other.modified; 261 my.setTimestamp(other.getTimestamp()); 262 } 263 return true; // merge done. 264 } 265 if (my.id == other.id && my.id != 0) { 266 if (my.incomplete || other.incomplete) { 267 if (my.incomplete) { 268 my.cloneFrom(other); 269 } 270 } else if (my.modified && other.modified) { 271 conflicts.put(my, other); 272 } else if (!my.modified && !other.modified) { 273 if (my.version < other.version) { 274 my.cloneFrom(other); 275 } 276 } else if (other.modified) { 277 if (my.version > other.version) { 278 conflicts.put(my, other); 279 } else { 280 my.cloneFrom(other); 281 } 282 } else if (my.modified) { 283 if (my.version < other.version) { 284 conflicts.put(my, other); 285 } 286 } 287 merged.put(other, my); 288 return true; 289 } 273 } 274 return true; 290 275 } 291 276 return false; 292 277 } 278 279 280 /** 281 * Runs the merge operation. Successfully merged {@see OsmPrimitive}s are in 282 * {@see #getMyDataSet()}. 283 * 284 * See {@see #getConflicts()} for a map of conflicts after the merge operation. 285 */ 286 public void merge() { 287 for (final OsmPrimitive primitive : theirDataSet.allPrimitives()) { 288 primitive.visit(this); 289 } 290 fixReferences(); 291 } 292 293 /** 294 * replies my dataset 295 * 296 * @return 297 */ 298 public DataSet getMyDataSet() { 299 return myDataSet; 300 } 301 302 303 /** 304 * replies the map of conflicts 305 * 306 * @return the map of conflicts 307 */ 308 public Map<OsmPrimitive, OsmPrimitive> getConflicts() { 309 return conflicts; 310 } 293 311 } -
trunk/src/org/openstreetmap/josm/gui/conflict/ConflictResolver.java
r1654 r1690 23 23 import org.openstreetmap.josm.gui.conflict.nodes.NodeListMergeModel; 24 24 import org.openstreetmap.josm.gui.conflict.nodes.NodeListMerger; 25 import org.openstreetmap.josm.gui.conflict.properties.OperationCancelledException; 25 26 import org.openstreetmap.josm.gui.conflict.properties.PropertiesMergeModel; 26 27 import org.openstreetmap.josm.gui.conflict.properties.PropertiesMerger; … … 145 146 this.their = their; 146 147 propertiesMerger.getModel().populate(my, their); 148 if (propertiesMerger.getModel().hasVisibleStateConflict()) { 149 tabbedPane.setEnabledAt(1, false); 150 tabbedPane.setEnabledAt(2, false); 151 tabbedPane.setEnabledAt(3, false); 152 return; 153 } 147 154 tabbedPane.setEnabledAt(0, true); 148 155 tagMerger.getModel().populate(my, their); … … 165 172 tabbedPane.setEnabledAt(3, true); 166 173 } 174 167 175 } 168 176 … … 173 181 * @return the resolution command 174 182 */ 175 public Command buildResolveCommand() {183 public Command buildResolveCommand() throws OperationCancelledException { 176 184 ArrayList<Command> commands = new ArrayList<Command>(); 177 if (tagMerger.getModel().getNumResolvedConflicts() > 0) { 178 commands.add(tagMerger.getModel().buildResolveCommand(my, their)); 179 } 180 commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their)); 181 if (my instanceof Way && nodeListMerger.getModel().isFrozen()) { 182 NodeListMergeModel model =(NodeListMergeModel)nodeListMerger.getModel(); 183 commands.add(model.buildResolveCommand((Way)my, (Way)their)); 184 } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) { 185 RelationMemberListMergeModel model =(RelationMemberListMergeModel)relationMemberMerger.getModel(); 186 commands.add(model.buildResolveCommand((Relation)my, (Relation)their)); 187 } 188 if (isResolvedCompletely()) { 189 commands.add( 190 new VersionConflictResolveCommand(my, their) 191 ); 185 if (propertiesMerger.getModel().hasVisibleStateConflict()) { 186 if (propertiesMerger.getModel().isDecidedVisibleState()) { 187 commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their)); 188 } 189 } else { 190 if (tagMerger.getModel().getNumResolvedConflicts() > 0) { 191 commands.add(tagMerger.getModel().buildResolveCommand(my, their)); 192 } 193 commands.addAll(propertiesMerger.getModel().buildResolveCommand(my, their)); 194 if (my instanceof Way && nodeListMerger.getModel().isFrozen()) { 195 NodeListMergeModel model =(NodeListMergeModel)nodeListMerger.getModel(); 196 commands.add(model.buildResolveCommand((Way)my, (Way)their)); 197 } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) { 198 RelationMemberListMergeModel model =(RelationMemberListMergeModel)relationMemberMerger.getModel(); 199 commands.add(model.buildResolveCommand((Relation)my, (Relation)their)); 200 } 201 if (isResolvedCompletely()) { 202 commands.add( 203 new VersionConflictResolveCommand(my, their) 204 ); 205 } 192 206 } 193 207 return new SequenceCommand(tr("Conflict Resolution"), commands); -
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 } -
trunk/src/org/openstreetmap/josm/gui/conflict/properties/PropertiesMerger.java
r1676 r1690 18 18 import javax.swing.JButton; 19 19 import javax.swing.JLabel; 20 import javax.swing.JOptionPane; 20 21 import javax.swing.JPanel; 21 22 import javax.swing.SwingUtilities; 23 24 import org.openstreetmap.josm.Main; 22 25 import org.openstreetmap.josm.data.coor.LatLon; 26 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 23 27 import org.openstreetmap.josm.gui.conflict.MergeDecisionType; 24 28 import org.openstreetmap.josm.tools.ImageProvider; … … 48 52 private JLabel lblTheirDeletedState; 49 53 54 private JLabel lblMyVisibleState; 55 private JLabel lblMergedVisibleState; 56 private JLabel lblTheirVisibleState; 57 50 58 private final PropertiesMergeModel model; 51 59 … … 59 67 } 60 68 61 protected void build() { 62 setLayout(new GridBagLayout()); 69 protected void buildHeaderRow() { 63 70 GridBagConstraints gc = new GridBagConstraints(); 64 71 65 // ------------------66 72 gc.gridx = 1; 67 73 gc.gridy = 0; … … 88 94 lblTheirVersion.setToolTipText(tr("Properties in their dataset, i.e. the server dataset")); 89 95 add(lblTheirVersion, gc); 90 91 // -------------------------------- 96 } 97 98 protected void buildCoordinateConflictRows() { 99 GridBagConstraints gc = new GridBagConstraints(); 100 92 101 gc.gridx = 0; 93 102 gc.gridy = 1; … … 161 170 JButton btnUndecideCoordinates = new JButton(actUndecideCoordinates); 162 171 add(btnUndecideCoordinates, gc); 163 // --------------------------------------------------- 172 } 173 174 protected void buildDeletedStateConflictRows() { 175 GridBagConstraints gc = new GridBagConstraints(); 164 176 165 177 gc.gridx = 0; … … 191 203 model.addObserver(actKeepMyDeletedState); 192 204 JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState); 193 btnKeepMy Coordinates.setName("button.keepmydeletedstate");205 btnKeepMyDeletedState.setName("button.keepmydeletedstate"); 194 206 add(btnKeepMyDeletedState, gc); 195 207 … … 212 224 model.addObserver(actKeepTheirDeletedState); 213 225 JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState); 214 btnKeep MyCoordinates.setName("button.keeptheirdeletedstate");226 btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate"); 215 227 add(btnKeepTheirDeletedState, gc); 216 228 … … 233 245 model.addObserver(actUndecideDeletedState); 234 246 JButton btnUndecideDeletedState = new JButton(actUndecideDeletedState); 235 btn KeepMyCoordinates.setName("button.undecidedeletedstate");247 btnUndecideDeletedState.setName("button.undecidedeletedstate"); 236 248 add(btnUndecideDeletedState, gc); 237 249 } 250 251 protected void buildVisibleStateRows() { 252 GridBagConstraints gc = new GridBagConstraints(); 253 254 gc.gridx = 0; 255 gc.gridy = 5; 256 gc.gridwidth = 1; 257 gc.gridheight = 1; 258 gc.fill = GridBagConstraints.BOTH; 259 gc.anchor = GridBagConstraints.LINE_START; 260 gc.weightx = 0.0; 261 gc.weighty = 0.0; 262 gc.insets = new Insets(0,5,0,5); 263 add(new JLabel(tr("Visible State:")), gc); 264 265 gc.gridx = 1; 266 gc.gridy = 5; 267 gc.fill = GridBagConstraints.BOTH; 268 gc.anchor = GridBagConstraints.CENTER; 269 gc.weightx = 0.33; 270 gc.weighty = 0.0; 271 add(lblMyVisibleState = buildValueLabel("label.myvisiblestate"), gc); 272 273 gc.gridx = 2; 274 gc.gridy = 5; 275 gc.fill = GridBagConstraints.NONE; 276 gc.anchor = GridBagConstraints.CENTER; 277 gc.weightx = 0.0; 278 gc.weighty = 0.0; 279 KeepMyVisibleStateAction actKeepMyVisibleState = new KeepMyVisibleStateAction(); 280 model.addObserver(actKeepMyVisibleState); 281 JButton btnKeepMyVisibleState = new JButton(actKeepMyVisibleState); 282 btnKeepMyVisibleState.setName("button.keepmyvisiblestate"); 283 add(btnKeepMyVisibleState, gc); 284 285 gc.gridx = 3; 286 gc.gridy = 5; 287 gc.fill = GridBagConstraints.BOTH; 288 gc.anchor = GridBagConstraints.CENTER; 289 gc.weightx = 0.33; 290 gc.weighty = 0.0; 291 add(lblMergedVisibleState = buildValueLabel("label.mergedvisiblestate"), gc); 292 293 gc.gridx = 4; 294 gc.gridy = 5; 295 gc.fill = GridBagConstraints.NONE; 296 gc.anchor = GridBagConstraints.CENTER; 297 gc.weightx = 0.0; 298 gc.weighty = 0.0; 299 KeepTheirVisibleStateAction actKeepTheirVisibleState = new KeepTheirVisibleStateAction(); 300 model.addObserver(actKeepTheirVisibleState); 301 JButton btnKeepTheirVisibleState = new JButton(actKeepTheirVisibleState); 302 btnKeepTheirVisibleState.setName("button.keeptheirvisiblestate"); 303 add(btnKeepTheirVisibleState, gc); 304 305 gc.gridx = 5; 306 gc.gridy = 5; 307 gc.fill = GridBagConstraints.BOTH; 308 gc.anchor = GridBagConstraints.CENTER; 309 gc.weightx = 0.33; 310 gc.weighty = 0.0; 311 add(lblTheirVisibleState = buildValueLabel("label.theirvisiblestate"), gc); 312 313 // --------------------------------------------------- 314 gc.gridx = 3; 315 gc.gridy = 6; 316 gc.fill = GridBagConstraints.NONE; 317 gc.anchor = GridBagConstraints.CENTER; 318 gc.weightx = 0.0; 319 gc.weighty = 0.0; 320 UndecideVisibleStateConflictAction actUndecideVisibleState = new UndecideVisibleStateConflictAction(); 321 model.addObserver(actUndecideVisibleState); 322 JButton btnUndecideVisibleState = new JButton(actUndecideVisibleState); 323 btnUndecideVisibleState.setName("button.undecidevisiblestate"); 324 add(btnUndecideVisibleState, gc); 325 } 326 327 protected void build() { 328 setLayout(new GridBagLayout()); 329 buildHeaderRow(); 330 buildCoordinateConflictRows(); 331 buildDeletedStateConflictRows(); 332 buildVisibleStateRows(); 238 333 } 239 334 … … 265 360 } 266 361 267 protected void updateCoordiates() { 362 public String visibleStateToString(Boolean visible) { 363 if (visible == null) 364 return tr("(none)"); 365 if (visible) 366 return tr("visible (on the server)"); 367 else 368 return tr("not visible (on the server)"); 369 } 370 371 public String visibleStateToStringMerged(Boolean visible) { 372 if (visible == null) 373 return tr("(none)"); 374 if (visible) 375 return tr("Keep a clone of the local version"); 376 else 377 return tr("Physically delete from local dataset"); 378 } 379 380 protected void updateCoordinates() { 268 381 lblMyCoordinates.setText(coordToString(model.getMyCoords())); 269 382 lblMergedCoordinates.setText(coordToString(model.getMergedCoords())); … … 320 433 } 321 434 435 protected void updateVisibleState() { 436 lblMyVisibleState.setText(visibleStateToString(model.getMyVisibleState())); 437 lblMergedVisibleState.setText(visibleStateToStringMerged(model.getMergedVisibleState())); 438 lblTheirVisibleState.setText(visibleStateToString(model.getTheirVisibleState())); 439 440 if (! model.hasVisibleStateConflict()) { 441 lblMyVisibleState.setBackground(BGCOLOR_NO_CONFLICT); 442 lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT); 443 lblTheirVisibleState.setBackground(BGCOLOR_NO_CONFLICT); 444 } else { 445 if (!model.isDecidedVisibleState()) { 446 lblMyVisibleState.setBackground(BGCOLOR_UNDECIDED); 447 lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT); 448 lblTheirVisibleState.setBackground(BGCOLOR_UNDECIDED); 449 } else { 450 lblMyVisibleState.setBackground( 451 model.isVisibleStateDecision(MergeDecisionType.KEEP_MINE) 452 ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT 453 ); 454 lblMergedVisibleState.setBackground(BGCOLOR_DECIDED); 455 lblTheirVisibleState.setBackground( 456 model.isVisibleStateDecision(MergeDecisionType.KEEP_THEIR) 457 ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT 458 ); 459 } 460 } 461 } 462 322 463 public void update(Observable o, Object arg) { 323 updateCoordi ates();464 updateCoordinates(); 324 465 updateDeletedState(); 466 updateVisibleState(); 325 467 } 326 468 … … 418 560 } 419 561 } 562 563 class KeepMyVisibleStateAction extends AbstractAction implements Observer { 564 public KeepMyVisibleStateAction() { 565 putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine")); 566 putValue(Action.SHORT_DESCRIPTION, tr("Keep my visible state")); 567 } 568 569 public void actionPerformed(ActionEvent e) { 570 if (confirmKeepMine()) { 571 model.decideVisibleStateConflict(MergeDecisionType.KEEP_MINE); 572 } 573 } 574 575 public void update(Observable o, Object arg) { 576 setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState()); 577 } 578 579 protected boolean confirmKeepMine() { 580 String [] options = { 581 tr("Yes, reset the id"), 582 tr("No, abort") 583 }; 584 int ret = JOptionPane.showOptionDialog( 585 null, 586 tr("<html>To keep your local version, JOSM<br>" 587 + "has to reset the id of {0} {1} to 0.<br>" 588 + "On the next upload the server will assign<br>" 589 + "it a new id.<br>" 590 + "Do yo agree?</html>", 591 OsmPrimitiveType.from(model.getMyPrimitive()).getLocalizedDisplayNamePlural(), 592 model.getMyPrimitive().id 593 ), 594 tr("Reset id to 0"), 595 JOptionPane.YES_NO_OPTION, 596 JOptionPane.QUESTION_MESSAGE, 597 null, 598 options, 599 options[1] 600 ); 601 return ret == JOptionPane.YES_OPTION; 602 } 603 } 604 605 class KeepTheirVisibleStateAction extends AbstractAction implements Observer { 606 public KeepTheirVisibleStateAction() { 607 putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir")); 608 putValue(Action.SHORT_DESCRIPTION, tr("Keep their visible state")); 609 } 610 611 public void actionPerformed(ActionEvent e) { 612 if (confirmKeepTheir()){ 613 model.decideVisibleStateConflict(MergeDecisionType.KEEP_THEIR); 614 } 615 } 616 617 public void update(Observable o, Object arg) { 618 setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState()); 619 } 620 621 protected boolean confirmKeepTheir() { 622 String [] options = { 623 tr("Yes, purge it"), 624 tr("No, abort") 625 }; 626 int ret = JOptionPane.showOptionDialog( 627 null, 628 tr("<html>JOSM will have to remove your local primitive with id {0}<br>" 629 + "from the dataset.<br>" 630 + "Do you agree?</html>", 631 model.getMyPrimitive().id 632 ), 633 tr("Remove from dataset"), 634 JOptionPane.YES_NO_OPTION, 635 JOptionPane.QUESTION_MESSAGE, 636 null, 637 options, 638 options[1] 639 ); 640 return ret == JOptionPane.YES_OPTION; 641 } 642 } 643 644 class UndecideVisibleStateConflictAction extends AbstractAction implements Observer { 645 public UndecideVisibleStateConflictAction() { 646 putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide")); 647 putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between visible state")); 648 } 649 650 public void actionPerformed(ActionEvent e) { 651 model.decideVisibleStateConflict(MergeDecisionType.UNDECIDED); 652 } 653 654 public void update(Observable o, Object arg) { 655 setEnabled(model.hasVisibleStateConflict() && model.isDecidedVisibleState()); 656 } 657 } 420 658 } -
trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java
r1677 r1690 23 23 import org.openstreetmap.josm.command.Command; 24 24 import org.openstreetmap.josm.gui.conflict.ConflictResolver; 25 import org.openstreetmap.josm.gui.conflict.properties.OperationCancelledException; 25 26 import org.openstreetmap.josm.tools.ImageProvider; 26 27 … … 190 191 return; 191 192 } 192 Command cmd = resolver.buildResolveCommand(); 193 Main.main.undoRedo.add(cmd); 193 try { 194 Command cmd = resolver.buildResolveCommand(); 195 Main.main.undoRedo.add(cmd); 196 } catch(OperationCancelledException e) { 197 // do nothing. Exception already reported 198 } 194 199 setVisible(false); 195 200 } -
trunk/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
r1670 r1690 527 527 final MergeVisitor visitor = new MergeVisitor(Main.main 528 528 .editLayer().data, dataSet); 529 for (final OsmPrimitive osm : dataSet.allPrimitives()) { 530 osm.visit(visitor); 531 } 532 visitor.fixReferences(); 529 visitor.merge(); 533 530 534 531 // copy the merged layer's data source info … … 538 535 Main.main.editLayer().fireDataChange(); 539 536 540 if (visitor. conflicts.isEmpty())537 if (visitor.getConflicts().isEmpty()) 541 538 return; 542 539 final ConflictDialog dlg = Main.map.conflictDialog; 543 dlg.add(visitor. conflicts);540 dlg.add(visitor.getConflicts()); 544 541 JOptionPane.showMessageDialog(Main.parent, 545 542 tr("There were conflicts during import.")); -
trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
r1677 r1690 245 245 public void mergeFrom(final DataSet from) { 246 246 final MergeVisitor visitor = new MergeVisitor(data,from); 247 for (final OsmPrimitive osm : from.allPrimitives()) { 248 osm.visit(visitor); 249 } 250 visitor.fixReferences(); 247 visitor.merge(); 251 248 252 249 Area a = data.getDataSourceArea(); … … 274 271 Main.map.mapView.repaint(); 275 272 276 if (visitor. conflicts.isEmpty())273 if (visitor.getConflicts().isEmpty()) 277 274 return; 278 275 final ConflictDialog dlg = Main.map.conflictDialog; 279 dlg.add(visitor. conflicts);280 JOptionPane.showMessageDialog(Main.parent,tr("There were {0} conflicts during import.", visitor. conflicts.size()));276 dlg.add(visitor.getConflicts()); 277 JOptionPane.showMessageDialog(Main.parent,tr("There were {0} conflicts during import.", visitor.getConflicts().size())); 281 278 if (!dlg.isVisible()) { 282 279 dlg.action.actionPerformed(new ActionEvent(this, 0, "")); -
trunk/src/org/openstreetmap/josm/io/OsmApi.java
r1688 r1690 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.EventQueue; 6 7 import java.io.BufferedReader; 7 8 import java.io.BufferedWriter; … … 323 324 } 324 325 notifyStatusMessage(tr("Uploading...")); 326 setAutoProgressIndication(true); 325 327 326 328 String diff = duv.getDocument(); … … 330 332 } catch(Exception e) { 331 333 throw new OsmTransferException(e); 334 } finally { 335 setAutoProgressIndication(false); 332 336 } 333 337 … … 481 485 Main.pleaseWaitDlg.progress.setValue(current + delta); 482 486 } 487 488 489 protected void setAutoProgressIndication(final boolean enabled) { 490 EventQueue.invokeLater( 491 new Runnable() { 492 public void run() { 493 Main.pleaseWaitDlg.setIndeterminate(enabled); 494 } 495 } 496 ); 497 } 483 498 } -
trunk/src/org/openstreetmap/josm/io/OsmReader.java
r1677 r1690 9 9 import java.util.ArrayList; 10 10 import java.util.Collection; 11 import java.util.Date; 11 12 import java.util.HashMap; 13 import java.util.HashSet; 12 14 import java.util.LinkedList; 13 15 import java.util.Map; 16 import java.util.Set; 14 17 import java.util.Map.Entry; 15 18 … … 29 32 import org.openstreetmap.josm.data.osm.Way; 30 33 import org.openstreetmap.josm.data.osm.visitor.AddVisitor; 31 import org.openstreetmap.josm.data.osm.visitor.Visitor;32 34 import org.openstreetmap.josm.gui.PleaseWaitDialog; 33 35 import org.openstreetmap.josm.tools.DateUtils; … … 49 51 public class OsmReader { 50 52 51 // static long tagsN = 0; 52 // static long nodesN = 0; 53 // static long waysN = 0; 54 // static long relationsN = 0; 55 // static long membersN = 0; 56 57 static InputStream currSource; 58 59 /** 60 * This is used as (readonly) source for finding missing references when not transferred in the 61 * file. 62 */ 63 private DataSet references; 64 65 /** 66 * The dataset to add parsed objects to. 67 */ 68 private DataSet ds = new DataSet(); 69 public DataSet getDs() { return ds; } 70 71 /** 72 * Record warnings. If there were any data inconsistencies, append 73 * a newline-terminated string. 74 */ 75 private String parseNotes = new String(); 76 private int parseNotesCount = 0; 77 public String getParseNotes() { 78 return parseNotes; 79 } 80 81 /** 82 * The visitor to use to add the data to the set. 83 */ 84 private AddVisitor adder = new AddVisitor(ds); 85 86 /** 87 * All read nodes after phase 1. 88 */ 89 private Map<Long, Node> nodes = new HashMap<Long, Node>(); 90 91 // TODO: What the hack? Is this really from me? Please, clean this up! 92 private static class OsmPrimitiveData extends OsmPrimitive { 93 @Override public void visit(Visitor visitor) {} 94 public int compareTo(OsmPrimitive o) {return 0;} 95 96 public void copyTo(OsmPrimitive osm) { 97 osm.id = id; 98 osm.keys = keys; 99 osm.modified = modified; 100 osm.selected = selected; 101 osm.deleted = deleted; 102 osm.setTimestamp(getTimestamp()); 103 osm.user = user; 104 osm.visible = visible; 105 osm.version = version; 106 osm.mappaintStyle = null; 107 } 108 } 109 110 /** 111 * Used as a temporary storage for relation members, before they 112 * are resolved into pointers to real objects. 113 */ 114 private static class RelationMemberData { 115 public String type; 116 public long id; 117 public RelationMember relationMember; 118 } 119 120 /** 121 * Data structure for the remaining way objects 122 */ 123 private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>(); 124 125 /** 126 * Data structure for relation objects 127 */ 128 private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>(); 129 130 private class Parser extends DefaultHandler { 131 /** 132 * The current osm primitive to be read. 133 */ 134 private OsmPrimitive current; 135 private String generator; 136 private Map<String, String> keys = new HashMap<String, String>(); 137 // int n = 0; 138 139 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 140 try { 141 // if(n%100000 == 0) { 142 // try { 143 // FileInputStream fis = (FileInputStream)currSource; 144 // FileChannel channel = fis.getChannel(); 145 // double perc = (((double)channel.position()) / ((double)channel.size()) * 100.0); 146 // System.out.format(" " + (int)perc + "%%"); 147 // } 148 // catch(java.lang.ClassCastException cce) { 149 // } 150 // catch(IOException e) { 151 // System.out.format("Error reading file position " + e); 152 // } 153 // } 154 // n++; 155 156 if (qName.equals("osm")) { 157 if (atts == null) 158 throw new SAXException(tr("Unknown version")); 159 String v = atts.getValue("version"); 160 if (v == null) 161 throw new SAXException(tr("Version number missing from OSM data")); 162 if (!(v.equals("0.5") || v.equals("0.6"))) 163 throw new SAXException(tr("Unknown version: {0}", v)); 164 // save generator attribute for later use when creating DataSource objects 165 generator = atts.getValue("generator"); 166 ds.version = v; 167 168 } else if (qName.equals("bounds")) { 169 // new style bounds. 170 String minlon = atts.getValue("minlon"); 171 String minlat = atts.getValue("minlat"); 172 String maxlon = atts.getValue("maxlon"); 173 String maxlat = atts.getValue("maxlat"); 174 String origin = atts.getValue("origin"); 175 if (minlon != null && maxlon != null && minlat != null && maxlat != null) { 176 if (origin == null) origin = generator; 177 Bounds bounds = new Bounds( 178 new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)), 179 new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon))); 180 DataSource src = new DataSource(bounds, origin); 181 ds.dataSources.add(src); 182 } 53 /** 54 * This is used as (readonly) source for finding missing references when not transferred in the 55 * file. 56 */ 57 private DataSet references; 58 59 /** 60 * The dataset to add parsed objects to. 61 */ 62 private DataSet ds = new DataSet(); 63 public DataSet getDs() { return ds; } 64 65 /** 66 * Record warnings. If there were any data inconsistencies, append 67 * a newline-terminated string. 68 */ 69 private String parseNotes = new String(); 70 private int parseNotesCount = 0; 71 public String getParseNotes() { 72 return parseNotes; 73 } 74 75 /** the list of ids of skipped {@see Way}s, i.e. ways which referred to nodes 76 * not included in the parsed data 77 */ 78 private Set<Long> skippedWayIds = new HashSet<Long>(); 79 80 /** 81 * The visitor to use to add the data to the set. 82 */ 83 private AddVisitor adder = new AddVisitor(ds); 84 85 /** 86 * All read nodes after phase 1. 87 */ 88 private Map<Long, Node> nodes = new HashMap<Long, Node>(); 89 90 91 private static class OsmPrimitiveData { 92 public long id = 0; 93 public Map<String,String> keys = new HashMap<String, String>(); 94 public boolean modified = false; 95 public boolean selected = false; 96 public boolean deleted = false; 97 public Date timestamp = new Date(); 98 public User user = null; 99 public boolean visible = true; 100 public int version = -1; 101 public LatLon latlon = new LatLon(0,0); 102 103 public void copyTo(OsmPrimitive osm) { 104 osm.id = id; 105 osm.keys = keys; 106 osm.modified = modified; 107 osm.selected = selected; 108 osm.deleted = deleted; 109 osm.setTimestamp(timestamp); 110 osm.user = user; 111 osm.visible = visible; 112 osm.version = version; 113 osm.mappaintStyle = null; 114 } 115 116 public Node createNode() { 117 Node node = new Node(latlon); 118 copyTo(node); 119 return node; 120 } 121 122 public Way createWay() { 123 Way way = new Way(id); 124 copyTo(way); 125 return way; 126 } 127 128 public Relation createRelation() { 129 Relation rel = new Relation(id); 130 copyTo(rel); 131 return rel; 132 } 133 } 134 135 /** 136 * Used as a temporary storage for relation members, before they 137 * are resolved into pointers to real objects. 138 */ 139 private static class RelationMemberData { 140 public String type; 141 public long id; 142 public RelationMember relationMember; 143 } 144 145 /** 146 * Data structure for the remaining way objects 147 */ 148 private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>(); 149 150 /** 151 * Data structure for relation objects 152 */ 153 private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>(); 154 155 private class Parser extends DefaultHandler { 156 /** 157 * The current osm primitive to be read. 158 */ 159 private OsmPrimitiveData current; 160 private String generator; 161 162 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 163 try { 164 if (qName.equals("osm")) { 165 if (atts == null) 166 throw new SAXException(tr("Unknown version")); 167 String v = atts.getValue("version"); 168 if (v == null) 169 throw new SAXException(tr("Version number missing from OSM data")); 170 if (!(v.equals("0.5") || v.equals("0.6"))) 171 throw new SAXException(tr("Unknown version: {0}", v)); 172 // save generator attribute for later use when creating DataSource objects 173 generator = atts.getValue("generator"); 174 ds.version = v; 175 176 } else if (qName.equals("bounds")) { 177 // new style bounds. 178 String minlon = atts.getValue("minlon"); 179 String minlat = atts.getValue("minlat"); 180 String maxlon = atts.getValue("maxlon"); 181 String maxlat = atts.getValue("maxlat"); 182 String origin = atts.getValue("origin"); 183 if (minlon != null && maxlon != null && minlat != null && maxlat != null) { 184 if (origin == null) { 185 origin = generator; 186 } 187 Bounds bounds = new Bounds( 188 new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)), 189 new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon))); 190 DataSource src = new DataSource(bounds, origin); 191 ds.dataSources.add(src); 192 } 183 193 184 194 // ---- PARSING NODES AND WAYS ---- 185 195 186 } else if (qName.equals("node")) { 187 // nodesN++; 188 current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon"))); 189 readCommon(atts, current); 190 nodes.put(current.id, (Node)current); 191 } else if (qName.equals("way")) { 192 // waysN++; 193 current = new OsmPrimitiveData(); 194 readCommon(atts, current); 195 ways.put((OsmPrimitiveData)current, new ArrayList<Long>()); 196 } else if (qName.equals("nd")) { 197 Collection<Long> list = ways.get(current); 198 if (list == null) 199 throw new SAXException(tr("Found <nd> element in non-way.")); 200 long id = getLong(atts, "ref"); 201 if (id == 0) 202 throw new SAXException(tr("<nd> has zero ref")); 203 list.add(id); 196 } else if (qName.equals("node")) { 197 current = new OsmPrimitiveData(); 198 current.latlon = new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")); 199 readCommon(atts, current); 200 } else if (qName.equals("way")) { 201 current = new OsmPrimitiveData(); 202 readCommon(atts, current); 203 ways.put(current, new ArrayList<Long>()); 204 } else if (qName.equals("nd")) { 205 Collection<Long> list = ways.get(current); 206 if (list == null) 207 throw new SAXException(tr("Found <nd> element in non-way.")); 208 long id = getLong(atts, "ref"); 209 if (id == 0) 210 throw new SAXException(tr("<nd> has zero ref")); 211 list.add(id); 204 212 205 213 // ---- PARSING RELATIONS ---- 206 214 207 } else if (qName.equals("relation")) { 208 current = new OsmPrimitiveData(); 209 readCommon(atts, current); 210 relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>()); 211 } else if (qName.equals("member")) { 212 Collection<RelationMemberData> list = relations.get(current); 213 if (list == null) 214 throw new SAXException(tr("Found <member> element in non-relation.")); 215 RelationMemberData emd = new RelationMemberData(); 216 emd.relationMember = new RelationMember(); 217 String value = atts.getValue("ref"); 218 if (value == null) { 219 throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id)); 220 } 221 try { 222 emd.id = Long.parseLong(value); 223 } catch(NumberFormatException e) { 224 throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value)); 225 } 226 value = atts.getValue("type"); 227 if (value == null) { 228 throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id))); 229 } 230 if (! (value.equals("way") || value.equals("node") || value.equals("relation"))) { 231 throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value)); 232 } 233 emd.type= value; 234 value = atts.getValue("role"); 235 emd.relationMember.role = value; 236 237 if (emd.id == 0) 238 throw new SAXException(tr("Incomplete <member> specification with ref=0")); 239 240 list.add(emd); 215 } else if (qName.equals("relation")) { 216 current = new OsmPrimitiveData(); 217 readCommon(atts, current); 218 relations.put(current, new LinkedList<RelationMemberData>()); 219 } else if (qName.equals("member")) { 220 Collection<RelationMemberData> list = relations.get(current); 221 if (list == null) 222 throw new SAXException(tr("Found <member> element in non-relation.")); 223 RelationMemberData emd = new RelationMemberData(); 224 emd.relationMember = new RelationMember(); 225 String value = atts.getValue("ref"); 226 if (value == null) 227 throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id)); 228 try { 229 emd.id = Long.parseLong(value); 230 } catch(NumberFormatException e) { 231 throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value)); 232 } 233 value = atts.getValue("type"); 234 if (value == null) 235 throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id))); 236 if (! (value.equals("way") || value.equals("node") || value.equals("relation"))) 237 throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value)); 238 emd.type= value; 239 value = atts.getValue("role"); 240 emd.relationMember.role = value; 241 242 if (emd.id == 0) 243 throw new SAXException(tr("Incomplete <member> specification with ref=0")); 244 245 list.add(emd); 241 246 242 247 // ---- PARSING TAGS (applicable to all objects) ---- 243 248 244 } else if (qName.equals("tag")) { 245 // tagsN++; 246 String key = atts.getValue("k"); 247 String internedKey = keys.get(key); 248 if (internedKey == null) { 249 internedKey = key; 250 keys.put(key, key); 251 } 252 current.put(internedKey, atts.getValue("v")); 253 } 254 } catch (NumberFormatException x) { 255 x.printStackTrace(); // SAXException does not chain correctly 256 throw new SAXException(x.getMessage(), x); 257 } catch (NullPointerException x) { 258 x.printStackTrace(); // SAXException does not chain correctly 259 throw new SAXException(tr("NullPointerException, possibly some missing tags."), x); 260 } 261 } 262 263 private double getDouble(Attributes atts, String value) { 264 return Double.parseDouble(atts.getValue(value)); 265 } 266 } 267 268 /** 269 * Read out the common attributes from atts and put them into this.current. 270 */ 271 void readCommon(Attributes atts, OsmPrimitive current) throws SAXException { 272 current.id = getLong(atts, "id"); 273 if (current.id == 0) 274 throw new SAXException(tr("Illegal object with id=0")); 275 276 String time = atts.getValue("timestamp"); 277 if (time != null && time.length() != 0) { 278 current.setTimestamp(DateUtils.fromString(time)); 279 } 280 281 // user attribute added in 0.4 API 282 String user = atts.getValue("user"); 283 if (user != null) { 284 // do not store literally; get object reference for string 285 current.user = User.get(user); 286 } 287 288 // uid attribute added in 0.6 API 289 String uid = atts.getValue("uid"); 290 if (uid != null) { 291 if (current.user != null) { 292 current.user.uid = uid; 293 } 294 } 295 296 // visible attribute added in 0.4 API 297 String visible = atts.getValue("visible"); 298 if (visible != null) { 299 current.visible = Boolean.parseBoolean(visible); 300 } 301 302 String version = atts.getValue("version"); 303 current.version = 0; 304 if (version != null) { 305 try { 306 current.version = Integer.parseInt(version); 307 } catch(NumberFormatException e) { 308 throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version)); 309 } 310 } else { 311 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6 312 // 313 if (current.id > 0 && ds.version != null && ds.version.equals("0.6")) { 314 throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id))); 315 } 316 } 317 318 String action = atts.getValue("action"); 319 if (action == null) 320 return; 321 if (action.equals("delete")) 322 current.delete(true); 323 else if (action.startsWith("modify")) 324 current.modified = true; 325 } 326 private long getLong(Attributes atts, String value) throws SAXException { 327 String s = atts.getValue(value); 328 if (s == null) 329 throw new SAXException(tr("Missing required attribute \"{0}\".",value)); 330 return Long.parseLong(s); 331 } 332 333 private Node findNode(long id) { 334 Node n = nodes.get(id); 335 if (n != null) 336 return n; 337 for (Node node : references.nodes) 338 if (node.id == id) 339 return node; 340 // TODO: This has to be changed to support multiple layers. 341 for (Node node : Main.ds.nodes) 342 if (node.id == id) 343 return new Node(node); 344 return null; 345 } 346 347 private void createWays() { 348 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) { 349 Way w = new Way(); 350 boolean failed = false; 351 for (long id : e.getValue()) { 352 Node n = findNode(id); 353 if (n == null) { 354 /* don't report ALL of them, just a few */ 355 if (parseNotesCount++ < 6) { 356 parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id); 357 } else if (parseNotesCount == 6) { 358 parseNotes += "...\n"; 359 } 360 failed = true; 361 break; 362 } 363 w.nodes.add(n); 364 } 365 if (failed) continue; 366 e.getKey().copyTo(w); 367 adder.visit(w); 368 } 369 370 } 371 372 /** 373 * Return the Way object with the given id, or null if it doesn't 374 * exist yet. This method only looks at ways stored in the data set. 375 * 376 * @param id 377 * @return way object or null 378 */ 379 private Way findWay(long id) { 380 for (Way wy : Main.ds.ways) 381 if (wy.id == id) 382 return wy; 383 return null; 384 } 385 386 /** 387 * Return the Relation object with the given id, or null if it doesn't 388 * exist yet. This method only looks at relations stored in the data set. 389 * 390 * @param id 391 * @return relation object or null 392 */ 393 private Relation findRelation(long id) { 394 for (Relation e : ds.relations) 395 if (e.id == id) 396 return e; 397 for (Relation e : Main.ds.relations) 398 if (e.id == id) 399 return e; 400 return null; 401 } 402 403 /** 404 * Create relations. This is slightly different than n/s/w because 405 * unlike other objects, relations may reference other relations; it 406 * is not guaranteed that a referenced relation will have been created 407 * before it is referenced. So we have to create all relations first, 408 * and populate them later. 409 */ 410 private void createRelations() { 411 412 // pass 1 - create all relations 413 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) { 414 Relation en = new Relation(); 415 e.getKey().copyTo(en); 416 adder.visit(en); 417 } 418 419 // Cache the ways here for much better search performance 420 HashMap<Long, Way> hm = new HashMap<Long, Way>(10000); 421 for (Way wy : ds.ways) 249 } else if (qName.equals("tag")) { 250 String key = atts.getValue("k"); 251 String value = atts.getValue("v"); 252 current.keys.put(key,value); 253 } 254 } catch (NumberFormatException x) { 255 x.printStackTrace(); // SAXException does not chain correctly 256 throw new SAXException(x.getMessage(), x); 257 } catch (NullPointerException x) { 258 x.printStackTrace(); // SAXException does not chain correctly 259 throw new SAXException(tr("NullPointerException, possibly some missing tags."), x); 260 } 261 } 262 263 @Override 264 public void endElement(String uri, String localName, String qName) throws SAXException { 265 if (qName.equals("node")) { 266 nodes.put(current.id, current.createNode()); 267 } 268 } 269 270 private double getDouble(Attributes atts, String value) { 271 return Double.parseDouble(atts.getValue(value)); 272 } 273 } 274 275 /** 276 * Read out the common attributes from atts and put them into this.current. 277 */ 278 void readCommon(Attributes atts, OsmPrimitiveData current) throws SAXException { 279 current.id = getLong(atts, "id"); 280 if (current.id == 0) 281 throw new SAXException(tr("Illegal object with id=0")); 282 283 String time = atts.getValue("timestamp"); 284 if (time != null && time.length() != 0) { 285 current.timestamp = DateUtils.fromString(time); 286 } 287 288 // user attribute added in 0.4 API 289 String user = atts.getValue("user"); 290 if (user != null) { 291 // do not store literally; get object reference for string 292 current.user = User.get(user); 293 } 294 295 // uid attribute added in 0.6 API 296 String uid = atts.getValue("uid"); 297 if (uid != null) { 298 if (current.user != null) { 299 current.user.uid = uid; 300 } 301 } 302 303 // visible attribute added in 0.4 API 304 String visible = atts.getValue("visible"); 305 if (visible != null) { 306 current.visible = Boolean.parseBoolean(visible); 307 } 308 309 String version = atts.getValue("version"); 310 current.version = 0; 311 if (version != null) { 312 try { 313 current.version = Integer.parseInt(version); 314 } catch(NumberFormatException e) { 315 throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version)); 316 } 317 } else { 318 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6 319 // 320 if (current.id > 0 && ds.version != null && ds.version.equals("0.6")) 321 throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id))); 322 } 323 324 String action = atts.getValue("action"); 325 if (action == null) 326 return; 327 if (action.equals("delete")) { 328 current.deleted = true; 329 } else if (action.startsWith("modify")) { 330 current.modified = true; 331 } 332 } 333 private long getLong(Attributes atts, String value) throws SAXException { 334 String s = atts.getValue(value); 335 if (s == null) 336 throw new SAXException(tr("Missing required attribute \"{0}\".",value)); 337 return Long.parseLong(s); 338 } 339 340 private Node findNode(long id) { 341 Node n = nodes.get(id); 342 if (n != null) 343 return n; 344 for (Node node : references.nodes) 345 if (node.id == id) 346 return node; 347 // TODO: This has to be changed to support multiple layers. 348 for (Node node : Main.ds.nodes) 349 if (node.id == id) 350 return new Node(node); 351 return null; 352 } 353 354 protected void createWays() { 355 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) { 356 Way w = new Way(); 357 boolean failed = false; 358 for (long id : e.getValue()) { 359 Node n = findNode(id); 360 if (n == null) { 361 /* don't report ALL of them, just a few */ 362 if (parseNotesCount++ < 6) { 363 parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id); 364 } else if (parseNotesCount == 6) { 365 parseNotes += "...\n"; 366 } 367 failed = true; 368 break; 369 } 370 w.nodes.add(n); 371 } 372 if (failed) { 373 skippedWayIds.add(e.getKey().id); 374 continue; 375 } 376 e.getKey().copyTo(w); 377 adder.visit(w); 378 } 379 380 } 381 382 /** 383 * Return the Way object with the given id, or null if it doesn't 384 * exist yet. This method only looks at ways stored in the data set. 385 * 386 * @param id 387 * @return way object or null 388 */ 389 private Way findWay(long id) { 390 for (Way wy : Main.ds.ways) 391 if (wy.id == id) 392 return wy; 393 return null; 394 } 395 396 /** 397 * Return the Relation object with the given id, or null if it doesn't 398 * exist yet. This method only looks at relations stored in the data set. 399 * 400 * @param id 401 * @return relation object or null 402 */ 403 private Relation findRelation(long id) { 404 for (Relation e : ds.relations) 405 if (e.id == id) 406 return e; 407 for (Relation e : Main.ds.relations) 408 if (e.id == id) 409 return e; 410 return null; 411 } 412 413 /** 414 * Create relations. This is slightly different than n/s/w because 415 * unlike other objects, relations may reference other relations; it 416 * is not guaranteed that a referenced relation will have been created 417 * before it is referenced. So we have to create all relations first, 418 * and populate them later. 419 */ 420 private void createRelations() { 421 422 // pass 1 - create all relations 423 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) { 424 Relation en = new Relation(); 425 e.getKey().copyTo(en); 426 adder.visit(en); 427 } 428 429 // Cache the ways here for much better search performance 430 HashMap<Long, Way> hm = new HashMap<Long, Way>(10000); 431 for (Way wy : ds.ways) { 422 432 hm.put(wy.id, wy); 423 424 // pass 2 - sort out members 425 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) { 426 Relation en = findRelation(e.getKey().id); 427 if (en == null) throw new Error("Failed to create relation " + e.getKey().id); 428 429 for (RelationMemberData emd : e.getValue()) { 430 RelationMember em = emd.relationMember; 431 if (emd.type.equals("node")) { 432 em.member = findNode(emd.id); 433 if (em.member == null) { 434 em.member = new Node(emd.id); 435 adder.visit((Node)em.member); 436 } 437 } else if (emd.type.equals("way")) { 438 em.member = hm.get(emd.id); 439 if (em.member == null) 440 em.member = findWay(emd.id); 441 if (em.member == null) { 442 em.member = new Way(emd.id); 443 adder.visit((Way)em.member); 444 } 445 } else if (emd.type.equals("relation")) { 446 em.member = findRelation(emd.id); 447 if (em.member == null) { 448 em.member = new Relation(emd.id); 449 adder.visit((Relation)em.member); 450 } 451 } else { 452 // this is an error. 453 } 454 en.members.add(em); 455 } 456 } 457 hm = null; 458 } 459 460 /** 461 * Parse the given input source and return the dataset. 462 * @param ref The dataset that is search in for references first. If 463 * the Reference is not found here, Main.ds is searched and a copy of the 464 * element found there is returned. 465 */ 466 public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException { 467 return parseDataSetOsm(source, ref, pleaseWaitDlg).ds; 468 } 469 470 public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException { 471 OsmReader osm = new OsmReader(); 472 osm.references = ref == null ? new DataSet() : ref; 473 474 currSource = source; 475 476 // phase 1: Parse nodes and read in raw ways 477 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8")); 478 try { 479 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser()); 480 } catch (ParserConfigurationException e1) { 481 e1.printStackTrace(); // broken SAXException chaining 482 throw new SAXException(e1); 483 } 484 485 Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data...")); 486 Main.pleaseWaitDlg.setIndeterminate(true); 487 488 // System.out.println("Parser finished: Tags " + tagsN + " Nodes " + nodesN + " Ways " + waysN + 489 // " Relations " + relationsN + " Members " + membersN); 490 491 for (Node n : osm.nodes.values()) 492 osm.adder.visit(n); 493 494 try { 495 osm.createWays(); 496 osm.createRelations(); 497 } catch (NumberFormatException e) { 498 e.printStackTrace(); 499 throw new SAXException(tr("Ill-formed node id")); 500 } 501 502 // clear all negative ids (new to this file) 503 for (OsmPrimitive o : osm.ds.allPrimitives()) 504 if (o.id < 0) 505 o.id = 0; 506 507 // System.out.println("Data loaded!"); 508 Main.pleaseWaitDlg.setIndeterminate(false); 509 Main.pleaseWaitDlg.progress.setValue(0); 510 511 return osm; 512 } 433 } 434 435 // pass 2 - sort out members 436 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) { 437 Relation en = findRelation(e.getKey().id); 438 if (en == null) throw new Error("Failed to create relation " + e.getKey().id); 439 440 for (RelationMemberData emd : e.getValue()) { 441 RelationMember em = emd.relationMember; 442 if (emd.type.equals("node")) { 443 em.member = findNode(emd.id); 444 if (em.member == null) { 445 em.member = new Node(emd.id); 446 adder.visit((Node)em.member); 447 } 448 } else if (emd.type.equals("way")) { 449 em.member = hm.get(emd.id); 450 if (em.member == null) { 451 em.member = findWay(emd.id); 452 } 453 if (em.member == null) { 454 em.member = new Way(emd.id); 455 adder.visit((Way)em.member); 456 } 457 } else if (emd.type.equals("relation")) { 458 em.member = findRelation(emd.id); 459 if (em.member == null) { 460 em.member = new Relation(emd.id); 461 adder.visit((Relation)em.member); 462 } 463 } else { 464 // this is an error. 465 } 466 en.members.add(em); 467 } 468 } 469 hm = null; 470 } 471 472 /** 473 * Parse the given input source and return the dataset. 474 * @param ref The dataset that is search in for references first. If 475 * the Reference is not found here, Main.ds is searched and a copy of the 476 * element found there is returned. 477 */ 478 public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException { 479 return parseDataSetOsm(source, ref, pleaseWaitDlg).ds; 480 } 481 482 public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException { 483 OsmReader osm = new OsmReader(); 484 osm.references = ref == null ? new DataSet() : ref; 485 486 // phase 1: Parse nodes and read in raw ways 487 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8")); 488 try { 489 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser()); 490 } catch (ParserConfigurationException e1) { 491 e1.printStackTrace(); // broken SAXException chaining 492 throw new SAXException(e1); 493 } 494 495 Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data...")); 496 Main.pleaseWaitDlg.setIndeterminate(true); 497 498 for (Node n : osm.nodes.values()) { 499 osm.adder.visit(n); 500 } 501 502 try { 503 osm.createWays(); 504 osm.createRelations(); 505 } catch (NumberFormatException e) { 506 e.printStackTrace(); 507 throw new SAXException(tr("Ill-formed node id")); 508 } 509 510 // clear all negative ids (new to this file) 511 for (OsmPrimitive o : osm.ds.allPrimitives()) 512 if (o.id < 0) { 513 o.id = 0; 514 } 515 516 Main.pleaseWaitDlg.setIndeterminate(false); 517 Main.pleaseWaitDlg.progress.setValue(0); 518 519 return osm; 520 } 521 522 /** 523 * replies a set of ids of skipped {@see Way}s, i.e. ways which were included in the downloaded 524 * data but which referred to nodes <strong>not</strong> available in the downloaded data 525 * 526 * @return the set of ids 527 */ 528 public Set<Long> getSkippedWayIds() { 529 return skippedWayIds; 530 } 513 531 }
Note:
See TracChangeset
for help on using the changeset viewer.