001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.history; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.HashSet; 007import java.util.Set; 008 009import javax.swing.JTable; 010import javax.swing.table.TableModel; 011 012import org.openstreetmap.josm.Main; 013import org.openstreetmap.josm.data.UserIdentityManager; 014import org.openstreetmap.josm.data.osm.DataSet; 015import org.openstreetmap.josm.data.osm.Node; 016import org.openstreetmap.josm.data.osm.OsmPrimitive; 017import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 018import org.openstreetmap.josm.data.osm.Relation; 019import org.openstreetmap.josm.data.osm.RelationMember; 020import org.openstreetmap.josm.data.osm.RelationMemberData; 021import org.openstreetmap.josm.data.osm.User; 022import org.openstreetmap.josm.data.osm.UserInfo; 023import org.openstreetmap.josm.data.osm.Way; 024import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 025import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 026import org.openstreetmap.josm.data.osm.event.DataSetListener; 027import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 028import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 029import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 030import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 031import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 032import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 033import org.openstreetmap.josm.data.osm.history.History; 034import org.openstreetmap.josm.data.osm.history.HistoryNode; 035import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 036import org.openstreetmap.josm.data.osm.history.HistoryRelation; 037import org.openstreetmap.josm.data.osm.history.HistoryWay; 038import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor; 039import org.openstreetmap.josm.gui.MainApplication; 040import org.openstreetmap.josm.gui.layer.Layer; 041import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 042import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 043import org.openstreetmap.josm.gui.layer.OsmDataLayer; 044import org.openstreetmap.josm.gui.util.ChangeNotifier; 045import org.openstreetmap.josm.tools.CheckParameterUtil; 046import org.openstreetmap.josm.tools.Logging; 047 048/** 049 * This is the model used by the history browser. 050 * 051 * The model state consists of the following elements: 052 * <ul> 053 * <li>the {@link History} of a specific {@link OsmPrimitive}</li> 054 * <li>a dedicated version in this {@link History} called the {@link PointInTimeType#REFERENCE_POINT_IN_TIME}</li> 055 * <li>another version in this {@link History} called the {@link PointInTimeType#CURRENT_POINT_IN_TIME}</li> 056 * </ul> 057 * {@link HistoryBrowser} always compares the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} with the 058 * {@link PointInTimeType#CURRENT_POINT_IN_TIME}. 059 060 * This model provides various {@link TableModel}s for {@link JTable}s used in {@link HistoryBrowser}, for 061 * instance: 062 * <ul> 063 * <li>{@link #getTagTableModel(PointInTimeType)} replies a {@link TableModel} for the tags of either of 064 * the two selected versions</li> 065 * <li>{@link #getNodeListTableModel(PointInTimeType)} replies a {@link TableModel} for the list of nodes of 066 * the two selected versions (if the current history provides information about a {@link Way}</li> 067 * <li> {@link #getRelationMemberTableModel(PointInTimeType)} replies a {@link TableModel} for the list of relation 068 * members of the two selected versions (if the current history provides information about a {@link Relation}</li> 069 * </ul> 070 * 071 * @see HistoryBrowser 072 */ 073public class HistoryBrowserModel extends ChangeNotifier implements ActiveLayerChangeListener, DataSetListener { 074 /** the history of an OsmPrimitive */ 075 private History history; 076 private HistoryOsmPrimitive reference; 077 private HistoryOsmPrimitive current; 078 /** 079 * latest isn't a reference of history. It's a clone of the currently edited 080 * {@link OsmPrimitive} in the current edit layer. 081 */ 082 private HistoryOsmPrimitive latest; 083 084 private final VersionTableModel versionTableModel; 085 private final TagTableModel currentTagTableModel; 086 private final TagTableModel referenceTagTableModel; 087 private final DiffTableModel currentRelationMemberTableModel; 088 private final DiffTableModel referenceRelationMemberTableModel; 089 private final DiffTableModel referenceNodeListTableModel; 090 private final DiffTableModel currentNodeListTableModel; 091 092 /** 093 * constructor 094 */ 095 public HistoryBrowserModel() { 096 versionTableModel = new VersionTableModel(this); 097 currentTagTableModel = new TagTableModel(this, PointInTimeType.CURRENT_POINT_IN_TIME); 098 referenceTagTableModel = new TagTableModel(this, PointInTimeType.REFERENCE_POINT_IN_TIME); 099 referenceNodeListTableModel = new DiffTableModel(); 100 currentNodeListTableModel = new DiffTableModel(); 101 currentRelationMemberTableModel = new DiffTableModel(); 102 referenceRelationMemberTableModel = new DiffTableModel(); 103 104 if (Main.main != null) { 105 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 106 if (ds != null) { 107 ds.addDataSetListener(this); 108 } 109 } 110 MainApplication.getLayerManager().addActiveLayerChangeListener(this); 111 } 112 113 /** 114 * Creates a new history browser model for a given history. 115 * 116 * @param history the history. Must not be null. 117 * @throws IllegalArgumentException if history is null 118 */ 119 public HistoryBrowserModel(History history) { 120 this(); 121 CheckParameterUtil.ensureParameterNotNull(history, "history"); 122 setHistory(history); 123 } 124 125 /** 126 * replies the history managed by this model 127 * @return the history 128 */ 129 public History getHistory() { 130 return history; 131 } 132 133 private boolean canShowAsLatest(OsmPrimitive primitive) { 134 if (primitive == null) 135 return false; 136 if (primitive.isNew() || !primitive.isUsable()) 137 return false; 138 139 //try creating a history primitive. if that fails, the primitive cannot be used. 140 try { 141 HistoryOsmPrimitive.forOsmPrimitive(primitive); 142 } catch (IllegalArgumentException ign) { 143 Logging.trace(ign); 144 return false; 145 } 146 147 if (history == null) 148 return false; 149 // only show latest of the same version if it is modified 150 if (history.getByVersion(primitive.getVersion()) != null) 151 return primitive.isModified(); 152 153 // if latest version from history is higher than a non existing primitive version, 154 // that means this version has been redacted and the primitive cannot be used. 155 return history.getLatest().getVersion() <= primitive.getVersion(); 156 157 // latest has a higher version than one of the primitives 158 // in the history (probably because the history got out of sync 159 // with uploaded data) -> show the primitive as latest 160 } 161 162 /** 163 * sets the history to be managed by this model 164 * 165 * @param history the history 166 * 167 */ 168 public void setHistory(History history) { 169 this.history = history; 170 if (history.getNumVersions() > 0) { 171 HistoryOsmPrimitive newLatest = null; 172 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 173 if (ds != null) { 174 OsmPrimitive p = ds.getPrimitiveById(history.getId(), history.getType()); 175 if (canShowAsLatest(p)) { 176 newLatest = new HistoryPrimitiveBuilder().build(p); 177 } 178 } 179 if (newLatest == null) { 180 current = history.getLatest(); 181 int prevIndex = history.getNumVersions() - 2; 182 reference = prevIndex < 0 ? history.getEarliest() : history.get(prevIndex); 183 } else { 184 reference = history.getLatest(); 185 current = newLatest; 186 } 187 setLatest(newLatest); 188 } 189 initTagTableModels(); 190 fireModelChange(); 191 } 192 193 private void fireModelChange() { 194 initNodeListTableModels(); 195 initMemberListTableModels(); 196 fireStateChanged(); 197 versionTableModel.fireTableDataChanged(); 198 } 199 200 /** 201 * Replies the table model to be used in a {@link JTable} which 202 * shows the list of versions in this history. 203 * 204 * @return the table model 205 */ 206 public VersionTableModel getVersionTableModel() { 207 return versionTableModel; 208 } 209 210 private void initTagTableModels() { 211 currentTagTableModel.initKeyList(); 212 referenceTagTableModel.initKeyList(); 213 } 214 215 /** 216 * Should be called everytime either reference of current changes to update the diff. 217 * TODO: Maybe rename to reflect this? eg. updateNodeListTableModels 218 */ 219 private void initNodeListTableModels() { 220 if (current == null || current.getType() != OsmPrimitiveType.WAY 221 || reference == null || reference.getType() != OsmPrimitiveType.WAY) 222 return; 223 TwoColumnDiff diff = new TwoColumnDiff( 224 ((HistoryWay) reference).getNodes().toArray(), 225 ((HistoryWay) current).getNodes().toArray()); 226 referenceNodeListTableModel.setRows(diff.referenceDiff, diff.referenceReversed); 227 currentNodeListTableModel.setRows(diff.currentDiff, false); 228 } 229 230 private void initMemberListTableModels() { 231 if (current == null || current.getType() != OsmPrimitiveType.RELATION 232 || reference == null || reference.getType() != OsmPrimitiveType.RELATION) 233 return; 234 TwoColumnDiff diff = new TwoColumnDiff( 235 ((HistoryRelation) reference).getMembers().toArray(), 236 ((HistoryRelation) current).getMembers().toArray()); 237 referenceRelationMemberTableModel.setRows(diff.referenceDiff, diff.referenceReversed); 238 currentRelationMemberTableModel.setRows(diff.currentDiff, false); 239 } 240 241 /** 242 * Replies the tag table model for the respective point in time. 243 * 244 * @param pointInTimeType the type of the point in time (must not be null) 245 * @return the tag table model 246 * @throws IllegalArgumentException if pointInTimeType is null 247 */ 248 public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) { 249 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 250 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 251 return currentTagTableModel; 252 else // REFERENCE_POINT_IN_TIME 253 return referenceTagTableModel; 254 } 255 256 /** 257 * Replies the node list table model for the respective point in time. 258 * 259 * @param pointInTimeType the type of the point in time (must not be null) 260 * @return the node list table model 261 * @throws IllegalArgumentException if pointInTimeType is null 262 */ 263 public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) { 264 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 265 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 266 return currentNodeListTableModel; 267 else // REFERENCE_POINT_IN_TIME 268 return referenceNodeListTableModel; 269 } 270 271 /** 272 * Replies the relation member table model for the respective point in time. 273 * 274 * @param pointInTimeType the type of the point in time (must not be null) 275 * @return the relation member table model 276 * @throws IllegalArgumentException if pointInTimeType is null 277 */ 278 public DiffTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) { 279 CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType"); 280 if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 281 return currentRelationMemberTableModel; 282 else // REFERENCE_POINT_IN_TIME 283 return referenceRelationMemberTableModel; 284 } 285 286 /** 287 * Sets the {@link HistoryOsmPrimitive} which plays the role of a reference point 288 * in time (see {@link PointInTimeType}). 289 * 290 * @param reference the reference history primitive. Must not be null. 291 * @throws IllegalArgumentException if reference is null 292 * @throws IllegalStateException if this model isn't a assigned a history yet 293 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 294 * 295 * @see #setHistory(History) 296 * @see PointInTimeType 297 */ 298 public void setReferencePointInTime(HistoryOsmPrimitive reference) { 299 CheckParameterUtil.ensureParameterNotNull(reference, "reference"); 300 if (history == null) 301 throw new IllegalStateException(tr("History not initialized yet. Failed to set reference primitive.")); 302 if (reference.getId() != history.getId()) 303 throw new IllegalArgumentException( 304 tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", reference.getId(), history.getId())); 305 if (history.getByVersion(reference.getVersion()) == null) 306 throw new IllegalArgumentException( 307 tr("Failed to set reference. Reference version {0} not available in history.", reference.getVersion())); 308 309 this.reference = reference; 310 initTagTableModels(); 311 initNodeListTableModels(); 312 initMemberListTableModels(); 313 fireStateChanged(); 314 } 315 316 /** 317 * Sets the {@link HistoryOsmPrimitive} which plays the role of the current point 318 * in time (see {@link PointInTimeType}). 319 * 320 * @param current the reference history primitive. Must not be {@code null}. 321 * @throws IllegalArgumentException if reference is {@code null} 322 * @throws IllegalStateException if this model isn't a assigned a history yet 323 * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode 324 * 325 * @see #setHistory(History) 326 * @see PointInTimeType 327 */ 328 public void setCurrentPointInTime(HistoryOsmPrimitive current) { 329 CheckParameterUtil.ensureParameterNotNull(current, "current"); 330 if (history == null) 331 throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive.")); 332 if (current.getId() != history.getId()) 333 throw new IllegalArgumentException( 334 tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", current.getId(), history.getId())); 335 if (history.getByVersion(current.getVersion()) == null) 336 throw new IllegalArgumentException( 337 tr("Failed to set current primitive. Current version {0} not available in history.", current.getVersion())); 338 this.current = current; 339 initTagTableModels(); 340 initNodeListTableModels(); 341 initMemberListTableModels(); 342 fireStateChanged(); 343 } 344 345 /** 346 * Replies the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} 347 * 348 * @return the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} (may be null) 349 */ 350 public HistoryOsmPrimitive getCurrentPointInTime() { 351 return getPointInTime(PointInTimeType.CURRENT_POINT_IN_TIME); 352 } 353 354 /** 355 * Replies the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} 356 * 357 * @return the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} (may be null) 358 */ 359 public HistoryOsmPrimitive getReferencePointInTime() { 360 return getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME); 361 } 362 363 /** 364 * replies the history OSM primitive for a given point in time 365 * 366 * @param type the type of the point in time (must not be null) 367 * @return the respective primitive. Can be null. 368 * @throws IllegalArgumentException if type is null 369 */ 370 public HistoryOsmPrimitive getPointInTime(PointInTimeType type) { 371 CheckParameterUtil.ensureParameterNotNull(type, "type"); 372 if (type.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) 373 return current; 374 else if (type.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) 375 return reference; 376 377 // should not happen 378 return null; 379 } 380 381 /** 382 * Returns true if <code>primitive</code> is the latest primitive 383 * representing the version currently edited in the current data layer. 384 * 385 * @param primitive the primitive to check 386 * @return true if <code>primitive</code> is the latest primitive 387 */ 388 public boolean isLatest(HistoryOsmPrimitive primitive) { 389 return primitive != null && primitive == latest; 390 } 391 392 /** 393 * Sets the reference point in time to the given row. 394 * @param row row number 395 */ 396 public void setReferencePointInTime(int row) { 397 if (history == null) 398 return; 399 if (row == history.getNumVersions()) { 400 if (latest != null) { 401 setReferencePointInTime(latest); 402 } 403 return; 404 } 405 if (row < 0 || row > history.getNumVersions()) 406 return; 407 setReferencePointInTime(history.get(row)); 408 } 409 410 /** 411 * Sets the current point in time to the given row. 412 * @param row row number 413 */ 414 public void setCurrentPointInTime(int row) { 415 if (history == null) 416 return; 417 if (row == history.getNumVersions()) { 418 if (latest != null) { 419 setCurrentPointInTime(latest); 420 } 421 return; 422 } 423 if (row < 0 || row > history.getNumVersions()) 424 return; 425 setCurrentPointInTime(history.get(row)); 426 } 427 428 /** 429 * Determines if the given row is the reference point in time. 430 * @param row row number 431 * @return {@code true} if the given row is the reference point in time 432 */ 433 public boolean isReferencePointInTime(int row) { 434 if (history == null) 435 return false; 436 if (row == history.getNumVersions()) 437 return latest == reference; 438 if (row < 0 || row > history.getNumVersions()) 439 return false; 440 return history.get(row) == reference; 441 } 442 443 /** 444 * Determines if the given row is the current point in time. 445 * @param row row number 446 * @return {@code true} if the given row is the current point in time 447 */ 448 public boolean isCurrentPointInTime(int row) { 449 if (history == null) 450 return false; 451 if (row == history.getNumVersions()) 452 return latest == current; 453 if (row < 0 || row > history.getNumVersions()) 454 return false; 455 return history.get(row) == current; 456 } 457 458 /** 459 * Returns the {@code HistoryPrimitive} at the given row. 460 * @param row row number 461 * @return the {@code HistoryPrimitive} at the given row 462 */ 463 public HistoryOsmPrimitive getPrimitive(int row) { 464 if (history == null) 465 return null; 466 return isLatest(row) ? latest : history.get(row); 467 } 468 469 /** 470 * Determines if the given row is the latest. 471 * @param row row number 472 * @return {@code true} if the given row is the latest 473 */ 474 public boolean isLatest(int row) { 475 return row >= history.getNumVersions(); 476 } 477 478 /** 479 * Returns the latest {@code HistoryOsmPrimitive}. 480 * @return the latest {@code HistoryOsmPrimitive} 481 * @since 11646 482 */ 483 public HistoryOsmPrimitive getLatest() { 484 return latest; 485 } 486 487 /** 488 * Returns the key set (union of current and reference point in type key sets). 489 * @return the key set (union of current and reference point in type key sets) 490 * @since 11647 491 */ 492 public Set<String> getKeySet() { 493 Set<String> keySet = new HashSet<>(); 494 if (current != null) { 495 keySet.addAll(current.getTags().keySet()); 496 } 497 if (reference != null) { 498 keySet.addAll(reference.getTags().keySet()); 499 } 500 return keySet; 501 } 502 503 /** 504 * Sets the latest {@code HistoryOsmPrimitive}. 505 * @param latest the latest {@code HistoryOsmPrimitive} 506 */ 507 protected void setLatest(HistoryOsmPrimitive latest) { 508 if (latest == null) { 509 if (this.current == this.latest) { 510 this.current = history != null ? history.getLatest() : null; 511 } 512 if (this.reference == this.latest) { 513 this.reference = history != null ? history.getLatest() : null; 514 } 515 this.latest = null; 516 } else { 517 if (this.current == this.latest) { 518 this.current = latest; 519 } 520 if (this.reference == this.latest) { 521 this.reference = latest; 522 } 523 this.latest = latest; 524 } 525 fireModelChange(); 526 } 527 528 /** 529 * Removes this model as listener for data change and layer change events. 530 * 531 */ 532 public void unlinkAsListener() { 533 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 534 if (ds != null) { 535 ds.removeDataSetListener(this); 536 } 537 MainApplication.getLayerManager().removeActiveLayerChangeListener(this); 538 } 539 540 /* ---------------------------------------------------------------------- */ 541 /* DataSetListener */ 542 /* ---------------------------------------------------------------------- */ 543 @Override 544 public void nodeMoved(NodeMovedEvent event) { 545 Node node = event.getNode(); 546 if (!node.isNew() && node.getId() == history.getId()) { 547 setLatest(new HistoryPrimitiveBuilder().build(node)); 548 } 549 } 550 551 @Override 552 public void primitivesAdded(PrimitivesAddedEvent event) { 553 for (OsmPrimitive p: event.getPrimitives()) { 554 if (canShowAsLatest(p)) { 555 setLatest(new HistoryPrimitiveBuilder().build(p)); 556 } 557 } 558 } 559 560 @Override 561 public void primitivesRemoved(PrimitivesRemovedEvent event) { 562 for (OsmPrimitive p: event.getPrimitives()) { 563 if (!p.isNew() && p.getId() == history.getId()) { 564 setLatest(null); 565 } 566 } 567 } 568 569 @Override 570 public void relationMembersChanged(RelationMembersChangedEvent event) { 571 Relation r = event.getRelation(); 572 if (!r.isNew() && r.getId() == history.getId()) { 573 setLatest(new HistoryPrimitiveBuilder().build(r)); 574 } 575 } 576 577 @Override 578 public void tagsChanged(TagsChangedEvent event) { 579 OsmPrimitive prim = event.getPrimitive(); 580 if (!prim.isNew() && prim.getId() == history.getId()) { 581 setLatest(new HistoryPrimitiveBuilder().build(prim)); 582 } 583 } 584 585 @Override 586 public void wayNodesChanged(WayNodesChangedEvent event) { 587 Way way = event.getChangedWay(); 588 if (!way.isNew() && way.getId() == history.getId()) { 589 setLatest(new HistoryPrimitiveBuilder().build(way)); 590 } 591 } 592 593 @Override 594 public void dataChanged(DataChangedEvent event) { 595 if (history == null) 596 return; 597 OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType()); 598 HistoryOsmPrimitive newLatest; 599 if (canShowAsLatest(primitive)) { 600 newLatest = new HistoryPrimitiveBuilder().build(primitive); 601 } else { 602 newLatest = null; 603 } 604 setLatest(newLatest); 605 fireModelChange(); 606 } 607 608 @Override 609 public void otherDatasetChange(AbstractDatasetChangedEvent event) { 610 // Irrelevant 611 } 612 613 /* ---------------------------------------------------------------------- */ 614 /* ActiveLayerChangeListener */ 615 /* ---------------------------------------------------------------------- */ 616 @Override 617 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 618 Layer oldLayer = e.getPreviousActiveLayer(); 619 if (oldLayer instanceof OsmDataLayer) { 620 OsmDataLayer l = (OsmDataLayer) oldLayer; 621 l.data.removeDataSetListener(this); 622 } 623 Layer newLayer = e.getSource().getActiveLayer(); 624 if (!(newLayer instanceof OsmDataLayer)) { 625 latest = null; 626 fireModelChange(); 627 return; 628 } 629 OsmDataLayer l = (OsmDataLayer) newLayer; 630 l.data.addDataSetListener(this); 631 OsmPrimitive primitive = history != null ? l.data.getPrimitiveById(history.getId(), history.getType()) : null; 632 HistoryOsmPrimitive newLatest; 633 if (canShowAsLatest(primitive)) { 634 newLatest = new HistoryPrimitiveBuilder().build(primitive); 635 } else { 636 newLatest = null; 637 } 638 setLatest(newLatest); 639 fireModelChange(); 640 } 641 642 /** 643 * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive} 644 * 645 */ 646 static class HistoryPrimitiveBuilder implements OsmPrimitiveVisitor { 647 private HistoryOsmPrimitive clone; 648 649 @Override 650 public void visit(Node n) { 651 clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false); 652 clone.setTags(n.getKeys()); 653 } 654 655 @Override 656 public void visit(Relation r) { 657 clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false); 658 clone.setTags(r.getKeys()); 659 HistoryRelation hr = (HistoryRelation) clone; 660 for (RelationMember rm : r.getMembers()) { 661 hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId())); 662 } 663 } 664 665 @Override 666 public void visit(Way w) { 667 clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false); 668 clone.setTags(w.getKeys()); 669 for (Node n: w.getNodes()) { 670 ((HistoryWay) clone).addNode(n.getUniqueId()); 671 } 672 } 673 674 private static User getCurrentUser() { 675 UserInfo info = UserIdentityManager.getInstance().getUserInfo(); 676 return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName()); 677 } 678 679 HistoryOsmPrimitive build(OsmPrimitive primitive) { 680 primitive.accept(this); 681 return clone; 682 } 683 } 684 685}