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

Last change on this file since 13186 was 12809, checked in by bastiK, 7 years ago

replace abstract class AbstractVisitor by interface OsmPrimitiveVisitor; deprecate Visitor

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