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

Last change on this file since 7226 was 7005, checked in by Don-vip, 10 years ago

see #8465 - use diamond operator where applicable

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