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

Last change on this file since 6049 was 6009, checked in by Don-vip, 11 years ago

fix #8799 - Follow conventional visitor design pattern by renaming visit(Visitor) to accept(Visitor)

  • Property svn:eol-style set to native
File size: 13.2 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
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<OsmPrimitive>();
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<OsmPrimitive>();
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 public void actionPerformed(ActionEvent e) {
90 if (!isEnabled())
91 return;
92 if (getEditLayer().isUploadDiscouraged()) {
93 if (UploadAction.warnUploadDiscouraged(getEditLayer())) {
94 return;
95 }
96 }
97 UploadHullBuilder builder = new UploadHullBuilder();
98 UploadSelectionDialog dialog = new UploadSelectionDialog();
99 Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(getEditLayer().data.getAllSelected());
100 Collection<OsmPrimitive> deletedCandidates = getDeletedPrimitives(getEditLayer().data);
101 if (modifiedCandidates.isEmpty() && deletedCandidates.isEmpty()) {
102 JOptionPane.showMessageDialog(
103 Main.parent,
104 tr("No changes to upload."),
105 tr("Warning"),
106 JOptionPane.INFORMATION_MESSAGE
107 );
108 return;
109 }
110 dialog.populate(
111 modifiedCandidates,
112 deletedCandidates
113 );
114 dialog.setVisible(true);
115 if (dialog.isCanceled())
116 return;
117 Collection<OsmPrimitive> toUpload = builder.build(dialog.getSelectedPrimitives());
118 if (toUpload.isEmpty()) {
119 JOptionPane.showMessageDialog(
120 Main.parent,
121 tr("No changes to upload."),
122 tr("Warning"),
123 JOptionPane.INFORMATION_MESSAGE
124 );
125 return;
126 }
127 uploadPrimitives(getEditLayer(), toUpload);
128 }
129
130 /**
131 * Replies true if there is at least one non-new, deleted primitive in
132 * <code>primitives</code>
133 *
134 * @param primitives the primitives to scan
135 * @return true if there is at least one non-new, deleted primitive in
136 * <code>primitives</code>
137 */
138 protected boolean hasPrimitivesToDelete(Collection<OsmPrimitive> primitives) {
139 for (OsmPrimitive p: primitives)
140 if (p.isDeleted() && p.isModified() && !p.isNew())
141 return true;
142 return false;
143 }
144
145 /**
146 * Uploads the primitives in <code>toUpload</code> to the server. Only
147 * uploads primitives which are either new, modified or deleted.
148 *
149 * Also checks whether <code>toUpload</code> has to be extended with
150 * deleted parents in order to avoid precondition violations on the server.
151 *
152 * @param layer the data layer from which we upload a subset of primitives
153 * @param toUpload the primitives to upload. If null or empty returns immediatelly
154 */
155 public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
156 if (toUpload == null || toUpload.isEmpty()) return;
157 UploadHullBuilder builder = new UploadHullBuilder();
158 toUpload = builder.build(toUpload);
159 if (hasPrimitivesToDelete(toUpload)) {
160 // runs the check for deleted parents and then invokes
161 // processPostParentChecker()
162 //
163 Main.worker.submit(new DeletedParentsChecker(layer, toUpload));
164 } else {
165 processPostParentChecker(layer, toUpload);
166 }
167 }
168
169 protected void processPostParentChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
170 APIDataSet ds = new APIDataSet(toUpload);
171 UploadAction action = new UploadAction();
172 action.uploadData(layer, ds);
173 }
174
175 /**
176 * Computes the collection of primitives to upload, given a collection of candidate
177 * primitives.
178 * Some of the candidates are excluded, i.e. if they aren't modified.
179 * Other primitives are added. A typical case is a primitive which is new and and
180 * which is referred by a modified relation. In order to upload the relation the
181 * new primitive has to be uploaded as well, even if it isn't included in the
182 * list of candidate primitives.
183 *
184 */
185 static class UploadHullBuilder implements Visitor {
186 private Set<OsmPrimitive> hull;
187
188 public UploadHullBuilder(){
189 hull = new HashSet<OsmPrimitive>();
190 }
191
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 public void visit(Way w) {
200 if (w.isNewOrUndeleted() || w.isModified() || w.isDeleted()) {
201 // upload new ways as well as modified and deleted ones
202 hull.add(w);
203 for (Node n: w.getNodes()) {
204 // we upload modified nodes even if they aren't in the current
205 // selection.
206 n.accept(this);
207 }
208 }
209 }
210
211 public void visit(Relation r) {
212 if (r.isNewOrUndeleted() || r.isModified() || r.isDeleted()) {
213 hull.add(r);
214 for (OsmPrimitive p : r.getMemberPrimitives()) {
215 // add new relation members. Don't include modified
216 // relation members. r shouldn't refer to deleted primitives,
217 // so wont check here for deleted primitives here
218 //
219 if (p.isNewOrUndeleted()) {
220 p.accept(this);
221 }
222 }
223 }
224 }
225
226 public void visit(Changeset cs) {
227 // do nothing
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 thrown if base is null
237 */
238 public Set<OsmPrimitive> build(Collection<OsmPrimitive> base) throws IllegalArgumentException{
239 CheckParameterUtil.ensureParameterNotNull(base, "base");
240 hull = new HashSet<OsmPrimitive>();
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 Collection<OsmPrimitive> toUpload;
252 private 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 public 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 Runnable r = new Runnable() {
285 public void run() {
286 processPostParentChecker(layer, toUpload);
287 }
288 };
289 SwingUtilities.invokeLater(r);
290 }
291
292 /**
293 * Replies the collection of deleted OSM primitives for which we have to check whether
294 * there are dangling references on the server.
295 *
296 * @return primitives to check
297 */
298 protected Set<OsmPrimitive> getPrimitivesToCheckForParents() {
299 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
300 for (OsmPrimitive p: toUpload) {
301 if (p.isDeleted() && !p.isNewOrUndeleted()) {
302 ret.add(p);
303 }
304 }
305 return ret;
306 }
307
308 @Override
309 protected void realRun() throws SAXException, IOException, OsmTransferException {
310 try {
311 Stack<OsmPrimitive> toCheck = new Stack<OsmPrimitive>();
312 toCheck.addAll(getPrimitivesToCheckForParents());
313 Set<OsmPrimitive> checked = new HashSet<OsmPrimitive>();
314 while(!toCheck.isEmpty()) {
315 if (canceled) return;
316 OsmPrimitive current = toCheck.pop();
317 synchronized(this) {
318 reader = new OsmServerBackreferenceReader(current);
319 }
320 getProgressMonitor().subTask(tr("Reading parents of ''{0}''", current.getDisplayName(DefaultNameFormatter.getInstance())));
321 DataSet ds = reader.parseOsm(getProgressMonitor().createSubTaskMonitor(1, false));
322 synchronized(this) {
323 reader = null;
324 }
325 checked.add(current);
326 getProgressMonitor().subTask(tr("Checking for deleted parents in the local dataset"));
327 for (OsmPrimitive p: ds.allPrimitives()) {
328 if (canceled) return;
329 OsmPrimitive myDeletedParent = layer.data.getPrimitiveById(p);
330 // our local dataset includes a deleted parent of a primitive we want
331 // to delete. Include this parent in the collection of uploaded primitives
332 //
333 if (myDeletedParent != null && myDeletedParent.isDeleted()) {
334 if (!toUpload.contains(myDeletedParent)) {
335 toUpload.add(myDeletedParent);
336 }
337 if (!checked.contains(myDeletedParent)) {
338 toCheck.push(myDeletedParent);
339 }
340 }
341 }
342 }
343 } catch(Exception e) {
344 if (canceled)
345 // ignore exception
346 return;
347 lastException = e;
348 }
349 }
350 }
351}
Note: See TracBrowser for help on using the repository browser.