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

Last change on this file since 2381 was 2381, checked in by jttt, 14 years ago

Change most occurrences of Dataset.nodes/ways/relations with getNodes()/../.. or addPrimitive

File size: 13.5 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.ActionEvent;
7import java.io.IOException;
8import java.util.Collection;
9import java.util.HashSet;
10import java.util.Set;
11import java.util.Stack;
12import java.util.logging.Logger;
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.OsmPrimitiveType;
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.ExceptionUtil;
34import org.xml.sax.SAXException;
35import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
36
37/**
38 * Uploads the current selection to the server.
39 *
40 */
41public class UploadSelectionAction extends JosmAction{
42 static private Logger logger = Logger.getLogger(UploadSelectionAction.class.getName());
43
44 public UploadSelectionAction() {
45 super(
46 tr("Upload selection"),
47 "uploadselection",
48 tr("Upload all changes in the current selection to the OSM server."),
49 null, /* no shortcut */
50 true);
51 putValue("help", ht("/Action/UploadSelection"));
52 }
53
54 @Override
55 protected void updateEnabledState() {
56 if (getCurrentDataSet() == null) {
57 setEnabled(false);
58 } else {
59 updateEnabledState(getCurrentDataSet().getSelected());
60 }
61 }
62
63 @Override
64 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
65 setEnabled(selection != null && !selection.isEmpty());
66 }
67
68 protected Set<OsmPrimitive> getDeletedPrimitives(DataSet ds) {
69 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
70 for (OsmPrimitive p : ds.getNodes()) {
71 if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
72 ret.add(p);
73 }
74 }
75 for (OsmPrimitive p : ds.getWays()) {
76 if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
77 ret.add(p);
78 }
79 }
80 for (OsmPrimitive p : ds.getRelations()) {
81 if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
82 ret.add(p);
83 }
84 }
85 return ret;
86 }
87
88 protected Set<OsmPrimitive> getModifiedPrimitives(Collection<OsmPrimitive> primitives) {
89 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
90 for (OsmPrimitive p: primitives) {
91 if (p.isNew()) {
92 ret.add(p);
93 } else if (p.isVisible() && p.isModified() && !p.incomplete) {
94 ret.add(p);
95 }
96 }
97 return ret;
98 }
99
100 public void actionPerformed(ActionEvent e) {
101 if (!isEnabled())
102 return;
103 UploadHullBuilder builder = new UploadHullBuilder();
104 UploadSelectionDialog dialog = new UploadSelectionDialog();
105 Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(getEditLayer().data.getSelected());
106 Collection<OsmPrimitive> deletedCandiates = getDeletedPrimitives(getEditLayer().data);
107 if (modifiedCandidates.isEmpty() && deletedCandiates.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 deletedCandiates
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 protected boolean hasPrimitivesToDelete(Collection<OsmPrimitive> primitives) {
137 for (OsmPrimitive p: primitives)
138 if (p.isDeleted() && p.isModified() && !p.isNew())
139 return true;
140 return false;
141 }
142 /**
143 * Uploads the primitives in <code>toUpload</code> to the server. Only
144 * uploads primitives which are either new, modified or deleted.
145 *
146 * Also checks whether <code>toUpload</code> has to be extended with
147 * deleted parents in order to avoid precondition violations on the server.
148 *
149 * @param layer the data layer from which we upload a subset of primitives
150 * @param toUpload the primitives to upload. If null or empty returns immediatelly
151 */
152 public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
153 if (toUpload == null || toUpload.isEmpty()) return;
154 UploadHullBuilder builder = new UploadHullBuilder();
155 toUpload = builder.build(toUpload);
156 if (hasPrimitivesToDelete(toUpload)) {
157 // runs the check for deleted parents and then invokes
158 // processPostParentChecker()
159 //
160 Main.worker.submit(new DeletedParentsChecker(layer, toUpload));
161 } else {
162 processPostParentChecker(layer, toUpload);
163 }
164 }
165
166 protected void processPostParentChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
167 APIDataSet ds = new APIDataSet(toUpload);
168 UploadAction action = new UploadAction();
169 action.uploadData(layer, ds);
170 }
171
172 /**
173 * Computes the collection of primitives to upload, given a collection of candidate
174 * primitives.
175 * Some of the candidates are excluded, i.e. if they aren't modified or if they
176 * aren't visible.
177 * Other primitives are added. A typical case is a primitive which is new and and
178 * which is referred by a modified relation. In order to upload the relation the
179 * new primitive has to be uploaded as well, even if it isn't included in the
180 * list of candidate primitives.
181 *
182 */
183 static class UploadHullBuilder implements Visitor {
184 private Set<OsmPrimitive> hull;
185
186 public UploadHullBuilder(){
187 hull = new HashSet<OsmPrimitive>();
188 }
189
190 public void visit(Node n) {
191 if (n.isNew() || ((n.isModified() || n.isDeleted()) && n.isVisible())) {
192 // upload new nodes as well as modified and deleted ones
193 hull.add(n);
194 }
195 }
196
197 public void visit(Way w) {
198 if (w.isNew() || ((w.isModified() || w.isDeleted()) && w.isVisible())) {
199 // upload new ways as well as modified and deleted ones
200 hull.add(w);
201 for (Node n: w.getNodes()) {
202 // we upload modified nodes even if they aren't in the current
203 // selection.
204 n.visit(this);
205 }
206 }
207 }
208
209 public void visit(Relation r) {
210 if (r.isNew() || ((r.isModified() || r.isDeleted()) && r.isVisible())) {
211 hull.add(r);
212 for (OsmPrimitive p : r.getMemberPrimitives()) {
213 // add new relation members. Don't include modified
214 // relation members. r shouldn't refer to deleted primitives,
215 // so wont check here for deleted primitives here
216 //
217 if (p.isNew()) {
218 p.visit(this);
219 }
220 }
221 }
222 }
223
224 public void visit(Changeset cs) {
225 // do nothing
226 }
227
228 /**
229 * Builds the "hull" of primitives to be uploaded given a base collection
230 * of osm primitives.
231 *
232 * @param base the base collection. Must not be null.
233 * @return the "hull"
234 * @throws IllegalArgumentException thrown if base is null
235 */
236 public Set<OsmPrimitive> build(Collection<OsmPrimitive> base) throws IllegalArgumentException{
237 if (base == null)
238 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "base"));
239 hull = new HashSet<OsmPrimitive>();
240 for (OsmPrimitive p: base) {
241 p.visit(this);
242 }
243 return hull;
244 }
245 }
246
247 class DeletedParentsChecker extends PleaseWaitRunnable {
248 private boolean canceled;
249 private Exception lastException;
250 private Collection<OsmPrimitive> toUpload;
251 private OsmDataLayer layer;
252 private OsmServerBackreferenceReader reader;
253
254 /**
255 *
256 * @param layer the data layer for which a collection of selected primitives is uploaded
257 * @param toUpload the collection of primitives to upload
258 */
259 public DeletedParentsChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
260 super(tr("Checking parents for deleted objects"));
261 this.toUpload = toUpload;
262 this.layer = layer;
263 }
264
265 @Override
266 protected void cancel() {
267 this.canceled = true;
268 synchronized (this) {
269 if (reader != null) {
270 reader.cancel();
271 }
272 }
273 }
274
275 @Override
276 protected void finish() {
277 if (canceled)
278 return;
279 if (lastException != null) {
280 ExceptionUtil.explainException(lastException);
281 return;
282 }
283 Runnable r = new Runnable() {
284 public void run() {
285 processPostParentChecker(layer, toUpload);
286 }
287 };
288 SwingUtilities.invokeLater(r);
289 }
290
291 /**
292 * Replies the collection of deleted OSM primitives for which we have to check whether
293 * there are dangling references on the server.
294 *
295 * @return
296 */
297 protected Set<OsmPrimitive> getPrimitivesToCheckForParents() {
298 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
299 for (OsmPrimitive p: toUpload) {
300 if (p.isDeleted() && !p.isNew()) {
301 ret.add(p);
302 }
303 }
304 return ret;
305 }
306
307 @Override
308 protected void realRun() throws SAXException, IOException, OsmTransferException {
309 try {
310 Stack<OsmPrimitive> toCheck = new Stack<OsmPrimitive>();
311 toCheck.addAll(getPrimitivesToCheckForParents());
312 Set<OsmPrimitive> checked = new HashSet<OsmPrimitive>();
313 while(!toCheck.isEmpty()) {
314 if (canceled) return;
315 OsmPrimitive current = toCheck.pop();
316 synchronized(this) {
317 reader = new OsmServerBackreferenceReader(current);
318 }
319 getProgressMonitor().subTask(tr("Reading parents of ''{0}''", current.getDisplayName(DefaultNameFormatter.getInstance())));
320 DataSet ds = reader.parseOsm(getProgressMonitor().createSubTaskMonitor(1, false));
321 synchronized(this) {
322 reader = null;
323 }
324 checked.add(current);
325 getProgressMonitor().subTask(tr("Checking for deleted parents in the local dataset"));
326 for (OsmPrimitive p: ds.allPrimitives()) {
327 if (canceled) return;
328 OsmPrimitive myDeletedParent = layer.data.getPrimitiveById(p.getId(), OsmPrimitiveType.from(p));
329 // our local dataset includes a deleted parent of a primitive we want
330 // to delete. Include this parent in the collection of uploaded primitives
331 //
332 if (myDeletedParent != null && myDeletedParent.isDeleted()) {
333 if (!toUpload.contains(myDeletedParent)) {
334 toUpload.add(myDeletedParent);
335 }
336 if (!checked.contains(myDeletedParent)) {
337 toCheck.push(myDeletedParent);
338 }
339 }
340 }
341 }
342 } catch(Exception e) {
343 if (canceled)
344 // ignore exception
345 return;
346 lastException = e;
347 }
348 }
349 }
350}
Note: See TracBrowser for help on using the repository browser.