source: josm/trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java

Last change on this file was 18871, checked in by taylor.smock, 6 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

  • Property svn:eol-style set to native
File size: 18.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.remotecontrol.handler;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.awt.geom.Rectangle2D;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.LinkedHashSet;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15import java.util.concurrent.ExecutionException;
16import java.util.concurrent.Future;
17import java.util.concurrent.TimeUnit;
18import java.util.concurrent.TimeoutException;
19import java.util.stream.Collectors;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.actions.AutoScaleAction;
24import org.openstreetmap.josm.actions.AutoScaleAction.AutoScaleMode;
25import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
26import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
27import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
28import org.openstreetmap.josm.data.Bounds;
29import org.openstreetmap.josm.data.coor.LatLon;
30import org.openstreetmap.josm.data.osm.BBox;
31import org.openstreetmap.josm.data.osm.DataSet;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.Relation;
34import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
35import org.openstreetmap.josm.data.osm.search.SearchCompiler;
36import org.openstreetmap.josm.data.osm.search.SearchParseError;
37import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
38import org.openstreetmap.josm.gui.ExceptionDialogUtil;
39import org.openstreetmap.josm.gui.MainApplication;
40import org.openstreetmap.josm.gui.MapFrame;
41import org.openstreetmap.josm.gui.Notification;
42import org.openstreetmap.josm.gui.util.GuiHelper;
43import org.openstreetmap.josm.io.OsmApiException;
44import org.openstreetmap.josm.io.OsmTransferException;
45import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
46import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
47import org.openstreetmap.josm.tools.Logging;
48import org.openstreetmap.josm.tools.SubclassFilteredCollection;
49import org.openstreetmap.josm.tools.Utils;
50
51/**
52 * Handler for {@code load_and_zoom} and {@code zoom} requests.
53 * @since 3707
54 */
55public class LoadAndZoomHandler extends RequestHandler {
56
57 /**
58 * The remote control command name used to load data and zoom.
59 */
60 public static final String command = "load_and_zoom";
61
62 /**
63 * The remote control command name used to zoom.
64 */
65 public static final String command2 = "zoom";
66 private static final String CURRENT_SELECTION = "currentselection";
67
68 // Mandatory arguments
69 private double minlat;
70 private double maxlat;
71 private double minlon;
72 private double maxlon;
73
74 // Optional argument 'select'
75 private final Set<SimplePrimitiveId> toSelect = new LinkedHashSet<>();
76
77 private boolean isKeepingCurrentSelection;
78
79 @Override
80 public String getPermissionMessage() {
81 String msg = tr("Remote Control has been asked to load data from the API.") +
82 "<br>" + tr("Bounding box: ") + new BBox(minlon, minlat, maxlon, maxlat).toStringCSV(", ");
83 if (args.containsKey("select") && !toSelect.isEmpty()) {
84 msg += "<br>" + tr("Selection: {0}", toSelect.size());
85 }
86 return msg;
87 }
88
89 @Override
90 public String[] getMandatoryParams() {
91 return new String[] {"bottom", "top", "left", "right"};
92 }
93
94 @Override
95 public String[] getOptionalParams() {
96 return new String[] {"new_layer", "layer_name", "addtags", "select", "zoom_mode",
97 "changeset_comment", "changeset_source", "changeset_hashtags", "changeset_tags",
98 "search", "layer_locked", "download_policy", "upload_policy"};
99 }
100
101 @Override
102 public String getUsage() {
103 return "download a bounding box from the API, zoom to the downloaded area and optionally select one or more objects";
104 }
105
106 @Override
107 public String[] getUsageExamples() {
108 return getUsageExamples(myCommand);
109 }
110
111 @Override
112 public String[] getUsageExamples(String cmd) {
113 if (command.equals(cmd)) {
114 return new String[] {
115 "/load_and_zoom?addtags=wikipedia:de=Wei%C3%9Fe_Gasse|maxspeed=5&select=way23071688,way23076176,way23076177," +
116 "&left=13.740&right=13.741&top=51.05&bottom=51.049",
117 "/load_and_zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999&new_layer=true"};
118 } else {
119 return new String[] {
120 "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999",
121 "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=highway+OR+railway",
122 "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=" + CURRENT_SELECTION + "&addtags=foo=bar",
123 };
124 }
125 }
126
127 @Override
128 protected void handleRequest() throws RequestHandlerErrorException {
129 DownloadOsmTask osmTask = new DownloadOsmTask();
130 try {
131 DownloadParams settings = getDownloadParams();
132
133 if (command.equals(myCommand)) {
134 if (!PermissionPrefWithDefault.LOAD_DATA.isAllowed()) {
135 Logging.info("RemoteControl: download forbidden by preferences");
136 } else {
137 Area toDownload = null;
138 if (!settings.isNewLayer()) {
139 // find out whether some data has already been downloaded
140 Area present = null;
141 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
142 if (ds != null) {
143 present = ds.getDataSourceArea();
144 }
145 if (present != null && !present.isEmpty()) {
146 toDownload = new Area(new Rectangle2D.Double(minlon, minlat, maxlon-minlon, maxlat-minlat));
147 toDownload.subtract(present);
148 if (!toDownload.isEmpty()) {
149 // the result might not be a rectangle (L shaped etc)
150 Rectangle2D downloadBounds = toDownload.getBounds2D();
151 minlat = downloadBounds.getMinY();
152 minlon = downloadBounds.getMinX();
153 maxlat = downloadBounds.getMaxY();
154 maxlon = downloadBounds.getMaxX();
155 }
156 }
157 }
158 if (toDownload != null && toDownload.isEmpty()) {
159 Logging.info("RemoteControl: no download necessary");
160 } else {
161 Future<?> future = MainApplication.worker.submit(
162 new PostDownloadHandler(osmTask, osmTask.download(settings, new Bounds(minlat, minlon, maxlat, maxlon),
163 null /* let the task manage the progress monitor */)));
164 GuiHelper.executeByMainWorkerInEDT(() -> {
165 try {
166 future.get(OSM_DOWNLOAD_TIMEOUT.get(), TimeUnit.SECONDS);
167 if (osmTask.isFailed()) {
168 Object error = osmTask.getErrorObjects().get(0);
169 if (error instanceof OsmApiException) {
170 throw (OsmApiException) error;
171 }
172 List<Throwable> exceptions = osmTask.getErrorObjects().stream()
173 .filter(Throwable.class::isInstance).map(Throwable.class::cast)
174 .collect(Collectors.toList());
175 OsmTransferException osmTransferException =
176 new OsmTransferException(String.join(", ", osmTask.getErrorMessages()));
177 if (!exceptions.isEmpty()) {
178 osmTransferException.initCause(exceptions.get(0));
179 exceptions.remove(0);
180 exceptions.forEach(osmTransferException::addSuppressed);
181 }
182 throw osmTransferException;
183 }
184 } catch (InterruptedException | ExecutionException | TimeoutException |
185 OsmTransferException | RuntimeException ex) { // NOPMD
186 ExceptionDialogUtil.explainException(ex);
187 }
188 });
189 }
190 }
191 }
192 } catch (RuntimeException ex) { // NOPMD
193 Logging.warn("RemoteControl: Error parsing load_and_zoom remote control request:");
194 Logging.error(ex);
195 throw new RequestHandlerErrorException(ex);
196 }
197
198 /*
199 * deselect objects if parameter addtags given
200 */
201 if (args.containsKey("addtags") && !isKeepingCurrentSelection) {
202 GuiHelper.executeByMainWorkerInEDT(() -> {
203 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
204 if (ds == null) // e.g. download failed
205 return;
206 ds.clearSelection();
207 });
208 }
209
210 final Collection<OsmPrimitive> forTagAdd = new LinkedHashSet<>();
211 final Bounds bbox = new Bounds(minlat, minlon, maxlat, maxlon);
212 if (args.containsKey("select") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
213 // select objects after downloading, zoom to selection.
214 GuiHelper.executeByMainWorkerInEDT(() -> {
215 Set<OsmPrimitive> newSel = new LinkedHashSet<>();
216 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
217 if (ds == null) // e.g. download failed
218 return;
219 for (SimplePrimitiveId id : toSelect) {
220 final OsmPrimitive p = ds.getPrimitiveById(id);
221 if (p != null) {
222 newSel.add(p);
223 forTagAdd.add(p);
224 }
225 }
226 if (isKeepingCurrentSelection) {
227 Collection<OsmPrimitive> sel = ds.getSelected();
228 newSel.addAll(sel);
229 forTagAdd.addAll(sel);
230 }
231 toSelect.clear();
232 ds.setSelected(newSel);
233 zoom(newSel, bbox);
234 MapFrame map = MainApplication.getMap();
235 if (MainApplication.isDisplayingMapView() && map.relationListDialog != null) {
236 map.relationListDialog.selectRelations(null); // unselect all relations to fix #7342
237 map.relationListDialog.dataChanged(null);
238 map.relationListDialog.selectRelations(Utils.filteredCollection(newSel, Relation.class));
239 }
240 });
241 } else if (args.containsKey("search") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
242 try {
243 final SearchCompiler.Match search = SearchCompiler.compile(args.get("search"));
244 MainApplication.worker.submit(() -> {
245 final DataSet ds = MainApplication.getLayerManager().getEditDataSet();
246 final Collection<OsmPrimitive> filteredPrimitives = SubclassFilteredCollection.filter(ds.allPrimitives(), search);
247 ds.setSelected(filteredPrimitives);
248 forTagAdd.addAll(filteredPrimitives);
249 zoom(filteredPrimitives, bbox);
250 });
251 } catch (SearchParseError ex) {
252 Logging.error(ex);
253 throw new RequestHandlerErrorException(ex);
254 }
255 } else {
256 // after downloading, zoom to downloaded area.
257 zoom(Collections.<OsmPrimitive>emptySet(), bbox);
258 }
259
260 // This comes before the other changeset tags, so that they can be overridden
261 parseChangesetTags(args);
262
263 // add changeset tags after download if necessary
264 if (args.containsKey("changeset_comment") || args.containsKey("changeset_source") || args.containsKey("changeset_hashtags")) {
265 MainApplication.worker.submit(() -> {
266 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
267 if (ds != null) {
268 for (String tag : Arrays.asList("changeset_comment", "changeset_source", "changeset_hashtags")) {
269 if (args.containsKey(tag)) {
270 final String tagKey = tag.substring("changeset_".length());
271 final String value = args.get(tag);
272 if (!Utils.isStripEmpty(value)) {
273 ds.addChangeSetTag(tagKey, value);
274 } else {
275 ds.addChangeSetTag(tagKey, null);
276 }
277 }
278 }
279 }
280 });
281 }
282
283 // add tags to objects
284 if (args.containsKey("addtags")) {
285 // needs to run in EDT since forTagAdd is updated in EDT as well
286 GuiHelper.executeByMainWorkerInEDT(() -> {
287 if (!forTagAdd.isEmpty()) {
288 AddTagsDialog.addTags(args, sender, forTagAdd);
289 } else {
290 new Notification(isKeepingCurrentSelection
291 ? tr("You clicked on a JOSM remotecontrol link that would apply tags onto selected objects.\n"
292 + "Since no objects have been selected before this click, no tags were added.\n"
293 + "Select one or more objects and click the link again.")
294 : tr("You clicked on a JOSM remotecontrol link that would apply tags onto objects.\n"
295 + "Unfortunately that link seems to be broken.\n"
296 + "Technical explanation: the URL query parameter ''select='' or ''search='' has an invalid value.\n"
297 + "Ask someone at the origin of the clicked link to fix this.")
298 ).setIcon(JOptionPane.WARNING_MESSAGE).setDuration(Notification.TIME_LONG).show();
299 }
300 });
301 }
302 }
303
304 static void parseChangesetTags(Map<String, String> args) {
305 if (args.containsKey("changeset_tags")) {
306 MainApplication.worker.submit(() -> {
307 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
308 if (ds != null) {
309 AddTagsDialog.parseUrlTagsToKeyValues(args.get("changeset_tags")).forEach(ds::addChangeSetTag);
310 }
311 });
312 }
313 }
314
315 protected void zoom(Collection<OsmPrimitive> primitives, final Bounds bbox) {
316 if (!PermissionPrefWithDefault.CHANGE_VIEWPORT.isAllowed()) {
317 return;
318 }
319 // zoom_mode=(download|selection), defaults to selection
320 if (!"download".equals(args.get("zoom_mode")) && !primitives.isEmpty()) {
321 AutoScaleAction.autoScale(AutoScaleMode.SELECTION);
322 } else if (MainApplication.isDisplayingMapView()) {
323 // make sure this isn't called unless there *is* a MapView
324 GuiHelper.executeByMainWorkerInEDT(() -> {
325 BoundingXYVisitor bbox1 = new BoundingXYVisitor();
326 bbox1.visit(bbox);
327 MainApplication.getMap().mapView.zoomTo(bbox1);
328 });
329 }
330 }
331
332 @Override
333 public PermissionPrefWithDefault getPermissionPref() {
334 return null;
335 }
336
337 @Override
338 protected void validateRequest() throws RequestHandlerBadRequestException {
339 validateDownloadParams();
340 // Process mandatory arguments
341 minlat = 0;
342 maxlat = 0;
343 minlon = 0;
344 maxlon = 0;
345 try {
346 minlat = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("bottom") : ""));
347 maxlat = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("top") : ""));
348 minlon = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("left") : ""));
349 maxlon = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("right") : ""));
350 } catch (NumberFormatException e) {
351 throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+')', e);
352 }
353
354 // Current API 0.6 check: "The latitudes must be between -90 and 90"
355 if (!LatLon.isValidLat(minlat) || !LatLon.isValidLat(maxlat)) {
356 throw new RequestHandlerBadRequestException(tr("The latitudes must be between {0} and {1}", -90d, 90d));
357 }
358 // Current API 0.6 check: "longitudes between -180 and 180"
359 if (!LatLon.isValidLon(minlon) || !LatLon.isValidLon(maxlon)) {
360 throw new RequestHandlerBadRequestException(tr("The longitudes must be between {0} and {1}", -180d, 180d));
361 }
362 // Current API 0.6 check: "the minima must be less than the maxima"
363 if (minlat > maxlat || minlon > maxlon) {
364 throw new RequestHandlerBadRequestException(tr("The minima must be less than the maxima"));
365 }
366
367 // Process optional argument 'select'
368 if (args != null && args.containsKey("select")) {
369 toSelect.clear();
370 for (String item : args.get("select").split(",", -1)) {
371 if (!item.isEmpty()) {
372 if (CURRENT_SELECTION.equalsIgnoreCase(item)) {
373 isKeepingCurrentSelection = true;
374 continue;
375 }
376 try {
377 toSelect.add(SimplePrimitiveId.fromString(item));
378 } catch (IllegalArgumentException ex) {
379 Logging.log(Logging.LEVEL_WARN, "RemoteControl: invalid selection '" + item + "' ignored", ex);
380 }
381 }
382 }
383 }
384 }
385}
Note: See TracBrowser for help on using the repository browser.