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