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        if (Main.main != null) {
108            OsmDataLayer editLayer = Main.main.getEditLayer();
109            if (editLayer != null) {
110                editLayer.data.addDataSetListener(this);
111            }
112        }
113        MapView.addLayerChangeListener(this);
114    }
115
116    /**
117     * Creates a new history browser model for a given history.
118     *
119     * @param history the history. Must not be null.
120     * @throws IllegalArgumentException if history is null
121     */
122    public HistoryBrowserModel(History history) {
123        this();
124        CheckParameterUtil.ensureParameterNotNull(history, "history");
125        setHistory(history);
126    }
127
128    /**
129     * replies the history managed by this model
130     * @return the history
131     */
132    public History getHistory() {
133        return history;
134    }
135
136    protected boolean canShowAsLatest(OsmPrimitive primitive) {
137        if (primitive == null) return false;
138        if (primitive.isNew() || !primitive.isUsable()) return false;
139
140        //try creating a history primitive. if that fails, the primitive cannot be used.
141        try {
142            HistoryOsmPrimitive.forOsmPrimitive(primitive);
143        } catch (Exception ign) {
144            return false;
145        }
146
147        if (history == null) return false;
148        // only show latest of the same version if it is modified
149        if (history.getByVersion(primitive.getVersion()) != null)
150            return primitive.isModified();
151
152        // if latest version from history is higher than a non existing primitive version,
153        // that means this version has been redacted and the primitive cannot be used.
154        if (history.getLatest().getVersion() > primitive.getVersion())
155            return false;
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        return true;
161    }
162
163    /**
164     * sets the history to be managed by this model
165     *
166     * @param history the history
167     *
168     */
169    public void setHistory(History history) {
170        this.history = history;
171        if (history.getNumVersions() > 0) {
172            HistoryOsmPrimitive newLatest = null;
173            OsmDataLayer editLayer = Main.main.getEditLayer();
174            if (editLayer != null) {
175                OsmPrimitive p = editLayer.data.getPrimitiveById(history.getId(), history.getType());
176                if (canShowAsLatest(p)) {
177                    newLatest = new HistoryPrimitiveBuilder().build(p);
178                }
179            }
180            if (newLatest == null) {
181                current = history.getLatest();
182                int prevIndex = history.getNumVersions() - 2;
183                reference = prevIndex < 0 ? history.getEarliest() : history.get(prevIndex);
184            } else {
185                reference = history.getLatest();
186                current = newLatest;
187            }
188            setLatest(newLatest);
189        }
190        initTagTableModels();
191        fireModelChange();
192    }
193
194    protected void fireModelChange() {
195        initNodeListTableModels();
196        initMemberListTableModels();
197        setChanged();
198        notifyObservers();
199        versionTableModel.fireTableDataChanged();
200    }
201
202    /**
203     * Replies the table model to be used in a {@link JTable} which
204     * shows the list of versions in this history.
205     *
206     * @return the table model
207     */
208    public VersionTableModel getVersionTableModel() {
209        return versionTableModel;
210    }
211
212    protected void initTagTableModels() {
213        currentTagTableModel.initKeyList();
214        referenceTagTableModel.initKeyList();
215    }
216
217    /**
218     * Should be called everytime either reference of current changes to update the diff.
219     * TODO: Maybe rename to reflect this? eg. updateNodeListTableModels
220     */
221    protected void initNodeListTableModels() {
222        if (current == null || current.getType() != OsmPrimitiveType.WAY
223         || reference == null || reference.getType() != OsmPrimitiveType.WAY)
224            return;
225        TwoColumnDiff diff = new TwoColumnDiff(
226                ((HistoryWay) reference).getNodes().toArray(),
227                ((HistoryWay) current).getNodes().toArray());
228        referenceNodeListTableModel.setRows(diff.referenceDiff, diff.referenceReversed);
229        currentNodeListTableModel.setRows(diff.currentDiff, false);
230    }
231
232    protected void initMemberListTableModels() {
233        if (current == null || current.getType() != OsmPrimitiveType.RELATION
234         || reference == null || reference.getType() != OsmPrimitiveType.RELATION)
235            return;
236        TwoColumnDiff diff = new TwoColumnDiff(
237                ((HistoryRelation) reference).getMembers().toArray(),
238                ((HistoryRelation) current).getMembers().toArray());
239        referenceRelationMemberTableModel.setRows(diff.referenceDiff, diff.referenceReversed);
240        currentRelationMemberTableModel.setRows(diff.currentDiff, false);
241    }
242
243    /**
244     * replies the tag table model for the respective point in time
245     *
246     * @param pointInTimeType the type of the point in time (must not be null)
247     * @return the tag table model
248     * @throws IllegalArgumentException if pointInTimeType is null
249     */
250    public TagTableModel getTagTableModel(PointInTimeType pointInTimeType) {
251        CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
252        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
253            return currentTagTableModel;
254        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
255            return referenceTagTableModel;
256
257        // should not happen
258        return null;
259    }
260
261    public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) {
262        CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
263        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
264            return currentNodeListTableModel;
265        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
266            return referenceNodeListTableModel;
267
268        // should not happen
269        return null;
270    }
271
272    public DiffTableModel getRelationMemberTableModel(PointInTimeType pointInTimeType) {
273        CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
274        if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
275            return currentRelationMemberTableModel;
276        else if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
277            return referenceRelationMemberTableModel;
278
279        // should not happen
280        return null;
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        HistoryOsmPrimitive primitive = history.getByVersion(reference.getVersion());
303        if (primitive == null)
304            throw new IllegalArgumentException(
305                    tr("Failed to set reference. Reference version {0} not available in history.", reference.getVersion()));
306
307        this.reference = reference;
308        initTagTableModels();
309        initNodeListTableModels();
310        initMemberListTableModels();
311        setChanged();
312        notifyObservers();
313    }
314
315    /**
316     * Sets the {@link HistoryOsmPrimitive} which plays the role of the current point
317     * in time (see {@link PointInTimeType}).
318     *
319     * @param current the reference history primitive. Must not be {@code null}.
320     * @throws IllegalArgumentException if reference is {@code null}
321     * @throws IllegalStateException if this model isn't a assigned a history yet
322     * @throws IllegalArgumentException if reference isn't an history primitive for the history managed by this mode
323     *
324     * @see #setHistory(History)
325     * @see PointInTimeType
326     */
327    public void setCurrentPointInTime(HistoryOsmPrimitive current) {
328        CheckParameterUtil.ensureParameterNotNull(current, "current");
329        if (history == null)
330            throw new IllegalStateException(tr("History not initialized yet. Failed to set current primitive."));
331        if (current.getId() != history.getId())
332            throw new IllegalArgumentException(
333                    tr("Failed to set reference. Reference ID {0} does not match history ID {1}.", current.getId(),  history.getId()));
334        HistoryOsmPrimitive primitive = history.getByVersion(current.getVersion());
335        if (primitive == 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        setChanged();
343        notifyObservers();
344    }
345
346    /**
347     * Replies the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME}
348     *
349     * @return the history OSM primitive for the {@link PointInTimeType#CURRENT_POINT_IN_TIME} (may be null)
350     */
351    public HistoryOsmPrimitive getCurrentPointInTime() {
352        return getPointInTime(PointInTimeType.CURRENT_POINT_IN_TIME);
353    }
354
355    /**
356     * Replies the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME}
357     *
358     * @return the history OSM primitive for the {@link PointInTimeType#REFERENCE_POINT_IN_TIME} (may be null)
359     */
360    public HistoryOsmPrimitive getReferencePointInTime() {
361        return getPointInTime(PointInTimeType.REFERENCE_POINT_IN_TIME);
362    }
363
364    /**
365     * replies the history OSM primitive for a given point in time
366     *
367     * @param type the type of the point in time (must not be null)
368     * @return the respective primitive. Can be null.
369     * @throws IllegalArgumentException if type is null
370     */
371    public HistoryOsmPrimitive getPointInTime(PointInTimeType type)  {
372        CheckParameterUtil.ensureParameterNotNull(type, "type");
373        if (type.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
374            return current;
375        else if (type.equals(PointInTimeType.REFERENCE_POINT_IN_TIME))
376            return reference;
377
378        // should not happen
379        return null;
380    }
381
382    /**
383     * Returns true if <code>primitive</code> is the latest primitive
384     * representing the version currently edited in the current data
385     * layer.
386     *
387     * @param primitive the primitive to check
388     * @return true if <code>primitive</code> is the latest primitive
389     */
390    public boolean isLatest(HistoryOsmPrimitive primitive) {
391        if (primitive == null) return false;
392        return primitive == latest;
393    }
394
395    /**
396     * The table model for the list of versions in the current history
397     *
398     */
399    public final class VersionTableModel extends AbstractTableModel {
400
401        private VersionTableModel() {
402        }
403
404        @Override
405        public int getRowCount() {
406            if (history == null)
407                return 0;
408            int ret = history.getNumVersions();
409            if (latest != null) {
410                ret++;
411            }
412            return ret;
413        }
414
415        @Override
416        public Object getValueAt(int row, int column) {
417            switch (column) {
418            case 0:
419                return Long.toString(getPrimitive(row).getVersion());
420            case 1:
421                return isReferencePointInTime(row);
422            case 2:
423                return isCurrentPointInTime(row);
424            case 3:
425                HistoryOsmPrimitive p3 = getPrimitive(row);
426                if (p3 != null && p3.getTimestamp() != null)
427                    return DateUtils.formatDateTime(p3.getTimestamp(), DateFormat.SHORT, DateFormat.SHORT);
428                return null;
429            case 4:
430                HistoryOsmPrimitive p4 = getPrimitive(row);
431                if (p4 != null) {
432                    User user = p4.getUser();
433                    if (user != null)
434                        return user.getName();
435                }
436                return null;
437            }
438            return null;
439        }
440
441        @Override
442        public void setValueAt(Object aValue, int row, int column) {
443            if (!((Boolean) aValue)) return;
444            switch (column) {
445            case 1:
446                setReferencePointInTime(row);
447                break;
448            case 2:
449                setCurrentPointInTime(row);
450                break;
451            default:
452                return;
453            }
454            fireTableDataChanged();
455        }
456
457        @Override
458        public boolean isCellEditable(int row, int column) {
459            return column >= 1 && column <= 2;
460        }
461
462        public void setReferencePointInTime(int row) {
463            if (history == null) return;
464            if (row == history.getNumVersions()) {
465                if (latest != null) {
466                    HistoryBrowserModel.this.setReferencePointInTime(latest);
467                }
468                return;
469            }
470            if (row < 0 || row > history.getNumVersions()) return;
471            HistoryOsmPrimitive reference = history.get(row);
472            HistoryBrowserModel.this.setReferencePointInTime(reference);
473        }
474
475        public void setCurrentPointInTime(int row) {
476            if (history == null) return;
477            if (row == history.getNumVersions()) {
478                if (latest != null) {
479                    HistoryBrowserModel.this.setCurrentPointInTime(latest);
480                }
481                return;
482            }
483            if (row < 0 || row > history.getNumVersions()) return;
484            HistoryOsmPrimitive current = history.get(row);
485            HistoryBrowserModel.this.setCurrentPointInTime(current);
486        }
487
488        public boolean isReferencePointInTime(int row) {
489            if (history == null) return false;
490            if (row == history.getNumVersions())
491                return latest == reference;
492            if (row < 0 || row > history.getNumVersions()) return false;
493            HistoryOsmPrimitive p = history.get(row);
494            return p == reference;
495        }
496
497        public boolean isCurrentPointInTime(int row) {
498            if (history == null) return false;
499            if (row == history.getNumVersions())
500                return latest == current;
501            if (row < 0 || row > history.getNumVersions()) return false;
502            HistoryOsmPrimitive p = history.get(row);
503            return p == current;
504        }
505
506        public HistoryOsmPrimitive getPrimitive(int row) {
507            if (history == null)
508                return null;
509            return isLatest(row) ? latest : history.get(row);
510        }
511
512        public boolean isLatest(int row) {
513            return row >= history.getNumVersions();
514        }
515
516        public OsmPrimitive getLatest() {
517            if (latest == null) return null;
518            OsmDataLayer editLayer = Main.main.getEditLayer();
519            if (editLayer == null) return null;
520            return editLayer.data.getPrimitiveById(latest.getId(), latest.getType());
521        }
522
523        @Override
524        public int getColumnCount() {
525            return 6;
526        }
527    }
528
529    /**
530     * The table model for the tags of the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME}
531     * or {@link PointInTimeType#CURRENT_POINT_IN_TIME}
532     *
533     */
534    public class TagTableModel extends AbstractTableModel {
535
536        private List<String> keys;
537        private final PointInTimeType pointInTimeType;
538
539        protected void initKeyList() {
540            Set<String> keySet = new HashSet<>();
541            if (current != null) {
542                keySet.addAll(current.getTags().keySet());
543            }
544            if (reference != null) {
545                keySet.addAll(reference.getTags().keySet());
546            }
547            keys = new ArrayList<>(keySet);
548            Collections.sort(keys);
549            fireTableDataChanged();
550        }
551
552        protected TagTableModel(PointInTimeType type) {
553            pointInTimeType = type;
554            initKeyList();
555        }
556
557        @Override
558        public int getRowCount() {
559            if (keys == null) return 0;
560            return keys.size();
561        }
562
563        @Override
564        public Object getValueAt(int row, int column) {
565            return keys.get(row);
566        }
567
568        public boolean hasTag(String key) {
569            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
570            if (primitive == null)
571                return false;
572            return primitive.hasTag(key);
573        }
574
575        public String getValue(String key) {
576            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
577            if (primitive == null)
578                return null;
579            return primitive.get(key);
580        }
581
582        public boolean oppositeHasTag(String key) {
583            PointInTimeType opposite = pointInTimeType.opposite();
584            HistoryOsmPrimitive primitive = getPointInTime(opposite);
585            if (primitive == null)
586                return false;
587            return primitive.hasTag(key);
588        }
589
590        public String getOppositeValue(String key) {
591            PointInTimeType opposite = pointInTimeType.opposite();
592            HistoryOsmPrimitive primitive = getPointInTime(opposite);
593            if (primitive == null)
594                return null;
595            return primitive.get(key);
596        }
597
598        public boolean hasSameValueAsOpposite(String key) {
599            String value = getValue(key);
600            String oppositeValue = getOppositeValue(key);
601            if (value == null || oppositeValue == null)
602                return false;
603            return value.equals(oppositeValue);
604        }
605
606        public PointInTimeType getPointInTimeType() {
607            return pointInTimeType;
608        }
609
610        public boolean isCurrentPointInTime() {
611            return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME);
612        }
613
614        public boolean isReferencePointInTime() {
615            return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME);
616        }
617
618        @Override
619        public int getColumnCount() {
620            return 1;
621        }
622    }
623
624    protected void setLatest(HistoryOsmPrimitive latest) {
625        if (latest == null) {
626            if (this.current == this.latest) {
627                this.current = history != null ? history.getLatest() : null;
628            }
629            if (this.reference == this.latest) {
630                this.reference = history != null ? history.getLatest() : null;
631            }
632            this.latest = null;
633        } else {
634            if (this.current == this.latest) {
635                this.current = latest;
636            }
637            if (this.reference == this.latest) {
638                this.reference = latest;
639            }
640            this.latest = latest;
641        }
642        fireModelChange();
643    }
644
645    /**
646     * Removes this model as listener for data change and layer change events.
647     *
648     */
649    public void unlinkAsListener() {
650        OsmDataLayer editLayer = Main.main.getEditLayer();
651        if (editLayer != null) {
652            editLayer.data.removeDataSetListener(this);
653        }
654        MapView.removeLayerChangeListener(this);
655    }
656
657    /* ---------------------------------------------------------------------- */
658    /* DataSetListener                                                        */
659    /* ---------------------------------------------------------------------- */
660    @Override
661    public void nodeMoved(NodeMovedEvent event) {
662        Node node = event.getNode();
663        if (!node.isNew() && node.getId() == history.getId()) {
664            setLatest(new HistoryPrimitiveBuilder().build(node));
665        }
666    }
667
668    @Override
669    public void primitivesAdded(PrimitivesAddedEvent event) {
670        for (OsmPrimitive p: event.getPrimitives()) {
671            if (canShowAsLatest(p)) {
672                setLatest(new HistoryPrimitiveBuilder().build(p));
673            }
674        }
675    }
676
677    @Override
678    public void primitivesRemoved(PrimitivesRemovedEvent event) {
679        for (OsmPrimitive p: event.getPrimitives()) {
680            if (!p.isNew() && p.getId() == history.getId()) {
681                setLatest(null);
682            }
683        }
684    }
685
686    @Override
687    public void relationMembersChanged(RelationMembersChangedEvent event) {
688        Relation r = event.getRelation();
689        if (!r.isNew() && r.getId() == history.getId()) {
690            setLatest(new HistoryPrimitiveBuilder().build(r));
691        }
692    }
693
694    @Override
695    public void tagsChanged(TagsChangedEvent event) {
696        OsmPrimitive prim = event.getPrimitive();
697        if (!prim.isNew() && prim.getId() == history.getId()) {
698            setLatest(new HistoryPrimitiveBuilder().build(prim));
699        }
700    }
701
702    @Override
703    public void wayNodesChanged(WayNodesChangedEvent event) {
704        Way way = event.getChangedWay();
705        if (!way.isNew() && way.getId() == history.getId()) {
706            setLatest(new HistoryPrimitiveBuilder().build(way));
707        }
708    }
709
710    @Override
711    public void dataChanged(DataChangedEvent event) {
712        if (history == null)
713            return;
714        OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType());
715        HistoryOsmPrimitive latest;
716        if (canShowAsLatest(primitive)) {
717            latest = new HistoryPrimitiveBuilder().build(primitive);
718        } else {
719            latest = null;
720        }
721        setLatest(latest);
722        fireModelChange();
723    }
724
725    @Override
726    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
727        // Irrelevant
728    }
729
730    /* ---------------------------------------------------------------------- */
731    /* LayerChangeListener                                                    */
732    /* ---------------------------------------------------------------------- */
733    @Override
734    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
735        if (oldLayer instanceof OsmDataLayer) {
736            OsmDataLayer l = (OsmDataLayer) oldLayer;
737            l.data.removeDataSetListener(this);
738        }
739        if (!(newLayer instanceof OsmDataLayer)) {
740            latest = null;
741            fireModelChange();
742            return;
743        }
744        OsmDataLayer l = (OsmDataLayer) newLayer;
745        l.data.addDataSetListener(this);
746        OsmPrimitive primitive = history != null ? l.data.getPrimitiveById(history.getId(), history.getType()) : null;
747        HistoryOsmPrimitive latest;
748        if (canShowAsLatest(primitive)) {
749            latest = new HistoryPrimitiveBuilder().build(primitive);
750        } else {
751            latest = null;
752        }
753        setLatest(latest);
754        fireModelChange();
755    }
756
757    @Override
758    public void layerAdded(Layer newLayer) {}
759
760    @Override
761    public void layerRemoved(Layer oldLayer) {}
762
763    /**
764     * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive}
765     *
766     */
767    static class HistoryPrimitiveBuilder extends AbstractVisitor {
768        private HistoryOsmPrimitive clone;
769
770        @Override
771        public void visit(Node n) {
772            clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false);
773            clone.setTags(n.getKeys());
774        }
775
776        @Override
777        public void visit(Relation r) {
778            clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false);
779            clone.setTags(r.getKeys());
780            HistoryRelation hr = (HistoryRelation) clone;
781            for (RelationMember rm : r.getMembers()) {
782                hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId()));
783            }
784        }
785
786        @Override
787        public void visit(Way w) {
788            clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false);
789            clone.setTags(w.getKeys());
790            for (Node n: w.getNodes()) {
791                ((HistoryWay) clone).addNode(n.getUniqueId());
792            }
793        }
794
795        private static User getCurrentUser() {
796            UserInfo info = JosmUserIdentityManager.getInstance().getUserInfo();
797            return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName());
798        }
799
800        public HistoryOsmPrimitive build(OsmPrimitive primitive) {
801            primitive.accept(this);
802            return clone;
803        }
804    }
805}