source: josm/trunk/src/org/openstreetmap/josm/actions/UploadSelectionAction.java@ 14693

Last change on this file since 14693 was 14397, checked in by Don-vip, 5 years ago

fix #16935 - simplify/cleanup help topics of ToggleDialog/ToggleDialogAction

  • Property svn:eol-style set to native
File size: 13.1 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.io.IOException;
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.Set;
13import java.util.Stack;
14
15import javax.swing.JOptionPane;
16import javax.swing.SwingUtilities;
17
18import org.openstreetmap.josm.data.APIDataSet;
19import org.openstreetmap.josm.data.osm.DataSet;
20import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
26import org.openstreetmap.josm.gui.MainApplication;
27import org.openstreetmap.josm.gui.PleaseWaitRunnable;
28import org.openstreetmap.josm.gui.io.UploadSelectionDialog;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
31import org.openstreetmap.josm.io.OsmTransferException;
32import org.openstreetmap.josm.tools.CheckParameterUtil;
33import org.openstreetmap.josm.tools.ExceptionUtil;
34import org.openstreetmap.josm.tools.Shortcut;
35import org.xml.sax.SAXException;
36
37/**
38 * Uploads the current selection to the server.
39 * @since 2250
40 */
41public class UploadSelectionAction extends JosmAction {
42 /**
43 * Constructs a new {@code UploadSelectionAction}.
44 */
45 public UploadSelectionAction() {
46 super(
47 tr("Upload selection..."),
48 "uploadselection",
49 tr("Upload all changes in the current selection to the OSM server."),
50 // CHECKSTYLE.OFF: LineLength
51 Shortcut.registerShortcut("file:uploadSelection", tr("File: {0}", tr("Upload selection")), KeyEvent.VK_U, Shortcut.ALT_CTRL_SHIFT),
52 // CHECKSTYLE.ON: LineLength
53 true);
54 setHelpId(ht("/Action/UploadSelection"));
55 }
56
57 @Override
58 protected void updateEnabledState() {
59 updateEnabledStateOnCurrentSelection();
60 }
61
62 @Override
63 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
64 updateEnabledStateOnModifiableSelection(selection);
65 OsmDataLayer editLayer = getLayerManager().getEditLayer();
66 if (editLayer != null && !editLayer.isUploadable()) {
67 setEnabled(false);
68 }
69 }
70
71 protected Set<OsmPrimitive> getDeletedPrimitives(DataSet ds) {
72 Set<OsmPrimitive> ret = new HashSet<>();
73 for (OsmPrimitive p: ds.allPrimitives()) {
74 if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
75 ret.add(p);
76 }
77 }
78 return ret;
79 }
80
81 protected Set<OsmPrimitive> getModifiedPrimitives(Collection<OsmPrimitive> primitives) {
82 Set<OsmPrimitive> ret = new HashSet<>();
83 for (OsmPrimitive p: primitives) {
84 if (p.isNewOrUndeleted() || (p.isModified() && !p.isIncomplete())) {
85 ret.add(p);
86 }
87 }
88 return ret;
89 }
90
91 @Override
92 public void actionPerformed(ActionEvent e) {
93 OsmDataLayer editLayer = getLayerManager().getEditLayer();
94 if (!isEnabled() || !editLayer.isUploadable())
95 return;
96 if (editLayer.isUploadDiscouraged() && UploadAction.warnUploadDiscouraged(editLayer)) {
97 return;
98 }
99 Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(editLayer.data.getAllSelected());
100 Collection<OsmPrimitive> deletedCandidates = getDeletedPrimitives(editLayer.getDataSet());
101 if (modifiedCandidates.isEmpty() && deletedCandidates.isEmpty()) {
102 JOptionPane.showMessageDialog(
103 MainApplication.getMainFrame(),
104 tr("No changes to upload."),
105 tr("Warning"),
106 JOptionPane.INFORMATION_MESSAGE
107 );
108 return;
109 }
110 UploadSelectionDialog dialog = new UploadSelectionDialog();
111 dialog.populate(
112 modifiedCandidates,
113 deletedCandidates
114 );
115 dialog.setVisible(true);
116 if (dialog.isCanceled())
117 return;
118 Collection<OsmPrimitive> toUpload = new UploadHullBuilder().build(dialog.getSelectedPrimitives());
119 if (toUpload.isEmpty()) {
120 JOptionPane.showMessageDialog(
121 MainApplication.getMainFrame(),
122 tr("No changes to upload."),
123 tr("Warning"),
124 JOptionPane.INFORMATION_MESSAGE
125 );
126 return;
127 }
128 uploadPrimitives(editLayer, toUpload);
129 }
130
131 /**
132 * Replies true if there is at least one non-new, deleted primitive in
133 * <code>primitives</code>
134 *
135 * @param primitives the primitives to scan
136 * @return true if there is at least one non-new, deleted primitive in
137 * <code>primitives</code>
138 */
139 protected boolean hasPrimitivesToDelete(Collection<OsmPrimitive> primitives) {
140 for (OsmPrimitive p: primitives) {
141 if (p.isDeleted() && p.isModified() && !p.isNew())
142 return true;
143 }
144 return false;
145 }
146
147 /**
148 * Uploads the primitives in <code>toUpload</code> to the server. Only
149 * uploads primitives which are either new, modified or deleted.
150 *
151 * Also checks whether <code>toUpload</code> has to be extended with
152 * deleted parents in order to avoid precondition violations on the server.
153 *
154 * @param layer the data layer from which we upload a subset of primitives
155 * @param toUpload the primitives to upload. If null or empty returns immediatelly
156 */
157 public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
158 if (toUpload == null || toUpload.isEmpty()) return;
159 UploadHullBuilder builder = new UploadHullBuilder();
160 toUpload = builder.build(toUpload);
161 if (hasPrimitivesToDelete(toUpload)) {
162 // runs the check for deleted parents and then invokes
163 // processPostParentChecker()
164 //
165 MainApplication.worker.submit(new DeletedParentsChecker(layer, toUpload));
166 } else {
167 processPostParentChecker(layer, toUpload);
168 }
169 }
170
171 protected void processPostParentChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
172 APIDataSet ds = new APIDataSet(toUpload);
173 UploadAction action = new UploadAction();
174 action.uploadData(layer, ds);
175 }
176
177 /**
178 * Computes the collection of primitives to upload, given a collection of candidate
179 * primitives.
180 * Some of the candidates are excluded, i.e. if they aren't modified.
181 * Other primitives are added. A typical case is a primitive which is new and and
182 * which is referred by a modified relation. In order to upload the relation the
183 * new primitive has to be uploaded as well, even if it isn't included in the
184 * list of candidate primitives.
185 *
186 */
187 static class UploadHullBuilder implements OsmPrimitiveVisitor {
188 private Set<OsmPrimitive> hull;
189
190 UploadHullBuilder() {
191 hull = new HashSet<>();
192 }
193
194 @Override
195 public void visit(Node n) {
196 if (n.isNewOrUndeleted() || n.isModified() || n.isDeleted()) {
197 // upload new nodes as well as modified and deleted ones
198 hull.add(n);
199 }
200 }
201
202 @Override
203 public void visit(Way w) {
204 if (w.isNewOrUndeleted() || w.isModified() || w.isDeleted()) {
205 // upload new ways as well as modified and deleted ones
206 hull.add(w);
207 for (Node n: w.getNodes()) {
208 // we upload modified nodes even if they aren't in the current selection.
209 n.accept(this);
210 }
211 }
212 }
213
214 @Override
215 public void visit(Relation r) {
216 if (r.isNewOrUndeleted() || r.isModified() || r.isDeleted()) {
217 hull.add(r);
218 for (OsmPrimitive p : r.getMemberPrimitives()) {
219 // add new relation members. Don't include modified
220 // relation members. r shouldn't refer to deleted primitives,
221 // so wont check here for deleted primitives here
222 //
223 if (p.isNewOrUndeleted()) {
224 p.accept(this);
225 }
226 }
227 }
228 }
229
230 /**
231 * Builds the "hull" of primitives to be uploaded given a base collection
232 * of osm primitives.
233 *
234 * @param base the base collection. Must not be null.
235 * @return the "hull"
236 * @throws IllegalArgumentException if base is null
237 */
238 public Set<OsmPrimitive> build(Collection<OsmPrimitive> base) {
239 CheckParameterUtil.ensureParameterNotNull(base, "base");
240 hull = new HashSet<>();
241 for (OsmPrimitive p: base) {
242 p.accept(this);
243 }
244 return hull;
245 }
246 }
247
248 class DeletedParentsChecker extends PleaseWaitRunnable {
249 private boolean canceled;
250 private Exception lastException;
251 private final Collection<OsmPrimitive> toUpload;
252 private final OsmDataLayer layer;
253 private OsmServerBackreferenceReader reader;
254
255 /**
256 *
257 * @param layer the data layer for which a collection of selected primitives is uploaded
258 * @param toUpload the collection of primitives to upload
259 */
260 DeletedParentsChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
261 super(tr("Checking parents for deleted objects"));
262 this.toUpload = toUpload;
263 this.layer = layer;
264 }
265
266 @Override
267 protected void cancel() {
268 this.canceled = true;
269 synchronized (this) {
270 if (reader != null) {
271 reader.cancel();
272 }
273 }
274 }
275
276 @Override
277 protected void finish() {
278 if (canceled)
279 return;
280 if (lastException != null) {
281 ExceptionUtil.explainException(lastException);
282 return;
283 }
284 SwingUtilities.invokeLater(() -> processPostParentChecker(layer, toUpload));
285 }
286
287 /**
288 * Replies the collection of deleted OSM primitives for which we have to check whether
289 * there are dangling references on the server.
290 *
291 * @return primitives to check
292 */
293 protected Set<OsmPrimitive> getPrimitivesToCheckForParents() {
294 Set<OsmPrimitive> ret = new HashSet<>();
295 for (OsmPrimitive p: toUpload) {
296 if (p.isDeleted() && !p.isNewOrUndeleted()) {
297 ret.add(p);
298 }
299 }
300 return ret;
301 }
302
303 @Override
304 protected void realRun() throws SAXException, IOException, OsmTransferException {
305 try {
306 Stack<OsmPrimitive> toCheck = new Stack<>();
307 toCheck.addAll(getPrimitivesToCheckForParents());
308 Set<OsmPrimitive> checked = new HashSet<>();
309 while (!toCheck.isEmpty()) {
310 if (canceled) return;
311 OsmPrimitive current = toCheck.pop();
312 synchronized (this) {
313 reader = new OsmServerBackreferenceReader(current);
314 }
315 getProgressMonitor().subTask(tr("Reading parents of ''{0}''", current.getDisplayName(DefaultNameFormatter.getInstance())));
316 DataSet ds = reader.parseOsm(getProgressMonitor().createSubTaskMonitor(1, false));
317 synchronized (this) {
318 reader = null;
319 }
320 checked.add(current);
321 getProgressMonitor().subTask(tr("Checking for deleted parents in the local dataset"));
322 for (OsmPrimitive p: ds.allPrimitives()) {
323 if (canceled) return;
324 OsmPrimitive myDeletedParent = layer.data.getPrimitiveById(p);
325 // our local dataset includes a deleted parent of a primitive we want
326 // to delete. Include this parent in the collection of uploaded primitives
327 if (myDeletedParent != null && myDeletedParent.isDeleted()) {
328 if (!toUpload.contains(myDeletedParent)) {
329 toUpload.add(myDeletedParent);
330 }
331 if (!checked.contains(myDeletedParent)) {
332 toCheck.push(myDeletedParent);
333 }
334 }
335 }
336 }
337 } catch (OsmTransferException e) {
338 if (canceled)
339 // ignore exception
340 return;
341 lastException = e;
342 }
343 }
344 }
345}
Note: See TracBrowser for help on using the repository browser.