001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Comparator;
007import java.util.List;
008import java.util.Set;
009import java.util.stream.Collectors;
010import java.util.stream.IntStream;
011
012import javax.swing.DefaultListModel;
013import javax.swing.DefaultListSelectionModel;
014
015import org.openstreetmap.josm.data.osm.AbstractPrimitive;
016import org.openstreetmap.josm.data.osm.Changeset;
017import org.openstreetmap.josm.data.osm.ChangesetCache;
018import org.openstreetmap.josm.data.osm.ChangesetCacheEvent;
019import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
020import org.openstreetmap.josm.data.osm.DataSet;
021import org.openstreetmap.josm.data.osm.OsmPrimitive;
022import org.openstreetmap.josm.data.osm.Storage;
023import org.openstreetmap.josm.gui.util.GuiHelper;
024import org.openstreetmap.josm.gui.util.TableHelper;
025import org.openstreetmap.josm.tools.Utils;
026
027/**
028 * This is the model that backs a list of changesets
029 */
030public class ChangesetListModel extends DefaultListModel<Changeset> implements ChangesetCacheListener {
031    private final transient List<Changeset> data = new ArrayList<>();
032    private final transient Storage<Changeset> shownChangesets = new Storage<>(true);
033    private final DefaultListSelectionModel selectionModel;
034
035    /**
036     * Creates a new {@link ChangesetListModel}
037     * @param selectionModel The selection model to use for this list
038     */
039    public ChangesetListModel(DefaultListSelectionModel selectionModel) {
040        this.selectionModel = selectionModel;
041    }
042
043    /**
044     * Gets the list of changesets that are currently selected
045     * @return The selected changesets
046     */
047    public synchronized Set<Changeset> getSelectedChangesets() {
048        return IntStream.range(0, getSize()).filter(selectionModel::isSelectedIndex)
049                .mapToObj(data::get).collect(Collectors.toSet());
050    }
051
052    /**
053     * Gets the IDs of the changesets that are selected
054     * @return The selected ids
055     */
056    public synchronized Set<Integer> getSelectedChangesetIds() {
057        return IntStream.range(0, getSize()).filter(selectionModel::isSelectedIndex)
058                .mapToObj(data::get).map(Changeset::getId).collect(Collectors.toSet());
059    }
060
061    /**
062     * Sets the changesets to select
063     * @param changesets The changesets
064     */
065    public synchronized void setSelectedChangesets(Collection<Changeset> changesets) {
066        TableHelper.setSelectedIndices(selectionModel,
067                changesets != null ? changesets.stream().mapToInt(data::indexOf) : IntStream.empty());
068    }
069
070    protected void setChangesets(Collection<Changeset> changesets) {
071        shownChangesets.clear();
072        if (changesets != null) {
073            shownChangesets.addAll(changesets);
074        }
075        updateModel();
076    }
077
078    private synchronized void updateModel() {
079        Set<Changeset> sel = getSelectedChangesets();
080        data.clear();
081        data.addAll(shownChangesets);
082        ChangesetCache cache = ChangesetCache.getInstance();
083        for (Changeset cs: data) {
084            if (cache.contains(cs) && cache.get(cs.getId()) != cs) {
085                cs.mergeFrom(cache.get(cs.getId()));
086            }
087        }
088        sort();
089        fireIntervalAdded(this, 0, getSize());
090        setSelectedChangesets(sel);
091    }
092
093    /**
094     * Loads this list with the given changesets
095     * @param ids The ids of the changesets to display
096     */
097    public void initFromChangesetIds(Collection<Integer> ids) {
098        if (Utils.isEmpty(ids)) {
099            setChangesets(null);
100            return;
101        }
102        Set<Changeset> changesets = ids.stream().mapToInt(id -> id)
103                .filter(id -> id > 0).mapToObj(Changeset::new).collect(Collectors.toSet());
104        setChangesets(changesets);
105    }
106
107    /**
108     * Loads this list with the given changesets
109     * @param primitives The primitives of which the changesets should be displayed
110     */
111    public void initFromPrimitives(Collection<? extends OsmPrimitive> primitives) {
112        if (primitives == null) {
113            setChangesets(null);
114            return;
115        }
116        initFromChangesetIds(primitives.stream().map(AbstractPrimitive::getChangesetId).collect(Collectors.toList()));
117    }
118
119    /**
120     * Loads this list with the given changesets
121     * @param ds The data set to get all changesets from
122     */
123    public void initFromDataSet(DataSet ds) {
124        if (ds == null) {
125            setChangesets(null);
126            return;
127        }
128        initFromChangesetIds(ds.allPrimitives().stream().map(AbstractPrimitive::getChangesetId).collect(Collectors.toList()));
129    }
130
131    @Override
132    public synchronized Changeset getElementAt(int idx) {
133        return data.get(idx);
134    }
135
136    @Override
137    public synchronized int getSize() {
138        return data.size();
139    }
140
141    protected synchronized void sort() {
142        data.sort(Comparator.comparingInt(Changeset::getId).reversed());
143    }
144
145    /**
146     * Replies true if  there is at least one selected open changeset
147     *
148     * @return true if  there is at least one selected open changeset
149     */
150    public boolean hasSelectedOpenChangesets() {
151        return !getSelectedOpenChangesets().isEmpty();
152    }
153
154    /**
155     * Replies the selected open changesets
156     *
157     * @return the selected open changesets
158     */
159    public synchronized List<Changeset> getSelectedOpenChangesets() {
160        return IntStream.range(0, getSize())
161                .filter(selectionModel::isSelectedIndex)
162                .mapToObj(data::get)
163                .filter(Changeset::isOpen)
164                .collect(Collectors.toList());
165    }
166
167    /* ---------------------------------------------------------------------------- */
168    /* Interface ChangesetCacheListener                                             */
169    /* ---------------------------------------------------------------------------- */
170    @Override
171    public synchronized void changesetCacheUpdated(ChangesetCacheEvent event) {
172        Set<Changeset> sel = getSelectedChangesets();
173        for (Changeset cs: event.getAddedChangesets()) {
174            int idx = data.indexOf(cs);
175            if (idx >= 0 && data.get(idx) != cs) {
176                data.get(idx).mergeFrom(cs);
177            }
178        }
179        for (Changeset cs: event.getUpdatedChangesets()) {
180            int idx = data.indexOf(cs);
181            if (idx >= 0 && data.get(idx) != cs) {
182                data.get(idx).mergeFrom(cs);
183            }
184        }
185        for (Changeset cs: event.getRemovedChangesets()) {
186            int idx = data.indexOf(cs);
187            if (idx >= 0) {
188                // replace with an incomplete changeset
189                data.set(idx, new Changeset(cs.getId()));
190            }
191        }
192        GuiHelper.runInEDT(() -> {
193            fireContentsChanged(this, 0, getSize());
194            setSelectedChangesets(sel);
195        });
196    }
197}