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

Last change on this file since 10873 was 10601, checked in by Don-vip, 8 years ago

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

  • Property svn:eol-style set to native
File size: 13.1 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[2512]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;
[5711]8import java.awt.event.KeyEvent;
[2512]9import java.io.IOException;
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.Set;
[8856]13import java.util.Stack;
[2512]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;
[2842]33import org.openstreetmap.josm.tools.CheckParameterUtil;
[2512]34import org.openstreetmap.josm.tools.ExceptionUtil;
[5711]35import org.openstreetmap.josm.tools.Shortcut;
[2512]36import org.xml.sax.SAXException;
37
38/**
39 * Uploads the current selection to the server.
[8291]40 * @since 2250
[2512]41 */
[8291]42public class UploadSelectionAction extends JosmAction {
43 /**
44 * Constructs a new {@code UploadSelectionAction}.
45 */
[2512]46 public UploadSelectionAction() {
47 super(
48 tr("Upload selection"),
49 "uploadselection",
50 tr("Upload all changes in the current selection to the OSM server."),
[8540]51 // CHECKSTYLE.OFF: LineLength
[5711]52 Shortcut.registerShortcut("file:uploadSelection", tr("File: {0}", tr("Upload selection")), KeyEvent.VK_U, Shortcut.ALT_CTRL_SHIFT),
[8540]53 // CHECKSTYLE.ON: LineLength
[2512]54 true);
55 putValue("help", ht("/Action/UploadSelection"));
56 }
57
58 @Override
59 protected void updateEnabledState() {
[10548]60 updateEnabledStateOnCurrentSelection();
[2512]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) {
[8338]69 Set<OsmPrimitive> ret = new HashSet<>();
[3206]70 for (OsmPrimitive p: ds.allPrimitives()) {
[2512]71 if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
72 ret.add(p);
73 }
74 }
75 return ret;
76 }
77
78 protected Set<OsmPrimitive> getModifiedPrimitives(Collection<OsmPrimitive> primitives) {
[8338]79 Set<OsmPrimitive> ret = new HashSet<>();
[2512]80 for (OsmPrimitive p: primitives) {
[3336]81 if (p.isNewOrUndeleted()) {
[2512]82 ret.add(p);
[3336]83 } else if (p.isModified() && !p.isIncomplete()) {
[2512]84 ret.add(p);
85 }
86 }
87 return ret;
88 }
89
[6084]90 @Override
[2512]91 public void actionPerformed(ActionEvent e) {
[10382]92 OsmDataLayer editLayer = getLayerManager().getEditLayer();
[2512]93 if (!isEnabled())
94 return;
[10382]95 if (editLayer.isUploadDiscouraged()) {
96 if (UploadAction.warnUploadDiscouraged(editLayer)) {
[5025]97 return;
98 }
99 }
[10382]100 Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(editLayer.data.getAllSelected());
101 Collection<OsmPrimitive> deletedCandidates = getDeletedPrimitives(editLayer.data);
[3426]102 if (modifiedCandidates.isEmpty() && deletedCandidates.isEmpty()) {
[2512]103 JOptionPane.showMessageDialog(
104 Main.parent,
105 tr("No changes to upload."),
106 tr("Warning"),
107 JOptionPane.INFORMATION_MESSAGE
108 );
109 return;
110 }
[9062]111 UploadSelectionDialog dialog = new UploadSelectionDialog();
[2512]112 dialog.populate(
113 modifiedCandidates,
[3426]114 deletedCandidates
[2512]115 );
116 dialog.setVisible(true);
117 if (dialog.isCanceled())
118 return;
[9062]119 Collection<OsmPrimitive> toUpload = new UploadHullBuilder().build(dialog.getSelectedPrimitives());
[2512]120 if (toUpload.isEmpty()) {
121 JOptionPane.showMessageDialog(
122 Main.parent,
123 tr("No changes to upload."),
124 tr("Warning"),
125 JOptionPane.INFORMATION_MESSAGE
126 );
127 return;
128 }
[10382]129 uploadPrimitives(editLayer, toUpload);
[2512]130 }
131
[2979]132 /**
133 * Replies true if there is at least one non-new, deleted primitive in
134 * <code>primitives</code>
[3206]135 *
[2979]136 * @param primitives the primitives to scan
137 * @return true if there is at least one non-new, deleted primitive in
138 * <code>primitives</code>
139 */
[2512]140 protected boolean hasPrimitivesToDelete(Collection<OsmPrimitive> primitives) {
[8513]141 for (OsmPrimitive p: primitives) {
[2512]142 if (p.isDeleted() && p.isModified() && !p.isNew())
143 return true;
[8513]144 }
[2512]145 return false;
146 }
[2979]147
[2512]148 /**
149 * Uploads the primitives in <code>toUpload</code> to the server. Only
150 * uploads primitives which are either new, modified or deleted.
151 *
152 * Also checks whether <code>toUpload</code> has to be extended with
153 * deleted parents in order to avoid precondition violations on the server.
154 *
155 * @param layer the data layer from which we upload a subset of primitives
156 * @param toUpload the primitives to upload. If null or empty returns immediatelly
157 */
158 public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
159 if (toUpload == null || toUpload.isEmpty()) return;
160 UploadHullBuilder builder = new UploadHullBuilder();
161 toUpload = builder.build(toUpload);
162 if (hasPrimitivesToDelete(toUpload)) {
163 // runs the check for deleted parents and then invokes
164 // processPostParentChecker()
165 //
166 Main.worker.submit(new DeletedParentsChecker(layer, toUpload));
167 } else {
168 processPostParentChecker(layer, toUpload);
169 }
170 }
171
172 protected void processPostParentChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
173 APIDataSet ds = new APIDataSet(toUpload);
174 UploadAction action = new UploadAction();
175 action.uploadData(layer, ds);
176 }
177
178 /**
179 * Computes the collection of primitives to upload, given a collection of candidate
180 * primitives.
[3336]181 * Some of the candidates are excluded, i.e. if they aren't modified.
[2512]182 * Other primitives are added. A typical case is a primitive which is new and and
183 * which is referred by a modified relation. In order to upload the relation the
184 * new primitive has to be uploaded as well, even if it isn't included in the
185 * list of candidate primitives.
186 *
187 */
188 static class UploadHullBuilder implements Visitor {
189 private Set<OsmPrimitive> hull;
190
[8836]191 UploadHullBuilder() {
[7005]192 hull = new HashSet<>();
[2512]193 }
194
[6084]195 @Override
[2512]196 public void visit(Node n) {
[3336]197 if (n.isNewOrUndeleted() || n.isModified() || n.isDeleted()) {
[2512]198 // upload new nodes as well as modified and deleted ones
199 hull.add(n);
200 }
201 }
202
[6084]203 @Override
[2512]204 public void visit(Way w) {
[3336]205 if (w.isNewOrUndeleted() || w.isModified() || w.isDeleted()) {
[2512]206 // upload new ways as well as modified and deleted ones
207 hull.add(w);
208 for (Node n: w.getNodes()) {
209 // we upload modified nodes even if they aren't in the current
210 // selection.
[6009]211 n.accept(this);
[2512]212 }
213 }
214 }
215
[6084]216 @Override
[2512]217 public void visit(Relation r) {
[3336]218 if (r.isNewOrUndeleted() || r.isModified() || r.isDeleted()) {
[2512]219 hull.add(r);
220 for (OsmPrimitive p : r.getMemberPrimitives()) {
221 // add new relation members. Don't include modified
222 // relation members. r shouldn't refer to deleted primitives,
223 // so wont check here for deleted primitives here
224 //
[3336]225 if (p.isNewOrUndeleted()) {
[6009]226 p.accept(this);
[2512]227 }
228 }
229 }
230 }
231
[6084]232 @Override
[2512]233 public void visit(Changeset cs) {
234 // do nothing
235 }
236
237 /**
238 * Builds the "hull" of primitives to be uploaded given a base collection
239 * of osm primitives.
240 *
241 * @param base the base collection. Must not be null.
242 * @return the "hull"
[8291]243 * @throws IllegalArgumentException if base is null
[2512]244 */
[8291]245 public Set<OsmPrimitive> build(Collection<OsmPrimitive> base) {
[2842]246 CheckParameterUtil.ensureParameterNotNull(base, "base");
[7005]247 hull = new HashSet<>();
[2512]248 for (OsmPrimitive p: base) {
[6009]249 p.accept(this);
[2512]250 }
251 return hull;
252 }
253 }
254
255 class DeletedParentsChecker extends PleaseWaitRunnable {
256 private boolean canceled;
257 private Exception lastException;
[9067]258 private final Collection<OsmPrimitive> toUpload;
259 private final OsmDataLayer layer;
[2512]260 private OsmServerBackreferenceReader reader;
261
262 /**
263 *
264 * @param layer the data layer for which a collection of selected primitives is uploaded
265 * @param toUpload the collection of primitives to upload
266 */
[8836]267 DeletedParentsChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
[2512]268 super(tr("Checking parents for deleted objects"));
269 this.toUpload = toUpload;
270 this.layer = layer;
271 }
272
273 @Override
274 protected void cancel() {
275 this.canceled = true;
276 synchronized (this) {
277 if (reader != null) {
278 reader.cancel();
279 }
280 }
281 }
282
283 @Override
284 protected void finish() {
285 if (canceled)
286 return;
287 if (lastException != null) {
288 ExceptionUtil.explainException(lastException);
289 return;
290 }
[10601]291 SwingUtilities.invokeLater(() -> processPostParentChecker(layer, toUpload));
[2512]292 }
293
294 /**
295 * Replies the collection of deleted OSM primitives for which we have to check whether
296 * there are dangling references on the server.
297 *
[5909]298 * @return primitives to check
[2512]299 */
300 protected Set<OsmPrimitive> getPrimitivesToCheckForParents() {
[8338]301 Set<OsmPrimitive> ret = new HashSet<>();
[2512]302 for (OsmPrimitive p: toUpload) {
[3336]303 if (p.isDeleted() && !p.isNewOrUndeleted()) {
[2512]304 ret.add(p);
305 }
306 }
307 return ret;
308 }
309
310 @Override
311 protected void realRun() throws SAXException, IOException, OsmTransferException {
312 try {
[8856]313 Stack<OsmPrimitive> toCheck = new Stack<>();
[2512]314 toCheck.addAll(getPrimitivesToCheckForParents());
[7005]315 Set<OsmPrimitive> checked = new HashSet<>();
[8510]316 while (!toCheck.isEmpty()) {
[2512]317 if (canceled) return;
318 OsmPrimitive current = toCheck.pop();
[8510]319 synchronized (this) {
[2512]320 reader = new OsmServerBackreferenceReader(current);
321 }
322 getProgressMonitor().subTask(tr("Reading parents of ''{0}''", current.getDisplayName(DefaultNameFormatter.getInstance())));
323 DataSet ds = reader.parseOsm(getProgressMonitor().createSubTaskMonitor(1, false));
[8510]324 synchronized (this) {
[2512]325 reader = null;
326 }
327 checked.add(current);
328 getProgressMonitor().subTask(tr("Checking for deleted parents in the local dataset"));
329 for (OsmPrimitive p: ds.allPrimitives()) {
330 if (canceled) return;
331 OsmPrimitive myDeletedParent = layer.data.getPrimitiveById(p);
332 // our local dataset includes a deleted parent of a primitive we want
333 // to delete. Include this parent in the collection of uploaded primitives
334 if (myDeletedParent != null && myDeletedParent.isDeleted()) {
335 if (!toUpload.contains(myDeletedParent)) {
336 toUpload.add(myDeletedParent);
337 }
338 if (!checked.contains(myDeletedParent)) {
339 toCheck.push(myDeletedParent);
340 }
341 }
342 }
343 }
[10234]344 } catch (OsmTransferException e) {
[2512]345 if (canceled)
346 // ignore exception
347 return;
348 lastException = e;
349 }
350 }
351 }
352}
Note: See TracBrowser for help on using the repository browser.