source: josm/trunk/src/org/openstreetmap/josm/actions/UploadAction.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: 13.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.HashMap;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Map;
13import java.util.Optional;
14import java.util.concurrent.CompletableFuture;
15import java.util.concurrent.ExecutionException;
16import java.util.concurrent.Future;
17import java.util.function.Consumer;
18
19import javax.swing.JOptionPane;
20
21import org.openstreetmap.josm.actions.upload.ApiPreconditionCheckerHook;
22import org.openstreetmap.josm.actions.upload.DiscardTagsHook;
23import org.openstreetmap.josm.actions.upload.FixDataHook;
24import org.openstreetmap.josm.actions.upload.RelationUploadOrderHook;
25import org.openstreetmap.josm.actions.upload.UploadHook;
26import org.openstreetmap.josm.actions.upload.ValidateUploadHook;
27import org.openstreetmap.josm.data.APIDataSet;
28import org.openstreetmap.josm.data.conflict.ConflictCollection;
29import org.openstreetmap.josm.data.osm.Changeset;
30import org.openstreetmap.josm.gui.HelpAwareOptionPane;
31import org.openstreetmap.josm.gui.MainApplication;
32import org.openstreetmap.josm.gui.Notification;
33import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
34import org.openstreetmap.josm.gui.io.UploadDialog;
35import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
36import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
37import org.openstreetmap.josm.gui.layer.OsmDataLayer;
38import org.openstreetmap.josm.gui.util.GuiHelper;
39import org.openstreetmap.josm.io.ChangesetUpdater;
40import org.openstreetmap.josm.io.UploadStrategySpecification;
41import org.openstreetmap.josm.spi.preferences.Config;
42import org.openstreetmap.josm.tools.ImageProvider;
43import org.openstreetmap.josm.tools.JosmRuntimeException;
44import org.openstreetmap.josm.tools.Logging;
45import org.openstreetmap.josm.tools.Shortcut;
46import org.openstreetmap.josm.tools.Utils;
47
48/**
49 * Action that opens a connection to the osm server and uploads all changes.
50 * <p>
51 * A dialog is displayed asking the user to specify a rectangle to grab.
52 * The url and account settings from the preferences are used.
53 * <p>
54 * If the upload fails, this action offers various options to resolve conflicts.
55 *
56 * @author imi
57 */
58public class UploadAction extends AbstractUploadAction {
59 /**
60 * The list of upload hooks. These hooks will be called one after the other
61 * when the user wants to upload data. Plugins can insert their own hooks here
62 * if they want to be able to veto an upload.
63 * <p>
64 * By default, the standard upload dialog is the only element in the list.
65 * Plugins should normally insert their code before that, so that the upload
66 * dialog is the last thing shown before upload really starts; on occasion
67 * however, a plugin might also want to insert something after that.
68 */
69 private static final List<UploadHook> UPLOAD_HOOKS = new LinkedList<>();
70 private static final List<UploadHook> LATE_UPLOAD_HOOKS = new LinkedList<>();
71
72 private static final String IS_ASYNC_UPLOAD_ENABLED = "asynchronous.upload";
73
74 static {
75 /*
76 * Calls validator before upload.
77 */
78 UPLOAD_HOOKS.add(new ValidateUploadHook());
79
80 /*
81 * Fixes database errors
82 */
83 UPLOAD_HOOKS.add(new FixDataHook());
84
85 /*
86 * Checks server capabilities before upload.
87 */
88 UPLOAD_HOOKS.add(new ApiPreconditionCheckerHook());
89
90 /*
91 * Adjusts the upload order of new relations
92 */
93 UPLOAD_HOOKS.add(new RelationUploadOrderHook());
94
95 /*
96 * Removes discardable tags like created_by on modified objects
97 */
98 LATE_UPLOAD_HOOKS.add(new DiscardTagsHook());
99 }
100
101 /**
102 * Registers an upload hook. Adds the hook at the first position of the upload hooks.
103 *
104 * @param hook the upload hook. Ignored if null.
105 */
106 public static void registerUploadHook(UploadHook hook) {
107 registerUploadHook(hook, false);
108 }
109
110 /**
111 * Registers an upload hook. Adds the hook at the first position of the upload hooks.
112 *
113 * @param hook the upload hook. Ignored if null.
114 * @param late true, if the hook should be executed after the upload dialog
115 * has been confirmed. Late upload hooks should in general succeed and not
116 * abort the upload.
117 */
118 public static void registerUploadHook(UploadHook hook, boolean late) {
119 if (hook == null) return;
120 if (late) {
121 if (!LATE_UPLOAD_HOOKS.contains(hook)) {
122 LATE_UPLOAD_HOOKS.add(0, hook);
123 }
124 } else {
125 if (!UPLOAD_HOOKS.contains(hook)) {
126 UPLOAD_HOOKS.add(0, hook);
127 }
128 }
129 }
130
131 /**
132 * Unregisters an upload hook. Removes the hook from the list of upload hooks.
133 *
134 * @param hook the upload hook. Ignored if null.
135 */
136 public static void unregisterUploadHook(UploadHook hook) {
137 if (hook == null) return;
138 UPLOAD_HOOKS.remove(hook);
139 LATE_UPLOAD_HOOKS.remove(hook);
140 }
141
142 /**
143 * Constructs a new {@code UploadAction}.
144 */
145 public UploadAction() {
146 super(tr("Upload data..."), "upload", tr("Upload all changes in the active data layer to the OSM server"),
147 Shortcut.registerShortcut("file:upload", tr("File: {0}", tr("Upload data")), KeyEvent.VK_UP, Shortcut.CTRL_SHIFT), true);
148 setHelpId(ht("/Action/Upload"));
149 }
150
151 @Override
152 protected boolean listenToSelectionChange() {
153 return false;
154 }
155
156 @Override
157 protected void updateEnabledState() {
158 OsmDataLayer editLayer = getLayerManager().getEditLayer();
159 setEnabled(editLayer != null && editLayer.requiresUploadToServer());
160 }
161
162 /**
163 * Check whether the preconditions are met to upload data from a given layer, if applicable.
164 * @param layer layer to check
165 * @return {@code true} if the preconditions are met, or not applicable
166 * @see #checkPreUploadConditions(AbstractModifiableLayer, APIDataSet)
167 */
168 public static boolean checkPreUploadConditions(AbstractModifiableLayer layer) {
169 return checkPreUploadConditions(layer,
170 layer instanceof OsmDataLayer ? new APIDataSet(((OsmDataLayer) layer).getDataSet()) : null);
171 }
172
173 protected static void alertUnresolvedConflicts(OsmDataLayer layer) {
174 HelpAwareOptionPane.showOptionDialog(
175 MainApplication.getMainFrame(),
176 tr("<html>The data to be uploaded participates in unresolved conflicts of layer ''{0}''.<br>"
177 + "You have to resolve them first.</html>", Utils.escapeReservedCharactersHTML(layer.getName())
178 ),
179 tr("Warning"),
180 JOptionPane.WARNING_MESSAGE,
181 ht("/Action/Upload#PrimitivesParticipateInConflicts")
182 );
183 }
184
185 /**
186 * Warn user about discouraged upload, propose to cancel operation.
187 * @param layer incriminated layer
188 * @return true if the user wants to cancel, false if they want to continue
189 */
190 public static boolean warnUploadDiscouraged(AbstractModifiableLayer layer) {
191 return GuiHelper.warnUser(tr("Upload discouraged"),
192 "<html>" +
193 tr("You are about to upload data from the layer ''{0}''.<br /><br />"+
194 "Sending data from this layer is <b>strongly discouraged</b>. If you continue,<br />"+
195 "it may require you subsequently have to revert your changes, or force other contributors to.<br /><br />"+
196 "Are you sure you want to continue?", Utils.escapeReservedCharactersHTML(layer.getName()))+
197 "</html>",
198 ImageProvider.get("upload"), tr("Ignore this hint and upload anyway"));
199 }
200
201 /**
202 * Check whether the preconditions are met to upload data in {@code apiData}.
203 * Makes sure upload is allowed, primitives in {@code apiData} don't participate in conflicts and
204 * runs the installed {@link UploadHook}s.
205 *
206 * @param layer the source layer of the data to be uploaded
207 * @param apiData the data to be uploaded
208 * @return true, if the preconditions are met; false, otherwise
209 */
210 public static boolean checkPreUploadConditions(AbstractModifiableLayer layer, APIDataSet apiData) {
211 try {
212 return checkPreUploadConditionsAsync(layer, apiData, null).get();
213 } catch (InterruptedException e) {
214 Thread.currentThread().interrupt();
215 throw new JosmRuntimeException(e);
216 } catch (ExecutionException e) {
217 throw new JosmRuntimeException(e);
218 }
219 }
220
221 /**
222 * Check whether the preconditions are met to upload data in {@code apiData}.
223 * Makes sure upload is allowed, primitives in {@code apiData} don't participate in conflicts and
224 * runs the installed {@link UploadHook}s.
225 *
226 * @param layer the source layer of the data to be uploaded
227 * @param apiData the data to be uploaded
228 * @param onFinish {@code true} if the preconditions are met; {@code false}, otherwise
229 * @return A future that completes when the checks are finished
230 * @since 18752
231 */
232 private static Future<Boolean> checkPreUploadConditionsAsync(AbstractModifiableLayer layer, APIDataSet apiData, Consumer<Boolean> onFinish) {
233 final CompletableFuture<Boolean> future = new CompletableFuture<>();
234 if (onFinish != null) {
235 future.thenAccept(onFinish);
236 }
237 if (layer.isUploadDiscouraged() && warnUploadDiscouraged(layer)) {
238 future.complete(false);
239 } else if (layer instanceof OsmDataLayer) {
240 OsmDataLayer osmLayer = (OsmDataLayer) layer;
241 ConflictCollection conflicts = osmLayer.getConflicts();
242 if (apiData.participatesInConflict(conflicts)) {
243 alertUnresolvedConflicts(osmLayer);
244 future.complete(false);
245 }
246 }
247 // Call all upload hooks in sequence.
248 if (!future.isDone()) {
249 MainApplication.worker.execute(() -> {
250 boolean hooks = true;
251 if (apiData != null) {
252 hooks = UPLOAD_HOOKS.stream().allMatch(hook -> hook.checkUpload(apiData));
253 }
254 future.complete(hooks);
255 });
256 }
257 return future;
258 }
259
260 /**
261 * Uploads data to the OSM API.
262 *
263 * @param layer the source layer for the data to upload
264 * @param apiData the primitives to be added, updated, or deleted
265 */
266 public void uploadData(final OsmDataLayer layer, APIDataSet apiData) {
267 if (apiData.isEmpty()) {
268 new Notification(tr("No changes to upload.")).show();
269 return;
270 }
271 checkPreUploadConditionsAsync(layer, apiData, passed -> GuiHelper.runInEDT(() -> {
272 if (Boolean.TRUE.equals(passed)) {
273 realUploadData(layer, apiData);
274 } else {
275 new Notification(tr("One of the upload verification processes failed")).show();
276 }
277 }));
278 }
279
280 /**
281 * Uploads data to the OSM API.
282 *
283 * @param layer the source layer for the data to upload
284 * @param apiData the primitives to be added, updated, or deleted
285 */
286 private static void realUploadData(final OsmDataLayer layer, final APIDataSet apiData) {
287
288 ChangesetUpdater.check();
289
290 final UploadDialog dialog = UploadDialog.getUploadDialog();
291 dialog.setUploadedPrimitives(apiData);
292 dialog.initLifeCycle(layer.getDataSet());
293 Map<String, String> changesetTags = dialog.getChangeset().getKeys();
294 Map<String, String> originalChangesetTags = new HashMap<>(changesetTags);
295 for (UploadHook hook : UPLOAD_HOOKS) {
296 hook.modifyChangesetTags(changesetTags);
297 }
298 dialog.getModel().putAll(changesetTags);
299 if (!originalChangesetTags.equals(changesetTags)) {
300 dialog.setChangesetTagsModifiedProgramatically();
301 }
302 dialog.setVisible(true);
303 dialog.rememberUserInput();
304 if (dialog.isCanceled()) {
305 dialog.clean();
306 return;
307 }
308
309 for (UploadHook hook : LATE_UPLOAD_HOOKS) {
310 if (!hook.checkUpload(apiData)) {
311 dialog.clean();
312 return;
313 }
314 }
315
316 // Any hooks want to change the changeset tags?
317 Changeset cs = dialog.getChangeset();
318 changesetTags = cs.getKeys();
319 for (UploadHook hook : LATE_UPLOAD_HOOKS) {
320 hook.modifyChangesetTags(changesetTags);
321 }
322
323 UploadStrategySpecification uploadStrategySpecification = dialog.getUploadStrategySpecification();
324 Logging.info("Starting upload with tags {0}", changesetTags);
325 Logging.info(uploadStrategySpecification.toString());
326 Logging.info(cs.toString());
327 dialog.clean();
328
329 if (Config.getPref().getBoolean(IS_ASYNC_UPLOAD_ENABLED, true)) {
330 Optional<AsynchronousUploadPrimitivesTask> asyncUploadTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(
331 uploadStrategySpecification, layer, apiData, cs);
332
333 asyncUploadTask.ifPresent(MainApplication.worker::execute);
334 } else {
335 MainApplication.worker.execute(new UploadPrimitivesTask(uploadStrategySpecification, layer, apiData, cs));
336 }
337 }
338
339 @Override
340 public void actionPerformed(ActionEvent e) {
341 if (!isEnabled())
342 return;
343 if (MainApplication.getMap() == null) {
344 new Notification(tr("Nothing to upload. Get some data first.")).show();
345 return;
346 }
347 APIDataSet apiData = new APIDataSet(getLayerManager().getEditDataSet());
348 uploadData(getLayerManager().getEditLayer(), apiData);
349 }
350}
Note: See TracBrowser for help on using the repository browser.