001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.io.IOException;
008import java.lang.reflect.InvocationTargetException;
009import java.util.Collection;
010import java.util.Collections;
011import java.util.HashSet;
012import java.util.Set;
013
014import javax.swing.SwingUtilities;
015
016import org.openstreetmap.josm.Main;
017import org.openstreetmap.josm.data.osm.Changeset;
018import org.openstreetmap.josm.data.osm.ChangesetCache;
019import org.openstreetmap.josm.gui.ExceptionDialogUtil;
020import org.openstreetmap.josm.gui.PleaseWaitRunnable;
021import org.openstreetmap.josm.io.OsmServerChangesetReader;
022import org.openstreetmap.josm.io.OsmTransferException;
023import org.openstreetmap.josm.tools.BugReportExceptionHandler;
024import org.openstreetmap.josm.tools.CheckParameterUtil;
025import org.openstreetmap.josm.tools.ExceptionUtil;
026import org.xml.sax.SAXException;
027
028/**
029 * This is an asynchronous task for downloading a collection of changests from the OSM
030 * server.
031 *
032 * The  task only downloads the changeset properties without the changeset content. It
033 * updates the global {@link ChangesetCache}.
034 *
035 */
036public class ChangesetHeaderDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask {
037
038    /**
039     * Builds a download task from for a collection of changesets.
040     *
041     * Ignores null values and changesets with {@link Changeset#isNew()} == true.
042     *
043     * @param changesets the collection of changesets. Assumes an empty collection if null.
044     * @return the download task
045     */
046    public static ChangesetHeaderDownloadTask buildTaskForChangesets(Collection<Changeset> changesets) {
047        return buildTaskForChangesets(Main.parent, changesets);
048    }
049
050    /**
051     * Builds a download task from for a collection of changesets.
052     *
053     * Ignores null values and changesets with {@link Changeset#isNew()} == true.
054     *
055     * @param parent the parent component relative to which the {@link org.openstreetmap.josm.gui.PleaseWaitDialog} is displayed.
056     * Must not be null.
057     * @param changesets the collection of changesets. Assumes an empty collection if null.
058     * @return the download task
059     * @throws IllegalArgumentException if parent is null
060     */
061    public static ChangesetHeaderDownloadTask buildTaskForChangesets(Component parent, Collection<Changeset> changesets) {
062        CheckParameterUtil.ensureParameterNotNull(parent, "parent");
063        if (changesets == null) {
064            changesets = Collections.emptyList();
065        }
066
067        Set<Integer> ids = new HashSet<>();
068        for (Changeset cs: changesets) {
069            if (cs == null || cs.isNew()) {
070                continue;
071            }
072            ids.add(cs.getId());
073        }
074        if (parent == null)
075            return new ChangesetHeaderDownloadTask(ids);
076        else
077            return new ChangesetHeaderDownloadTask(parent, ids);
078
079    }
080
081    private Set<Integer> idsToDownload;
082    private OsmServerChangesetReader reader;
083    private boolean canceled;
084    private Exception lastException;
085    private Set<Changeset> downloadedChangesets;
086    private final boolean includeDiscussion;
087
088    protected void init(Collection<Integer> ids) {
089        if (ids == null) {
090            ids = Collections.emptyList();
091        }
092        idsToDownload = new HashSet<>();
093        if (ids == null ||  ids.isEmpty())
094            return;
095        for (int id: ids) {
096            if (id <= 0) {
097                continue;
098            }
099            idsToDownload.add(id);
100        }
101    }
102
103    /**
104     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
105     * whose parent is {@link Main#parent}.
106     *
107     * Null ids or or ids &lt;= 0 in the id collection are ignored.
108     *
109     * @param ids the collection of ids. Empty collection assumed if null.
110     */
111    public ChangesetHeaderDownloadTask(Collection<Integer> ids) {
112        // parent for dialog is Main.parent
113        super(tr("Download changesets"), false /* don't ignore exceptions */);
114        init(ids);
115        this.includeDiscussion = false;
116    }
117
118    /**
119     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
120     * whose parent is the parent window of <code>dialogParent</code>.
121     *
122     * Null ids or or ids &lt;= 0 in the id collection are ignored.
123     *
124     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
125     * @param ids the collection of ids. Empty collection assumed if null.
126     * @throws IllegalArgumentException if dialogParent is null
127     */
128    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids) {
129        this(dialogParent, ids, false);
130    }
131
132    /**
133     * Creates the download task for a collection of changeset ids, with possibility to download changeset discussion.
134     * Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog} whose parent is the parent window of <code>dialogParent</code>.
135     *
136     * Null ids or or ids &lt;= 0 in the id collection are ignored.
137     *
138     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
139     * @param ids the collection of ids. Empty collection assumed if null.
140     * @param includeDiscussion determines if discussion comments must be downloaded or not
141     * @throws IllegalArgumentException if dialogParent is null
142     * @since 7704
143     */
144    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids, boolean includeDiscussion) {
145        super(dialogParent, tr("Download changesets"), false /* don't ignore exceptions */);
146        init(ids);
147        this.includeDiscussion = includeDiscussion;
148    }
149
150    @Override
151    protected void cancel() {
152        canceled = true;
153        synchronized (this) {
154            if (reader != null) {
155                reader.cancel();
156            }
157        }
158    }
159
160    @Override
161    protected void finish() {
162        if (canceled)
163            return;
164        if (lastException != null) {
165            ExceptionDialogUtil.explainException(lastException);
166        }
167        Runnable r = new Runnable() {
168            @Override
169            public void run() {
170                ChangesetCache.getInstance().update(downloadedChangesets);
171            }
172        };
173
174        if (SwingUtilities.isEventDispatchThread()) {
175            r.run();
176        } else {
177            try {
178                SwingUtilities.invokeAndWait(r);
179            } catch (InterruptedException e) {
180                Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
181            } catch (InvocationTargetException e) {
182                Throwable t = e.getTargetException();
183                if (t instanceof RuntimeException) {
184                    BugReportExceptionHandler.handleException(t);
185                } else if (t instanceof Exception) {
186                    ExceptionUtil.explainException(e);
187                } else {
188                    BugReportExceptionHandler.handleException(t);
189                }
190            }
191        }
192    }
193
194    @Override
195    protected void realRun() throws SAXException, IOException, OsmTransferException {
196        try {
197            synchronized (this) {
198                reader = new OsmServerChangesetReader();
199            }
200            downloadedChangesets = new HashSet<>();
201            downloadedChangesets.addAll(reader.readChangesets(idsToDownload, includeDiscussion,
202                    getProgressMonitor().createSubTaskMonitor(0, false)));
203        } catch (OsmTransferException e) {
204            if (canceled)
205                // ignore exception if canceled
206                return;
207            // remember other exceptions
208            lastException = e;
209        }
210    }
211
212    /* ------------------------------------------------------------------------------- */
213    /* interface ChangesetDownloadTask                                                 */
214    /* ------------------------------------------------------------------------------- */
215    @Override
216    public Set<Changeset> getDownloadedChangesets() {
217        return downloadedChangesets;
218    }
219
220    @Override
221    public boolean isCanceled() {
222        return canceled;
223    }
224
225    @Override
226    public boolean isFailed() {
227        return lastException != null;
228    }
229}