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

Last change on this file since 2399 was 2399, checked in by jttt, 15 years ago

Added map of primitives to dataset to make search by id faster
check if primitive already exist in addPrimitive and removePrimitive
use PrimitiveId instead of id + primitive type

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