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}