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

Last change on this file since 12749 was 12663, checked in by Don-vip, 7 years ago

see #15182 - move NameFormatter* from gui to data.osm

  • Property svn:eol-style set to native
File size: 13.0 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.DefaultNameFormatter;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Relation;
26import org.openstreetmap.josm.data.osm.Way;
27import org.openstreetmap.josm.data.osm.visitor.Visitor;
28import org.openstreetmap.josm.gui.MainApplication;
29import org.openstreetmap.josm.gui.PleaseWaitRunnable;
30import org.openstreetmap.josm.gui.io.UploadSelectionDialog;
31import org.openstreetmap.josm.gui.layer.OsmDataLayer;
32import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
33import org.openstreetmap.josm.io.OsmTransferException;
34import org.openstreetmap.josm.tools.CheckParameterUtil;
35import org.openstreetmap.josm.tools.ExceptionUtil;
36import org.openstreetmap.josm.tools.Shortcut;
37import org.xml.sax.SAXException;
38
39/**
40 * Uploads the current selection to the server.
41 * @since 2250
42 */
43public class UploadSelectionAction extends JosmAction {
44 /**
45 * Constructs a new {@code UploadSelectionAction}.
46 */
47 public UploadSelectionAction() {
48 super(
49 tr("Upload selection"),
50 "uploadselection",
51 tr("Upload all changes in the current selection to the OSM server."),
52 // CHECKSTYLE.OFF: LineLength
53 Shortcut.registerShortcut("file:uploadSelection", tr("File: {0}", tr("Upload selection")), KeyEvent.VK_U, Shortcut.ALT_CTRL_SHIFT),
54 // CHECKSTYLE.ON: LineLength
55 true);
56 putValue("help", ht("/Action/UploadSelection"));
57 }
58
59 @Override
60 protected void updateEnabledState() {
61 updateEnabledStateOnCurrentSelection();
62 }
63
64 @Override
65 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
66 setEnabled(selection != null && !selection.isEmpty());
67 }
68
69 protected Set<OsmPrimitive> getDeletedPrimitives(DataSet ds) {
70 Set<OsmPrimitive> ret = new HashSet<>();
71 for (OsmPrimitive p: ds.allPrimitives()) {
72 if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
73 ret.add(p);
74 }
75 }
76 return ret;
77 }
78
79 protected Set<OsmPrimitive> getModifiedPrimitives(Collection<OsmPrimitive> primitives) {
80 Set<OsmPrimitive> ret = new HashSet<>();
81 for (OsmPrimitive p: primitives) {
82 if (p.isNewOrUndeleted() || (p.isModified() && !p.isIncomplete())) {
83 ret.add(p);
84 }
85 }
86 return ret;
87 }
88
89 @Override
90 public void actionPerformed(ActionEvent e) {
91 OsmDataLayer editLayer = getLayerManager().getEditLayer();
92 if (!isEnabled())
93 return;
94 if (editLayer.isUploadDiscouraged() && UploadAction.warnUploadDiscouraged(editLayer)) {
95 return;
96 }
97 Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(editLayer.data.getAllSelected());
98 Collection<OsmPrimitive> deletedCandidates = getDeletedPrimitives(editLayer.data);
99 if (modifiedCandidates.isEmpty() && deletedCandidates.isEmpty()) {
100 JOptionPane.showMessageDialog(
101 Main.parent,
102 tr("No changes to upload."),
103 tr("Warning"),
104 JOptionPane.INFORMATION_MESSAGE
105 );
106 return;
107 }
108 UploadSelectionDialog dialog = new UploadSelectionDialog();
109 dialog.populate(
110 modifiedCandidates,
111 deletedCandidates
112 );
113 dialog.setVisible(true);
114 if (dialog.isCanceled())
115 return;
116 Collection<OsmPrimitive> toUpload = new UploadHullBuilder().build(dialog.getSelectedPrimitives());
117 if (toUpload.isEmpty()) {
118 JOptionPane.showMessageDialog(
119 Main.parent,
120 tr("No changes to upload."),
121 tr("Warning"),
122 JOptionPane.INFORMATION_MESSAGE
123 );
124 return;
125 }
126 uploadPrimitives(editLayer, toUpload);
127 }
128
129 /**
130 * Replies true if there is at least one non-new, deleted primitive in
131 * <code>primitives</code>
132 *
133 * @param primitives the primitives to scan
134 * @return true if there is at least one non-new, deleted primitive in
135 * <code>primitives</code>
136 */
137 protected boolean hasPrimitivesToDelete(Collection<OsmPrimitive> primitives) {
138 for (OsmPrimitive p: primitives) {
139 if (p.isDeleted() && p.isModified() && !p.isNew())
140 return true;
141 }
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 MainApplication.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 UploadHullBuilder() {
189 hull = new HashSet<>();
190 }
191
192 @Override
193 public void visit(Node n) {
194 if (n.isNewOrUndeleted() || n.isModified() || n.isDeleted()) {
195 // upload new nodes as well as modified and deleted ones
196 hull.add(n);
197 }
198 }
199
200 @Override
201 public void visit(Way w) {
202 if (w.isNewOrUndeleted() || w.isModified() || w.isDeleted()) {
203 // upload new ways as well as modified and deleted ones
204 hull.add(w);
205 for (Node n: w.getNodes()) {
206 // we upload modified nodes even if they aren't in the current
207 // selection.
208 n.accept(this);
209 }
210 }
211 }
212
213 @Override
214 public void visit(Relation r) {
215 if (r.isNewOrUndeleted() || r.isModified() || r.isDeleted()) {
216 hull.add(r);
217 for (OsmPrimitive p : r.getMemberPrimitives()) {
218 // add new relation members. Don't include modified
219 // relation members. r shouldn't refer to deleted primitives,
220 // so wont check here for deleted primitives here
221 //
222 if (p.isNewOrUndeleted()) {
223 p.accept(this);
224 }
225 }
226 }
227 }
228
229 @Override
230 public void visit(Changeset cs) {
231 // do nothing
232 }
233
234 /**
235 * Builds the "hull" of primitives to be uploaded given a base collection
236 * of osm primitives.
237 *
238 * @param base the base collection. Must not be null.
239 * @return the "hull"
240 * @throws IllegalArgumentException if base is null
241 */
242 public Set<OsmPrimitive> build(Collection<OsmPrimitive> base) {
243 CheckParameterUtil.ensureParameterNotNull(base, "base");
244 hull = new HashSet<>();
245 for (OsmPrimitive p: base) {
246 p.accept(this);
247 }
248 return hull;
249 }
250 }
251
252 class DeletedParentsChecker extends PleaseWaitRunnable {
253 private boolean canceled;
254 private Exception lastException;
255 private final Collection<OsmPrimitive> toUpload;
256 private final OsmDataLayer layer;
257 private OsmServerBackreferenceReader reader;
258
259 /**
260 *
261 * @param layer the data layer for which a collection of selected primitives is uploaded
262 * @param toUpload the collection of primitives to upload
263 */
264 DeletedParentsChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
265 super(tr("Checking parents for deleted objects"));
266 this.toUpload = toUpload;
267 this.layer = layer;
268 }
269
270 @Override
271 protected void cancel() {
272 this.canceled = true;
273 synchronized (this) {
274 if (reader != null) {
275 reader.cancel();
276 }
277 }
278 }
279
280 @Override
281 protected void finish() {
282 if (canceled)
283 return;
284 if (lastException != null) {
285 ExceptionUtil.explainException(lastException);
286 return;
287 }
288 SwingUtilities.invokeLater(() -> processPostParentChecker(layer, toUpload));
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 primitives to check
296 */
297 protected Set<OsmPrimitive> getPrimitivesToCheckForParents() {
298 Set<OsmPrimitive> ret = new HashSet<>();
299 for (OsmPrimitive p: toUpload) {
300 if (p.isDeleted() && !p.isNewOrUndeleted()) {
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<>();
311 toCheck.addAll(getPrimitivesToCheckForParents());
312 Set<OsmPrimitive> checked = new HashSet<>();
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);
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 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 (OsmTransferException 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.