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

Last change on this file since 8713 was 8540, checked in by Don-vip, 9 years ago

fix remaining checkstyle issues

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