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.tools.CheckParameterUtil;
048import org.openstreetmap.josm.tools.date.DateUtils;
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 p3 = getPrimitive(row);
432                if (p3 != null && p3.getTimestamp() != null)
433                    return DateUtils.formatDateTime(p3.getTimestamp(), DateFormat.SHORT, DateFormat.SHORT);
434                return null;
435            case 4:
436                HistoryOsmPrimitive p4 = getPrimitive(row);
437                if (p4 != null) {
438                    User user = p4.getUser();
439                    if (user != null)
440                        return user.getName();
441                }
442                return null;
443            }
444            return null;
445        }
446
447        @Override
448        public void setValueAt(Object aValue, int row, int column) {
449            if (!((Boolean) aValue)) return;
450            switch (column) {
451            case 1:
452                setReferencePointInTime(row);
453                break;
454            case 2:
455                setCurrentPointInTime(row);
456                break;
457            default:
458                return;
459            }
460            fireTableDataChanged();
461        }
462
463        @Override
464        public boolean isCellEditable(int row, int column) {
465            return column >= 1 && column <= 2;
466        }
467
468        public void setReferencePointInTime(int row) {
469            if (history == null) return;
470            if (row == history.getNumVersions()) {
471                if (latest != null) {
472                    HistoryBrowserModel.this.setReferencePointInTime(latest);
473                }
474                return;
475            }
476            if (row < 0 || row > history.getNumVersions()) return;
477            HistoryOsmPrimitive reference = history.get(row);
478            HistoryBrowserModel.this.setReferencePointInTime(reference);
479        }
480
481        public void setCurrentPointInTime(int row) {
482            if (history == null) return;
483            if (row == history.getNumVersions()) {
484                if (latest != null) {
485                    HistoryBrowserModel.this.setCurrentPointInTime(latest);
486                }
487                return;
488            }
489            if (row < 0 || row > history.getNumVersions()) return;
490            HistoryOsmPrimitive current = history.get(row);
491            HistoryBrowserModel.this.setCurrentPointInTime(current);
492        }
493
494        public boolean isReferencePointInTime(int row) {
495            if (history == null) return false;
496            if (row == history.getNumVersions())
497                return latest == reference;
498            if (row < 0 || row > history.getNumVersions()) return false;
499            HistoryOsmPrimitive p = history.get(row);
500            return p == reference;
501        }
502
503        public boolean isCurrentPointInTime(int row) {
504            if (history == null) return false;
505            if (row == history.getNumVersions())
506                return latest == current;
507            if (row < 0 || row > history.getNumVersions()) return false;
508            HistoryOsmPrimitive p = history.get(row);
509            return p == current;
510        }
511
512        public HistoryOsmPrimitive getPrimitive(int row) {
513            if (history == null)
514                return null;
515            return isLatest(row) ? latest : history.get(row);
516        }
517
518        public boolean isLatest(int row) {
519            return row >= history.getNumVersions();
520        }
521
522        public OsmPrimitive getLatest() {
523            if (latest == null) return null;
524            OsmDataLayer editLayer = Main.main.getEditLayer();
525            if (editLayer == null) return null;
526            return editLayer.data.getPrimitiveById(latest.getId(), latest.getType());
527        }
528
529        @Override
530        public int getColumnCount() {
531            return 6;
532        }
533    }
534
535    /**
536     * The table model for the tags of the version at {@link PointInTimeType#REFERENCE_POINT_IN_TIME}
537     * or {@link PointInTimeType#CURRENT_POINT_IN_TIME}
538     *
539     */
540    public class TagTableModel extends AbstractTableModel {
541
542        private List<String> keys;
543        private PointInTimeType pointInTimeType;
544
545        protected void initKeyList() {
546            HashSet<String> keySet = new HashSet<>();
547            if (current != null) {
548                keySet.addAll(current.getTags().keySet());
549            }
550            if (reference != null) {
551                keySet.addAll(reference.getTags().keySet());
552            }
553            keys = new ArrayList<>(keySet);
554            Collections.sort(keys);
555            fireTableDataChanged();
556        }
557
558        protected TagTableModel(PointInTimeType type) {
559            pointInTimeType = type;
560            initKeyList();
561        }
562
563        @Override
564        public int getRowCount() {
565            if (keys == null) return 0;
566            return keys.size();
567        }
568
569        @Override
570        public Object getValueAt(int row, int column) {
571            return keys.get(row);
572        }
573
574        @Override
575        public boolean isCellEditable(int row, int column) {
576            return false;
577        }
578
579        public boolean hasTag(String key) {
580            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
581            if (primitive == null)
582                return false;
583            return primitive.hasTag(key);
584        }
585
586        public String getValue(String key) {
587            HistoryOsmPrimitive primitive = getPointInTime(pointInTimeType);
588            if (primitive == null)
589                return null;
590            return primitive.get(key);
591        }
592
593        public boolean oppositeHasTag(String key) {
594            PointInTimeType opposite = pointInTimeType.opposite();
595            HistoryOsmPrimitive primitive = getPointInTime(opposite);
596            if (primitive == null)
597                return false;
598            return primitive.hasTag(key);
599        }
600
601        public String getOppositeValue(String key) {
602            PointInTimeType opposite = pointInTimeType.opposite();
603            HistoryOsmPrimitive primitive = getPointInTime(opposite);
604            if (primitive == null)
605                return null;
606            return primitive.get(key);
607        }
608
609        public boolean hasSameValueAsOpposite(String key) {
610            String value = getValue(key);
611            String oppositeValue = getOppositeValue(key);
612            if (value == null || oppositeValue == null)
613                return false;
614            return value.equals(oppositeValue);
615        }
616
617        public PointInTimeType getPointInTimeType() {
618            return pointInTimeType;
619        }
620
621        public boolean isCurrentPointInTime() {
622            return pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME);
623        }
624
625        public boolean isReferencePointInTime() {
626            return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME);
627        }
628
629        @Override
630        public int getColumnCount() {
631            return 1;
632        }
633    }
634
635    protected void setLatest(HistoryOsmPrimitive latest) {
636        if (latest == null) {
637            if (this.current == this.latest) {
638                this.current = history.getLatest();
639            }
640            if (this.reference == this.latest) {
641                this.current = history.getLatest();
642            }
643            this.latest = null;
644        } else {
645            if (this.current == this.latest) {
646                this.current = latest;
647            }
648            if (this.reference == this.latest) {
649                this.reference = latest;
650            }
651            this.latest = latest;
652        }
653        fireModelChange();
654    }
655
656    /**
657     * Removes this model as listener for data change and layer change
658     * events.
659     *
660     */
661    public void unlinkAsListener() {
662        OsmDataLayer editLayer = Main.main.getEditLayer();
663        if (editLayer != null) {
664            editLayer.data.removeDataSetListener(this);
665        }
666        MapView.removeLayerChangeListener(this);
667    }
668
669    /* ---------------------------------------------------------------------- */
670    /* DataSetListener                                                        */
671    /* ---------------------------------------------------------------------- */
672    @Override
673    public void nodeMoved(NodeMovedEvent event) {
674        Node node = event.getNode();
675        if (!node.isNew() && node.getId() == history.getId()) {
676            setLatest(new HistoryPrimitiveBuilder().build(node));
677        }
678    }
679
680    @Override
681    public void primitivesAdded(PrimitivesAddedEvent event) {
682        for (OsmPrimitive p: event.getPrimitives()) {
683            if (canShowAsLatest(p)) {
684                setLatest(new HistoryPrimitiveBuilder().build(p));
685            }
686        }
687    }
688
689    @Override
690    public void primitivesRemoved(PrimitivesRemovedEvent event) {
691        for (OsmPrimitive p: event.getPrimitives()) {
692            if (!p.isNew() && p.getId() == history.getId()) {
693                setLatest(null);
694            }
695        }
696    }
697
698    @Override
699    public void relationMembersChanged(RelationMembersChangedEvent event) {
700        Relation r = event.getRelation();
701        if (!r.isNew() && r.getId() == history.getId()) {
702            setLatest(new HistoryPrimitiveBuilder().build(r));
703        }
704    }
705
706    @Override
707    public void tagsChanged(TagsChangedEvent event) {
708        OsmPrimitive prim = event.getPrimitive();
709        if (!prim.isNew() && prim.getId() == history.getId()) {
710            setLatest(new HistoryPrimitiveBuilder().build(prim));
711        }
712    }
713
714    @Override
715    public void wayNodesChanged(WayNodesChangedEvent event) {
716        Way way = event.getChangedWay();
717        if (!way.isNew() && way.getId() == history.getId()) {
718            setLatest(new HistoryPrimitiveBuilder().build(way));
719        }
720    }
721
722    @Override
723    public void dataChanged(DataChangedEvent event) {
724        OsmPrimitive primitive = event.getDataset().getPrimitiveById(history.getId(), history.getType());
725        HistoryOsmPrimitive latest;
726        if (canShowAsLatest(primitive)) {
727            latest = new HistoryPrimitiveBuilder().build(primitive);
728        } else {
729            latest = null;
730        }
731        setLatest(latest);
732        fireModelChange();
733    }
734
735    @Override
736    public void otherDatasetChange(AbstractDatasetChangedEvent event) {
737        // Irrelevant
738    }
739
740    /* ---------------------------------------------------------------------- */
741    /* LayerChangeListener                                                    */
742    /* ---------------------------------------------------------------------- */
743    @Override
744    public void activeLayerChange(Layer oldLayer, Layer newLayer) {
745        if (oldLayer instanceof OsmDataLayer) {
746            OsmDataLayer l = (OsmDataLayer)oldLayer;
747            l.data.removeDataSetListener(this);
748        }
749        if (!(newLayer instanceof OsmDataLayer)) {
750            latest = null;
751            fireModelChange();
752            return;
753        }
754        OsmDataLayer l = (OsmDataLayer)newLayer;
755        l.data.addDataSetListener(this);
756        OsmPrimitive primitive = l.data.getPrimitiveById(history.getId(), history.getType());
757        HistoryOsmPrimitive latest;
758        if (canShowAsLatest(primitive)) {
759            latest = new HistoryPrimitiveBuilder().build(primitive);
760        } else {
761            latest = null;
762        }
763        setLatest(latest);
764        fireModelChange();
765    }
766
767    @Override
768    public void layerAdded(Layer newLayer) {}
769    @Override
770    public void layerRemoved(Layer oldLayer) {}
771
772    /**
773     * Creates a {@link HistoryOsmPrimitive} from a {@link OsmPrimitive}
774     *
775     */
776    static class HistoryPrimitiveBuilder extends AbstractVisitor {
777        private HistoryOsmPrimitive clone;
778
779        @Override
780        public void visit(Node n) {
781            clone = new HistoryNode(n.getId(), n.getVersion(), n.isVisible(), getCurrentUser(), 0, null, n.getCoor(), false);
782            clone.setTags(n.getKeys());
783        }
784
785        @Override
786        public void visit(Relation r) {
787            clone = new HistoryRelation(r.getId(), r.getVersion(), r.isVisible(), getCurrentUser(), 0, null, false);
788            clone.setTags(r.getKeys());
789            HistoryRelation hr = (HistoryRelation)clone;
790            for (RelationMember rm : r.getMembers()) {
791                hr.addMember(new RelationMemberData(rm.getRole(), rm.getType(), rm.getUniqueId()));
792            }
793        }
794
795        @Override
796        public void visit(Way w) {
797            clone = new HistoryWay(w.getId(), w.getVersion(), w.isVisible(), getCurrentUser(), 0, null, false);
798            clone.setTags(w.getKeys());
799            for (Node n: w.getNodes()) {
800                ((HistoryWay)clone).addNode(n.getUniqueId());
801            }
802        }
803
804        private User getCurrentUser() {
805            UserInfo info = JosmUserIdentityManager.getInstance().getUserInfo();
806            return info == null ? User.getAnonymous() : User.createOsmUser(info.getId(), info.getDisplayName());
807        }
808
809        public HistoryOsmPrimitive build(OsmPrimitive primitive) {
810            primitive.accept(this);
811            return clone;
812        }
813    }
814}