001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.dialogs.changeset;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Component;
007    import java.io.IOException;
008    import java.text.MessageFormat;
009    import java.util.ArrayList;
010    import java.util.Collection;
011    import java.util.Collections;
012    import java.util.HashSet;
013    import java.util.List;
014    import java.util.Set;
015    
016    import org.openstreetmap.josm.data.osm.Changeset;
017    import org.openstreetmap.josm.data.osm.ChangesetCache;
018    import org.openstreetmap.josm.data.osm.ChangesetDataSet;
019    import org.openstreetmap.josm.gui.ExceptionDialogUtil;
020    import org.openstreetmap.josm.gui.PleaseWaitRunnable;
021    import org.openstreetmap.josm.io.OsmServerChangesetReader;
022    import org.openstreetmap.josm.io.OsmTransferCanceledException;
023    import org.openstreetmap.josm.io.OsmTransferException;
024    import org.xml.sax.SAXException;
025    
026    /**
027     * This is an asynchronous task for downloading the changeset content of a collection of
028     * changesets.
029     *
030     */
031    public class ChangesetContentDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask{
032    
033        /** the list of changeset ids to download */
034        private final List<Integer> toDownload = new ArrayList<Integer>();
035        /** true if the task was canceled */
036        private boolean canceled;
037        /** keeps the last exception thrown in the task, if any */
038        private Exception lastException;
039        /** the reader object used to read changesets from the API */
040        private OsmServerChangesetReader reader;
041        /** the set of downloaded changesets */
042        private Set<Changeset> downloadedChangesets;
043    
044        /**
045         * Initialize the task with a collection of changeset ids to download
046         *
047         * @param ids the collection of ids. May be null.
048         */
049        protected void init(Collection<Integer> ids) {
050            if (ids == null) {
051                ids = Collections.emptyList();
052            }
053            for (Integer id: ids) {
054                if (id == null || id <= 0) {
055                    continue;
056                }
057                toDownload.add(id);
058            }
059            downloadedChangesets = new HashSet<Changeset>();
060        }
061    
062        /**
063         * Creates a download task for a single changeset
064         *
065         * @param changesetId the changeset id. >0 required.
066         * @throws IllegalArgumentException thrown if changesetId <= 0
067         */
068        public ChangesetContentDownloadTask(int changesetId) throws IllegalArgumentException{
069            super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
070            if (changesetId <= 0)
071                throw new IllegalArgumentException(MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
072            init(Collections.singleton(changesetId));
073        }
074    
075        /**
076         * Creates a download task for a collection of changesets. null values and id <=0 in
077         * the collection are sillently discarded.
078         *
079         * @param changesetIds the changeset ids. Empty collection assumed, if null.
080         */
081        public ChangesetContentDownloadTask(Collection<Integer> changesetIds) {
082            super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
083            init(changesetIds);
084        }
085    
086        /**
087         * Creates a download task for a single changeset
088         *
089         * @param parent the parent component for the {@link PleaseWaitDialog}. Must not be null.
090         * @param changesetId the changeset id. >0 required.
091         * @throws IllegalArgumentException thrown if changesetId <= 0
092         * @throws IllegalArgumentException thrown if parent is null
093         */
094        public ChangesetContentDownloadTask(Component parent, int changesetId) throws IllegalArgumentException{
095            super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
096            if (changesetId <= 0)
097                throw new IllegalArgumentException(MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
098            init(Collections.singleton(changesetId));
099        }
100    
101        /**
102         * Creates a download task for a collection of changesets. null values and id <=0 in
103         * the collection are sillently discarded.
104         *
105         * @param parent the parent component for the {@link PleaseWaitDialog}. Must not be null.
106         * @param changesetIds the changeset ids. Empty collection assumed, if null.
107         * @throws IllegalArgumentException thrown if parent is null
108         */
109        public ChangesetContentDownloadTask(Component parent, Collection<Integer> changesetIds) throws IllegalArgumentException {
110            super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
111            init(changesetIds);
112        }
113    
114        /**
115         * Replies true if the local {@link ChangesetCache} already includes the changeset with
116         * id <code>changesetId</code>.
117         *
118         * @param changesetId the changeset id
119         * @return true if the local {@link ChangesetCache} already includes the changeset with
120         * id <code>changesetId</code>
121         */
122        protected boolean isAvailableLocally(int changesetId) {
123            return ChangesetCache.getInstance().get(changesetId) != null;
124        }
125    
126        /**
127         * Downloads the changeset with id <code>changesetId</code> (only "header"
128         * information, no content)
129         *
130         * @param changesetId the changeset id
131         * @throws OsmTransferException thrown if something went wrong
132         */
133        protected void downloadChangeset(int changesetId) throws OsmTransferException {
134            synchronized(this) {
135                reader = new OsmServerChangesetReader();
136            }
137            Changeset cs = reader.readChangeset(changesetId, getProgressMonitor().createSubTaskMonitor(0, false));
138            synchronized(this) {
139                reader = null;
140            }
141            ChangesetCache.getInstance().update(cs);
142        }
143    
144        @Override
145        protected void cancel() {
146            canceled = true;
147            synchronized (this) {
148                if (reader != null) {
149                    reader.cancel();
150                }
151            }
152        }
153    
154        @Override
155        protected void finish() {
156            if (canceled) return;
157            if (lastException != null) {
158                ExceptionDialogUtil.explainException(lastException);
159            }
160        }
161    
162        @Override
163        protected void realRun() throws SAXException, IOException, OsmTransferException {
164            try {
165                getProgressMonitor().setTicksCount(toDownload.size());
166                int i=0;
167                for (int id: toDownload) {
168                    i++;
169                    if (!isAvailableLocally(id)) {
170                        getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id));
171                        downloadChangeset(id);
172                    }
173                    if (canceled) return;
174                    synchronized(this) {
175                        reader = new OsmServerChangesetReader();
176                    }
177                    getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id));
178                    ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false));
179                    synchronized(this) {
180                        reader = null;
181                    }
182                    Changeset cs = ChangesetCache.getInstance().get(id);
183                    cs.setContent(ds);
184                    ChangesetCache.getInstance().update(cs);
185                    downloadedChangesets.add(cs);
186                    getProgressMonitor().worked(1);
187                }
188            } catch(OsmTransferCanceledException e) {
189                // the download was canceled by the user. This exception is caught if the
190                // user canceled the authentication dialog.
191                //
192                canceled = true;
193                return;
194            } catch(OsmTransferException e) {
195                if (canceled)
196                    return;
197                lastException = e;
198            } catch(RuntimeException e) {
199                throw e;
200            }
201        }
202    
203        /* ------------------------------------------------------------------------------- */
204        /* interface ChangesetDownloadTask                                                 */
205        /* ------------------------------------------------------------------------------- */
206        public Set<Changeset> getDownloadedChangesets() {
207            return downloadedChangesets;
208        }
209    
210        public boolean isCanceled() {
211            return canceled;
212        }
213    
214        public boolean isFailed() {
215            return lastException != null;
216        }
217    }