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