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