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.text.DateFormat;
007import java.util.ArrayList;
008import java.util.Collections;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Observable;
012import java.util.Set;
013
014import javax.swing.JTable;
015import javax.swing.table.AbstractTableModel;
016import javax.swing.table.TableModel;
017
018import org.openstreetmap.josm.Main;
019import org.openstreetmap.josm.data.osm.Node;
020import org.openstreetmap.josm.data.osm.OsmPrimitive;
021import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
022import org.openstreetmap.josm.data.osm.Relation;
023import org.openstreetmap.josm.data.osm.RelationMember;
024import org.openstreetmap.josm.data.osm.RelationMemberData;
025import org.openstreetmap.josm.data.osm.User;
026import org.openstreetmap.josm.data.osm.UserInfo;
027import org.openstreetmap.josm.data.osm.Way;
028import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
029import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
030import org.openstreetmap.josm.data.osm.event.DataSetListener;
031import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
032import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
033import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
034import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
035import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
036import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
037import org.openstreetmap.josm.data.osm.history.History;
038import org.openstreetmap.josm.data.osm.history.HistoryNode;
039import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
040import org.openstreetmap.josm.data.osm.history.HistoryRelation;
041import org.openstreetmap.josm.data.osm.history.HistoryWay;
042import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
043import org.openstreetmap.josm.gui.JosmUserIdentityManager;
044import org.openstreetmap.josm.gui.MapView;
045import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
046import org.openstreetmap.josm.gui.layer.Layer;
047import org.openstreetmap.josm.gui.layer.OsmDataLayer;
048import org.openstreetmap.josm.tools.CheckParameterUtil;
049import org.openstreetmap.josm.tools.date.DateUtils;
050
051/**
052 * This is the model used by the history browser.
053 *
054 * The model state consists of the following elements:
055 * <ul>
056 *   <li>the {@link History} of a specific {@link OsmPrimitive}</li>
057 *   <li>a dedicated version in this {@link History} called the {@link PointInTimeType#REFERENCE_POINT_IN_TIME}</li>
058 *   <li>another version in this {@link History} called the {@link PointInTimeType#CURRENT_POINT_IN_TIME}</li>
059 * </ul>
060 * {@link HistoryBrowser} always compares the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} with the
061 * {@link PointInTimeType#CURRENT_POINT_IN_TIME}.
062
063 * This model provides various {@link TableModel}s for {@link JTable}s used in {@link HistoryBrowser}, for
064 * instance:
065 * <ul>
066 *  <li>{@link #getTagTableModel(PointInTimeType)} replies a {@link TableModel} for the tags of either of
067 *   the two selected versions</li>
068 *  <li>{@link #getNodeListTableModel(PointInTimeType)} replies a {@link TableModel} for the list of nodes of
069 *   the two selected versions (if the current history provides information about a {@link Way}</li>
070 *  <li> {@link #getRelationMemberTableModel(PointInTimeType)} replies a {@link TableModel} for the list of relation
071 *  members  of the two selected versions (if the current history provides information about a {@link Relation}</li>
072 *  </ul>
073 *
074 * @see HistoryBrowser
075 */
076public class HistoryBrowserModel extends Observable implements LayerChangeListener, DataSetListener {
077    /** the history of an OsmPrimitive */
078    private History history;
079    private HistoryOsmPrimitive reference;
080    private HistoryOsmPrimitive current;
081    /**
082     * latest isn't a reference of history. It's a clone of the currently edited
083     * {@link OsmPrimitive} in the current edit layer.
084     */
085    private HistoryOsmPrimitive latest;
086
087    private final VersionTableModel versionTableModel;
088    private final TagTableModel currentTagTableModel;
089    private final TagTableModel referenceTagTableModel;
090    private final DiffTableModel currentRelationMemberTableModel;
091    private final DiffTableModel referenceRelationMemberTableModel;
092    private final DiffTableModel referenceNodeListTableModel;
093    private final DiffTableModel currentNodeListTableModel;
094
095    /**
096     * constructor
097     */
098    public HistoryBrowserModel() {
099        versionTableModel = new VersionTableModel();
100        currentTagTableModel = new TagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
101        referenceTagTableModel = new TagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
102        referenceNodeListTableModel = new DiffTableModel();
103        currentNodeListTableModel = new DiffTableModel();
104        currentRelationMemberTableModel = new DiffTableModel();
105        referenceRelationMemberTableModel = new DiffTableModel();
106
107        OsmDataLayer editLayer = Main.main.getEditLayer();
108        if (editLayer != null) {
109            editLayer.data.addDataSetListener(this);
110        }
111        MapView.addLayerChangeListener(this);
112    }
113
114    /**
115     * Creates a new history browser model for a given history.
116     *
117     * @param history the history. Must not be null.
118     * @throws IllegalArgumentException if history is null
119     */
120    public HistoryBrowserModel(History history) {
121        this();
122        CheckParameterUtil.ensureParameterNotNull(history, "history");
123        setHistory(history);
124    }
125
126    /**
127     * replies the history managed by this model
128     * @return the history
129     */
130    public History getHistory() {
131        return history;
132    }
133
134    protected boolean canShowAsLatest(OsmPrimitive primitive) {
135        if (primitive == null) return false;
136        if (primitive.isNew() || !primitive.isUsable()) return false;
137
138        //try creating a history primitive. if that fails, the primitive cannot be used.
139        try {
140            HistoryOsmPrimitive.forOsmPrimitive(primitive);
141        } catch (Exception ign) {
142            return false;
143        }
144
145        if (history == null) 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        if (history.getLatest().getVersion() > primitive.getVersion())
153            return false;
154
155        // latest has a higher version than one of the primitives
156        // in the history (probably because the history got out of sync
157        // with uploaded data) -> show the primitive as latest
158        return true;
159    }
160
161    /**
162     * sets the history to be managed by this model
163     *
164     * @param history the history
165     *
166     */
167    public void setHistory(History history) {
168        this.history = history;
169        if (history.getNumVersions() > 0) {
170            HistoryOsmPrimitive newLatest = null;
171            OsmDataLayer editLayer = Main.main.getEditLayer();
172            if (editLayer != null) {
173                OsmPrimitive p = editLayer.data.getPrimitiveById(history.getId(), history.getType());
174                if (canShowAsLatest(p)) {
175                    newLatest = new HistoryPrimitiveBuilder().build(p);
176                }
177            }
178            if (newLatest == null) {
179                current = history.getLatest();
180                int prevIndex = history.getNumVersions() - 2;
181                reference = prevIndex < 0 ? history.getEarliest() : history.get(prevIndex);
182            } else {
183                reference = history.getLatest();
184                current = newLatest;
185            }
186            setLatest(newLatest);
187        }
188        initTagTableModels();
189        fireModelChange();
190    }
191
192    protected void fireModelChange() {
193        initNodeListTableModels();
194        initMemberListTableModels();
195        setChanged();
196        notifyObservers();
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    protected 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    protected void initNodeListTableModels() {
220        if (current.getType() != OsmPrimitiveType.WAY || reference.getType() != OsmPrimitiveType.WAY)
221            return;
222        TwoColumnDiff diff = new TwoColumnDiff(
223                ((HistoryWay) reference).getNodes().toArray(),
224                ((HistoryWay) current).getNodes().toArray());
225        referenceNodeListTableModel.setRows(diff.referenceDiff, diff.referenceReversed);
226        currentNodeListTableModel.setRows(diff.currentDiff, false);
227    }
228
229    protected void initMemberListTableModels() {
230        if (current.getType() != OsmPrimitiveType.RELATION || reference.getType() != OsmPrimitiveType.RELATION)
231            return;
232        TwoColumnDiff diff = new TwoColumnDiff(
233                ((HistoryRelation) reference).getMembers().toArray(),
234                ((HistoryRelation) current).getMembers().toArray());
235        referenceRelationMemberTableModel.setRows(diff.referenceDiff, diff.referenceReversed);
236        currentRelationMemberTableModel.setRows(diff.currentDiff, false);
237    }
238
239    /**
240     * replies the tag table model for the respective point in time
241     *
242     * @param pointInTimeType the type of the point in time (must not be null)
243     * @return the tag table model
244     * @throws IllegalArgumentException if pointInTimeType is null
245     */
246    public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) {
247        CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
248        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
249            return currentTagTableModel;
250        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
251            return referenceTagTableModel;
252
253        // should not happen
254        return null;
255    }
256
257    public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) {
258        CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
259        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
260            return currentNodeListTableModel;
261        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
262            return referenceNodeListTableModel;
263
264        // should not happen
265        return null;
266    }
267
268    public DiffTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) {
269        CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
270        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
271            return currentRelationMemberTableModel;
272        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
273            return referenceRelationMemberTableModel;
274
275        // should not happen
276        return null;
277    }
278
279    /**
280     * Sets the {@link HistoryOsmPrimitive} which plays the role of a reference point
281     * in time (see {@link PointInTimeType}).
282     *
283     * @param reference the reference history primitive. Must not be null.
284     * @throws IllegalArgumentException if reference is null
285     * @throws IllegalStateException if this model isn't a assigned a history yet
286     * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode
287     *
288     * @see #setHistory(History)
289     * @see PointInTimeType
290     */
291    public void setReferencePointInTime(HistoryOsmPrimitive reference) {
292        CheckParameterUtil.ensureParameterNotNull(reference, "reference");
293        if (history == null)
294            throw new IllegalStateException(tr("History not initialized yet. Failed to set reference primitive."));
295        if (reference.getId() != history.getId())
296            throw new IllegalArgumentException(
297                    tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", reference.getId(),  history.getId()));
298        HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion());
299        if (primitive == null)
300            throw new IllegalArgumentException(
301                    tr("Failed to set reference. Reference version {0} not available in history.", reference.getVersion()));
302
303        this.reference = reference;
304        initTagTableModels();
305        initNodeListTableModels();
306        initMemberListTableModels();
307        setChanged();
308        notifyObservers();
309    }
310
311    /**
312     * Sets the {@link HistoryOsmPrimitive} which plays the role of the current point
313     * in time (see {@link PointInTimeType}).
314     *
315     * @param current the reference history primitive. Must not be {@code null}.
316     * @throws IllegalArgumentException if reference is {@code null}
317     * @throws IllegalStateException if this model isn't a assigned a history yet
318     * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode
319     *
320     * @see #setHistory(History)
321     * @see PointInTimeType
322     */
323    public void setCurrentPointInTime(HistoryOsmPrimitive current) {
324        CheckParameterUtil.ensureParameterNotNull(current, "current");
325        if (history == null)
326            throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive."));
327        if (current.getId() != history.getId())
328            throw new IllegalArgumentException(
329                    tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", current.getId(),  history.getId()));
330        HistoryOsmPrimitive primitive = history.getByVersion(current.getVersion());
331        if (primitive == null)
332            throw new IllegalArgumentException(
333                    tr("Failed to set current primitive. Current version {0} not available in history.", current.getVersion()));
334        this.current = current;
335        initTagTableModels();
336        initNodeListTableModels();
337        initMemberListTableModels();
338        setChanged();
339        notifyObservers();
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.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
370            return current;
371        else if (type.equals(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
381     * layer.
382     *
383     * @param primitive the primitive to check
384     * @return true if <code>primitive</code> is the latest primitive
385     */
386    public boolean isLatest(HistoryOsmPrimitive primitive) {
387        if (primitive == null) return false;
388        return primitive == latest;
389    }
390
391    /**
392     * The table model for the list of versions in the current history
393     *
394     */
395    public final class VersionTableModel extends AbstractTableModel {
396
397        private VersionTableModel() {
398        }
399
400        @Override
401        public int getRowCount() {
402            if (history == null)
403                return 0;
404            int ret = history.getNumVersions();
405            if (latest != null) {
406                ret++;
407            }
408            return ret;
409        }
410
411        @Override
412        public Object getValueAt(int row, int column) {
413            switch (column) {
414            case 0:
415                return Long.toString(getPrimitive(row).getVersion());
416            case 1:
417                return isReferencePointInTime(row);
418            case 2:
419                return isCurrentPointInTime(row);
420            case 3:
421                HistoryOsmPrimitive p3 = getPrimitive(row);
422                if (p3 != null && p3.getTimestamp() != null)
423                    return DateUtils.formatDateTime(p3.getTimestamp(), DateFormat.SHORT, DateFormat.SHORT);
424                return null;
425            case 4:
426                HistoryOsmPrimitive p4 = getPrimitive(row);
427                if (p4 != null) {
428                    User user = p4.getUser();
429                    if (user != null)
430                        return user.getName();
431                }
432                return null;
433            }
434            return null;
435        }
436
437        @Override
438        public void setValueAt(Object aValue, int row, int column) {
439            if (!((Boolean) aValue)) return;
440            switch (column) {
441            case 1:
442                setReferencePointInTime(row);
443                break;
444            case 2:
445                setCurrentPointInTime(row);
446                break;
447            default:
448                return;
449            }
450            fireTableDataChanged();
451        }
452
453        @Override
454        public boolean isCellEditable(int row, int column) {
455            return column >= 1 && column <= 2;
456        }
457
458        public void setReferencePointInTime(int row) {
459            if (history == null) return;
460            if (row == history.getNumVersions()) {
461                if (latest != null) {
462                    HistoryBrowserModel.this.setReferencePointInTime(latest);
463                }
464                return;
465            }
466            if (row < 0 || row > history.getNumVersions()) return;
467            HistoryOsmPrimitive reference = history.get(row);
468            HistoryBrowserModel.this.setReferencePointInTime(reference);
469        }
470
471        public void setCurrentPointInTime(int row) {
472            if (history == null) return;
473            if (row == history.getNumVersions()) {
474                if (latest != null) {
475                    HistoryBrowserModel.this.setCurrentPointInTime(latest);
476                }
477                return;
478            }
479            if (row < 0 || row > history.getNumVersions()) return;
480            HistoryOsmPrimitive current = history.get(row);
481            HistoryBrowserModel.this.setCurrentPointInTime(current);
482        }
483
484        public boolean isReferencePointInTime(int row) {
485            if (history == null) return false;
486            if (row == history.getNumVersions())
487                return latest == reference;
488            if (row < 0 || row > history.getNumVersions()) return false;
489            HistoryOsmPrimitive p = history.get(row);
490            return p == reference;
491        }
492
493        public boolean isCurrentPointInTime(int row) {
494            if (history == null) return false;
495            if (row == history.getNumVersions())
496                return latest == current;
497            if (row < 0 || row > history.getNumVersions()) return false;
498            HistoryOsmPrimitive p = history.get(row);
499            return p == current;
500        }
501
502        public HistoryOsmPrimitive getPrimitive(int row) {
503            if (history == null)
504                return null;
505            return isLatest(row) ? latest : history.get(row);
506        }
507
508        public boolean isLatest(int row) {
509            return row >= history.getNumVersions();
510        }
511
512        public OsmPrimitive getLatest() {
513            if (latest == null) return null;
514            OsmDataLayer editLayer = Main.main.getEditLayer();
515            if (editLayer == null) return null;
516            return editLayer.data.getPrimitiveById(latest.getId(), latest.getType());
517        }
518
519        @Override
520        public int getColumnCount() {
521            return 6;
522        }
523    }
524
525    /**
526     * The table model for the tags of the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME}
527     * or {@link PointInTimeType#CURRENT_POINT_IN_TIME}
528     *
529     */
530    public class TagTableModel extends AbstractTableModel {
531
532        private List<String> keys;
533        private PointInTimeType pointInTimeType;
534
535        protected void initKeyList() {
536            Set<String> keySet = new HashSet<>();
537            if (current != null) {
538                keySet.addAll(current.getTags().keySet());
539            }
540            if (reference != null) {
541                keySet.addAll(reference.getTags().keySet());
542            }
543            keys = new ArrayList<>(keySet);
544            Collections.sort(keys);
545            fireTableDataChanged();
546        }
547
548        protected TagTableModel(PointInTimeType type) {
549            pointInTimeType = type;
550            initKeyList();
551        }
552
553        @Override
554        public int getRowCount() {
555            if (keys == null) return 0;
556            return keys.size();
557        }
558
559        @Override
560        public Object getValueAt(int row, int column) {
561            return keys.get(row);
562        }
563
564        public boolean hasTag(String key) {
565            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
566            if (primitive == null)
567                return false;
568            return primitive.hasTag(key);
569        }
570
571        public String getValue(String key) {
572            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
573            if (primitive == null)
574                return null;
575            return primitive.get(key);
576        }
577
578        public boolean oppositeHasTag(String key) {
579            PointInTimeType opposite = pointInTimeType.opposite();
580            HistoryOsmPrimitive primitive = getPointInTime(opposite);
581            if (primitive == null)
582                return false;
583            return primitive.hasTag(key);
584        }
585
586        public String getOppositeValue(String key) {
587            PointInTimeType opposite = pointInTimeType.opposite();
588            HistoryOsmPrimitive primitive = getPointInTime(opposite);
589            if (primitive == null)
590                return null;
591            return primitive.get(key);
592        }
593
594        public boolean hasSameValueAsOpposite(String key) {
595            String value = getValue(key);
596            String oppositeValue = getOppositeValue(key);
597            if (value == null || oppositeValue == null)
598                return false;
599            return value.equals(oppositeValue);
600        }
601
602        public PointInTimeType getPointInTimeType() {
603            return pointInTimeType;
604        }
605
606        public boolean isCurrentPointInTime() {
607            return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME);
608        }
609
610        public boolean isReferencePointInTime() {
611            return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME);
612        }
613
614        @Override
615        public int getColumnCount() {
616            return 1;
617        }
618    }
619
620    protected void setLatest(HistoryOsmPrimitive latest) {
621        if (latest == null) {
622            if (this.current == this.latest) {
623                this.current = history.getLatest();
624            }
625            if (this.reference == this.latest) {
626                this.current = history.getLatest();
627            }
628            this.latest = null;
629        } else {
630            if (this.current == this.latest) {
631                this.current = latest;
632            }
633            if (this.reference == this.latest) {
634                this.reference = latest;
635            }
636            this.latest = latest;
637        }
638        fireModelChange();
639    }
640
641    /**
642     * Removes this model as listener for data change and layer change
643     * events.
644     *
645     */
646    public void unlinkAsListener() {
647        OsmDataLayer editLayer = Main.main.getEditLayer();
648        if (editLayer != null) {
649            editLayer.data.removeDataSetListener(this);
650        }
651        MapView.removeLayerChangeListener(this);
652    }
653
654    /* ---------------------------------------------------------------------- */
655    /* DataSetListener                                                        */
656    /* ---------------------------------------------------------------------- */
657    @Override
658    public void nodeMoved(NodeMovedEvent event) {
659        Node node = event.getNode();
660        if (!node.isNew() && node.getId() == history.getId()) {
661            setLatest(new HistoryPrimitiveBuilder().build(node));
662        }
663    }
664
665    @Override
666    public void primitivesAdded(PrimitivesAddedEvent event) {
667        for (OsmPrimitive p: event.getPrimitives()) {
668            if (canShowAsLatest(p)) {
669                setLatest(new HistoryPrimitiveBuilder().build(p));
670            }
671        }
672    }
673
674    @Override
675    public void primitivesRemoved(PrimitivesRemovedEvent event) {
676        for (OsmPrimitive p: event.getPrimitives()) {
677            if (!p.isNew() && p.getId() == history.getId()) {
678                setLatest(null);
679            }
680        }
681    }
682
683    @Override
684    public void relationMembersChanged(RelationMembersChangedEvent event) {
685        Relation r = event.getRelation();
686        if (!r.isNew() && r.getId() == history.getId()) {
687            setLatest(new HistoryPrimitiveBuilder().build(r));
688        }
689    }
690
691    @Override
692    public void tagsChanged(TagsChangedEvent event) {
693        OsmPrimitive prim = event.getPrimitive();
694        if (!prim.isNew() && prim.getId() == history.getId()) {
695            setLatest(new HistoryPrimitiveBuilder().build(prim));
696        }
697    }
698
699    @Override
700    public void wayNodesChanged(WayNodesChangedEvent event) {
701        Way way = event.getChangedWay();
702        if (!way.isNew() && way.getId() == history.getId()) {
703            setLatest(new HistoryPrimitiveBuilder().build(way));
704        }
705    }
706
707    @Override
708    public void dataChanged(DataChangedEvent event) {
709        OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType());
710        HistoryOsmPrimitive latest;
711        if (canShowAsLatest(primitive)) {
712            latest = new HistoryPrimitiveBuilder().build(primitive);
713        } else {
714            latest = null;
715        }
716        setLatest(latest);
717        fireModelChange();
718    }
719
720    @Override
721    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
722        // Irrelevant
723    }
724
725    /* ---------------------------------------------------------------------- */
726    /* LayerChangeListener                                                    */
727    /* ---------------------------------------------------------------------- */
728    @Override
729    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
730        if (oldLayer instanceof OsmDataLayer) {
731            OsmDataLayer l = (OsmDataLayer) oldLayer;
732            l.data.removeDataSetListener(this);
733        }
734        if (!(newLayer instanceof OsmDataLayer)) {
735            latest = null;
736            fireModelChange();
737            return;
738        }
739        OsmDataLayer l = (OsmDataLayer) newLayer;
740        l.data.addDataSetListener(this);
741        OsmPrimitive primitive = l.data.getPrimitiveById(history.getId(), history.getType());
742        HistoryOsmPrimitive latest;
743        if (canShowAsLatest(primitive)) {
744            latest = new HistoryPrimitiveBuilder().build(primitive);
745        } else {
746            latest = null;
747        }
748        setLatest(latest);
749        fireModelChange();
750    }
751
752    @Override
753    public void layerAdded(Layer newLayer) {}
754
755    @Override
756    public void layerRemoved(Layer oldLayer) {}
757
758    /**
759     * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive}
760     *
761     */
762    static class HistoryPrimitiveBuilder extends AbstractVisitor {
763        private HistoryOsmPrimitive clone;
764
765        @Override
766        public void visit(Node n) {
767            clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false);
768            clone.setTags(n.getKeys());
769        }
770
771        @Override
772        public void visit(Relation r) {
773            clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false);
774            clone.setTags(r.getKeys());
775            HistoryRelation hr = (HistoryRelation) clone;
776            for (RelationMember rm : r.getMembers()) {
777                hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId()));
778            }
779        }
780
781        @Override
782        public void visit(Way w) {
783            clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false);
784            clone.setTags(w.getKeys());
785            for (Node n: w.getNodes()) {
786                ((HistoryWay) clone).addNode(n.getUniqueId());
787            }
788        }
789
790        private static User getCurrentUser() {
791            UserInfo info = JosmUserIdentityManager.getInstance().getUserInfo();
792            return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName());
793        }
794
795        public HistoryOsmPrimitive build(OsmPrimitive primitive) {
796            primitive.accept(this);
797            return clone;
798        }
799    }
800}