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 thrown 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        HashSet<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 thrown if dialogParent is null
127     */
128    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids) throws IllegalArgumentException{
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 thrown if dialogParent is null
142     * @since 7704
143     */
144    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids, boolean includeDiscussion)
145            throws IllegalArgumentException {
146        super(dialogParent, tr("Download changesets"), false /* don't ignore exceptions */);
147        init(ids);
148        this.includeDiscussion = includeDiscussion;
149    }
150
151    @Override
152    protected void cancel() {
153        canceled = true;
154        synchronized (this) {
155            if (reader != null) {
156                reader.cancel();
157            }
158        }
159    }
160
161    @Override
162    protected void finish() {
163        if (canceled)
164            return;
165        if (lastException != null) {
166            ExceptionDialogUtil.explainException(lastException);
167        }
168        Runnable r = new Runnable() {
169            @Override
170            public void run() {
171                ChangesetCache.getInstance().update(downloadedChangesets);
172            }
173        };
174
175        if (SwingUtilities.isEventDispatchThread()) {
176            r.run();
177        } else {
178            try {
179                SwingUtilities.invokeAndWait(r);
180            } catch(InterruptedException e) {
181                Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
182            } catch(InvocationTargetException e) {
183                Throwable t = e.getTargetException();
184                if (t instanceof RuntimeException) {
185                    BugReportExceptionHandler.handleException(t);
186                } else if (t instanceof Exception){
187                    ExceptionUtil.explainException(e);
188                } else {
189                    BugReportExceptionHandler.handleException(t);
190                }
191            }
192        }
193    }
194
195    @Override
196    protected void realRun() throws SAXException, IOException, OsmTransferException {
197        try {
198            synchronized (this) {
199                reader = new OsmServerChangesetReader();
200            }
201            downloadedChangesets = new HashSet<>();
202            downloadedChangesets.addAll(reader.readChangesets(idsToDownload, includeDiscussion,
203                    getProgressMonitor().createSubTaskMonitor(0, false)));
204        } catch(OsmTransferException e) {
205            if (canceled)
206                // ignore exception if canceled
207                return;
208            // remember other exceptions
209            lastException = e;
210        }
211    }
212
213    /* ------------------------------------------------------------------------------- */
214    /* interface ChangesetDownloadTask                                                 */
215    /* ------------------------------------------------------------------------------- */
216    @Override
217    public Set<Changeset> getDownloadedChangesets() {
218        return downloadedChangesets;
219    }
220
221    @Override
222    public boolean isCanceled() {
223        return canceled;
224    }
225
226    @Override
227    public boolean isFailed() {
228        return lastException != null;
229    }
230}