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

Last change on this file since 3779 was 3426, checked in by stoecker, 14 years ago

see #5303 - some cleanup patches from matthew Bell - partially applied only

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