source: josm/trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadTaskList.java@ 10212

Last change on this file since 10212 was 10212, checked in by Don-vip, 8 years ago

sonar - squid:S2221 - "Exception" should not be caught when not required by called methods

  • Property svn:eol-style set to native
File size: 13.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.downloadtasks;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.EventQueue;
9import java.awt.geom.Area;
10import java.awt.geom.Rectangle2D;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.LinkedHashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Set;
18import java.util.concurrent.CancellationException;
19import java.util.concurrent.ExecutionException;
20import java.util.concurrent.Future;
21
22import javax.swing.JOptionPane;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.actions.UpdateSelectionAction;
26import org.openstreetmap.josm.data.Bounds;
27import org.openstreetmap.josm.data.osm.DataSet;
28import org.openstreetmap.josm.data.osm.OsmPrimitive;
29import org.openstreetmap.josm.gui.HelpAwareOptionPane;
30import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
31import org.openstreetmap.josm.gui.Notification;
32import org.openstreetmap.josm.gui.layer.Layer;
33import org.openstreetmap.josm.gui.layer.OsmDataLayer;
34import org.openstreetmap.josm.gui.progress.ProgressMonitor;
35import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;
36import org.openstreetmap.josm.gui.util.GuiHelper;
37import org.openstreetmap.josm.tools.ExceptionUtil;
38import org.openstreetmap.josm.tools.ImageProvider;
39import org.openstreetmap.josm.tools.Utils;
40
41/**
42 * This class encapsulates the downloading of several bounding boxes that would otherwise be too
43 * large to download in one go. Error messages will be collected for all downloads and displayed as
44 * a list in the end.
45 * @author xeen
46 * @since 6053
47 */
48public class DownloadTaskList {
49 private final List<DownloadTask> tasks = new LinkedList<>();
50 private final List<Future<?>> taskFutures = new LinkedList<>();
51 private ProgressMonitor progressMonitor;
52
53 private void addDownloadTask(DownloadTask dt, Rectangle2D td, int i, int n) {
54 ProgressMonitor childProgress = progressMonitor.createSubTaskMonitor(1, false);
55 childProgress.setCustomText(tr("Download {0} of {1} ({2} left)", i, n, n - i));
56 Future<?> future = dt.download(false, new Bounds(td), childProgress);
57 taskFutures.add(future);
58 tasks.add(dt);
59 }
60
61 /**
62 * Downloads a list of areas from the OSM Server
63 * @param newLayer Set to true if all areas should be put into a single new layer
64 * @param rects The List of Rectangle2D to download
65 * @param osmData Set to true if OSM data should be downloaded
66 * @param gpxData Set to true if GPX data should be downloaded
67 * @param progressMonitor The progress monitor
68 * @return The Future representing the asynchronous download task
69 */
70 public Future<?> download(boolean newLayer, List<Rectangle2D> rects, boolean osmData, boolean gpxData, ProgressMonitor progressMonitor) {
71 this.progressMonitor = progressMonitor;
72 if (newLayer) {
73 Layer l = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null);
74 Main.main.addLayer(l);
75 Main.map.mapView.setActiveLayer(l);
76 }
77
78 int n = (osmData && gpxData ? 2 : 1)*rects.size();
79 progressMonitor.beginTask(null, n);
80 int i = 0;
81 for (Rectangle2D td : rects) {
82 i++;
83 if (osmData) {
84 addDownloadTask(new DownloadOsmTask(), td, i, n);
85 }
86 if (gpxData) {
87 addDownloadTask(new DownloadGpsTask(), td, i, n);
88 }
89 }
90 progressMonitor.addCancelListener(new CancelListener() {
91 @Override
92 public void operationCanceled() {
93 for (DownloadTask dt : tasks) {
94 dt.cancel();
95 }
96 }
97 });
98 return Main.worker.submit(new PostDownloadProcessor(osmData));
99 }
100
101 /**
102 * Downloads a list of areas from the OSM Server
103 * @param newLayer Set to true if all areas should be put into a single new layer
104 * @param areas The Collection of Areas to download
105 * @param osmData Set to true if OSM data should be downloaded
106 * @param gpxData Set to true if GPX data should be downloaded
107 * @param progressMonitor The progress monitor
108 * @return The Future representing the asynchronous download task
109 */
110 public Future<?> download(boolean newLayer, Collection<Area> areas, boolean osmData, boolean gpxData, ProgressMonitor progressMonitor) {
111 progressMonitor.beginTask(tr("Updating data"));
112 try {
113 List<Rectangle2D> rects = new ArrayList<>(areas.size());
114 for (Area a : areas) {
115 rects.add(a.getBounds2D());
116 }
117
118 return download(newLayer, rects, osmData, gpxData, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
119 } finally {
120 progressMonitor.finishTask();
121 }
122 }
123
124 /**
125 * Replies the set of ids of all complete, non-new primitives (i.e. those with !primitive.incomplete)
126 * @param ds data set
127 *
128 * @return the set of ids of all complete, non-new primitives
129 */
130 protected Set<OsmPrimitive> getCompletePrimitives(DataSet ds) {
131 Set<OsmPrimitive> ret = new HashSet<>();
132 for (OsmPrimitive primitive : ds.allPrimitives()) {
133 if (!primitive.isIncomplete() && !primitive.isNew()) {
134 ret.add(primitive);
135 }
136 }
137 return ret;
138 }
139
140 /**
141 * Updates the local state of a set of primitives (given by a set of primitive ids) with the
142 * state currently held on the server.
143 *
144 * @param potentiallyDeleted a set of ids to check update from the server
145 */
146 protected void updatePotentiallyDeletedPrimitives(Set<OsmPrimitive> potentiallyDeleted) {
147 final List<OsmPrimitive> toSelect = new ArrayList<>();
148 for (OsmPrimitive primitive : potentiallyDeleted) {
149 if (primitive != null) {
150 toSelect.add(primitive);
151 }
152 }
153 EventQueue.invokeLater(new Runnable() {
154 @Override public void run() {
155 UpdateSelectionAction.updatePrimitives(toSelect);
156 }
157 });
158 }
159
160 /**
161 * Processes a set of primitives (given by a set of their ids) which might be deleted on the
162 * server. First prompts the user whether he wants to check the current state on the server. If
163 * yes, retrieves the current state on the server and checks whether the primitives are indeed
164 * deleted on the server.
165 *
166 * @param potentiallyDeleted a set of primitives (given by their ids)
167 */
168 protected void handlePotentiallyDeletedPrimitives(Set<OsmPrimitive> potentiallyDeleted) {
169 ButtonSpec[] options = new ButtonSpec[] {
170 new ButtonSpec(
171 tr("Check on the server"),
172 ImageProvider.get("ok"),
173 tr("Click to check whether objects in your local dataset are deleted on the server"),
174 null /* no specific help topic */
175 ),
176 new ButtonSpec(
177 tr("Ignore"),
178 ImageProvider.get("cancel"),
179 tr("Click to abort and to resume editing"),
180 null /* no specific help topic */
181 ),
182 };
183
184 String message = "<html>" + trn(
185 "There is {0} object in your local dataset which "
186 + "might be deleted on the server.<br>If you later try to delete or "
187 + "update this the server is likely to report a conflict.",
188 "There are {0} objects in your local dataset which "
189 + "might be deleted on the server.<br>If you later try to delete or "
190 + "update them the server is likely to report a conflict.",
191 potentiallyDeleted.size(), potentiallyDeleted.size())
192 + "<br>"
193 + trn("Click <strong>{0}</strong> to check the state of this object on the server.",
194 "Click <strong>{0}</strong> to check the state of these objects on the server.",
195 potentiallyDeleted.size(),
196 options[0].text) + "<br>"
197 + tr("Click <strong>{0}</strong> to ignore." + "</html>", options[1].text);
198
199 int ret = HelpAwareOptionPane.showOptionDialog(
200 Main.parent,
201 message,
202 tr("Deleted or moved objects"),
203 JOptionPane.WARNING_MESSAGE,
204 null,
205 options,
206 options[0],
207 ht("/Action/UpdateData#SyncPotentiallyDeletedObjects")
208 );
209 if (ret != 0 /* OK */)
210 return;
211
212 updatePotentiallyDeletedPrimitives(potentiallyDeleted);
213 }
214
215 /**
216 * Replies the set of primitive ids which have been downloaded by this task list
217 *
218 * @return the set of primitive ids which have been downloaded by this task list
219 */
220 public Set<OsmPrimitive> getDownloadedPrimitives() {
221 Set<OsmPrimitive> ret = new HashSet<>();
222 for (DownloadTask task : tasks) {
223 if (task instanceof DownloadOsmTask) {
224 DataSet ds = ((DownloadOsmTask) task).getDownloadedData();
225 if (ds != null) {
226 ret.addAll(ds.allPrimitives());
227 }
228 }
229 }
230 return ret;
231 }
232
233 class PostDownloadProcessor implements Runnable {
234
235 private final boolean osmData;
236
237 PostDownloadProcessor(boolean osmData) {
238 this.osmData = osmData;
239 }
240
241 /**
242 * Grabs and displays the error messages after all download threads have finished.
243 */
244 @Override
245 public void run() {
246 progressMonitor.finishTask();
247
248 // wait for all download tasks to finish
249 //
250 for (Future<?> future : taskFutures) {
251 try {
252 future.get();
253 } catch (InterruptedException | ExecutionException | CancellationException e) {
254 Main.error(e);
255 return;
256 }
257 }
258 Set<Object> errors = new LinkedHashSet<>();
259 for (DownloadTask dt : tasks) {
260 errors.addAll(dt.getErrorObjects());
261 }
262 if (!errors.isEmpty()) {
263 final Collection<String> items = new ArrayList<>();
264 for (Object error : errors) {
265 if (error instanceof String) {
266 items.add((String) error);
267 } else if (error instanceof Exception) {
268 items.add(ExceptionUtil.explainException((Exception) error));
269 }
270 }
271
272 GuiHelper.runInEDT(new Runnable() {
273 @Override
274 public void run() {
275 if (items.size() == 1 && tr("No data found in this area.").equals(items.iterator().next())) {
276 new Notification(items.iterator().next().toString()).setIcon(JOptionPane.WARNING_MESSAGE).show();
277 } else {
278 JOptionPane.showMessageDialog(Main.parent, "<html>"
279 + tr("The following errors occurred during mass download: {0}",
280 Utils.joinAsHtmlUnorderedList(items)) + "</html>",
281 tr("Errors during download"), JOptionPane.ERROR_MESSAGE);
282 }
283 }
284 });
285
286 return;
287 }
288
289 // FIXME: this is a hack. We assume that the user canceled the whole download if at
290 // least one task was canceled or if it failed
291 //
292 for (DownloadTask task : tasks) {
293 if (task instanceof AbstractDownloadTask) {
294 AbstractDownloadTask<?> absTask = (AbstractDownloadTask<?>) task;
295 if (absTask.isCanceled() || absTask.isFailed())
296 return;
297 }
298 }
299 final OsmDataLayer editLayer = Main.main.getEditLayer();
300 if (editLayer != null && osmData) {
301 final Set<OsmPrimitive> myPrimitives = getCompletePrimitives(editLayer.data);
302 for (DownloadTask task : tasks) {
303 if (task instanceof DownloadOsmTask) {
304 DataSet ds = ((DownloadOsmTask) task).getDownloadedData();
305 if (ds != null) {
306 // myPrimitives.removeAll(ds.allPrimitives()) will do the same job but much slower
307 for (OsmPrimitive primitive: ds.allPrimitives()) {
308 myPrimitives.remove(primitive);
309 }
310 }
311 }
312 }
313 if (!myPrimitives.isEmpty()) {
314 GuiHelper.runInEDT(new Runnable() {
315 @Override public void run() {
316 handlePotentiallyDeletedPrimitives(myPrimitives);
317 }
318 });
319 }
320 }
321 }
322 }
323}
Note: See TracBrowser for help on using the repository browser.