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

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

fix squid:S1319 - Declarations should use Java collection interfaces rather than specific implementation classes

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