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
087    protected void init(Collection<Integer> ids) {
088        if (ids == null) {
089            ids = Collections.emptyList();
090        }
091        idsToDownload = new HashSet<>();
092        if (ids == null ||  ids.isEmpty())
093            return;
094        for (int id: ids) {
095            if (id <= 0) {
096                continue;
097            }
098            idsToDownload.add(id);
099        }
100    }
101
102    /**
103     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
104     * whose parent is {@link Main#parent}.
105     *
106     * Null ids or or ids &lt;= 0 in the id collection are ignored.
107     *
108     * @param ids the collection of ids. Empty collection assumed if null.
109     */
110    public ChangesetHeaderDownloadTask(Collection<Integer> ids) {
111        // parent for dialog is Main.parent
112        super(tr("Download changesets"), false /* don't ignore exceptions */);
113        init(ids);
114    }
115
116    /**
117     * Creates the download task for a collection of changeset ids. Uses a {@link org.openstreetmap.josm.gui.PleaseWaitDialog}
118     * whose parent is the parent window of <code>dialogParent</code>.
119     *
120     * Null ids or or ids &lt;= 0 in the id collection are ignored.
121     *
122     * @param dialogParent the parent reference component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be null.
123     * @param ids the collection of ids. Empty collection assumed if null.
124     * @throws IllegalArgumentException thrown if dialogParent is null
125     */
126    public ChangesetHeaderDownloadTask(Component dialogParent, Collection<Integer> ids) throws IllegalArgumentException{
127        super(dialogParent,tr("Download changesets"), false /* don't ignore exceptions */);
128        init(ids);
129    }
130
131    @Override
132    protected void cancel() {
133        canceled = true;
134        synchronized (this) {
135            if (reader != null) {
136                reader.cancel();
137            }
138        }
139    }
140
141    @Override
142    protected void finish() {
143        if (canceled)
144            return;
145        if (lastException != null) {
146            ExceptionDialogUtil.explainException(lastException);
147        }
148        Runnable r = new Runnable() {
149            @Override
150            public void run() {
151                ChangesetCache.getInstance().update(downloadedChangesets);
152            }
153        };
154
155        if (SwingUtilities.isEventDispatchThread()) {
156            r.run();
157        } else {
158            try {
159                SwingUtilities.invokeAndWait(r);
160            } catch(InterruptedException e) {
161                Main.warn("InterruptedException in "+getClass().getSimpleName()+" while updating changeset cache");
162            } catch(InvocationTargetException e) {
163                Throwable t = e.getTargetException();
164                if (t instanceof RuntimeException) {
165                    BugReportExceptionHandler.handleException(t);
166                } else if (t instanceof Exception){
167                    ExceptionUtil.explainException(e);
168                } else {
169                    BugReportExceptionHandler.handleException(t);
170                }
171            }
172        }
173    }
174
175    @Override
176    protected void realRun() throws SAXException, IOException, OsmTransferException {
177        try {
178            synchronized (this) {
179                reader = new OsmServerChangesetReader();
180            }
181            downloadedChangesets = new HashSet<>();
182            downloadedChangesets.addAll(reader.readChangesets(idsToDownload, getProgressMonitor().createSubTaskMonitor(0, false)));
183        } catch(OsmTransferException e) {
184            if (canceled)
185                // ignore exception if canceled
186                return;
187            // remember other exceptions
188            lastException = e;
189        }
190    }
191
192    /* ------------------------------------------------------------------------------- */
193    /* interface ChangesetDownloadTask                                                 */
194    /* ------------------------------------------------------------------------------- */
195    @Override
196    public Set<Changeset> getDownloadedChangesets() {
197        return downloadedChangesets;
198    }
199
200    @Override
201    public boolean isCanceled() {
202        return canceled;
203    }
204
205    @Override
206    public boolean isFailed() {
207        return lastException != null;
208    }
209}