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

Last change on this file since 15152 was 15152, checked in by Don-vip, 5 years ago

more uses of Java 8 stream API

  • Property svn:eol-style set to native
File size: 12.9 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;
14import java.util.stream.Collectors;
15
16import javax.swing.JOptionPane;
17import javax.swing.SwingUtilities;
18
19import org.openstreetmap.josm.data.APIDataSet;
20import org.openstreetmap.josm.data.osm.DataSet;
21import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
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.OsmPrimitiveVisitor;
27import org.openstreetmap.josm.gui.MainApplication;
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.CheckParameterUtil;
34import org.openstreetmap.josm.tools.ExceptionUtil;
35import org.openstreetmap.josm.tools.Shortcut;
36import org.xml.sax.SAXException;
37
38/**
39 * Uploads the current selection to the server.
40 * @since 2250
41 */
42public class UploadSelectionAction extends JosmAction {
43 /**
44 * Constructs a new {@code UploadSelectionAction}.
45 */
46 public UploadSelectionAction() {
47 super(
48 tr("Upload selection..."),
49 "uploadselection",
50 tr("Upload all changes in the current selection to the OSM server."),
51 // CHECKSTYLE.OFF: LineLength
52 Shortcut.registerShortcut("file:uploadSelection", tr("File: {0}", tr("Upload selection")), KeyEvent.VK_U, Shortcut.ALT_CTRL_SHIFT),
53 // CHECKSTYLE.ON: LineLength
54 true);
55 setHelpId(ht("/Action/UploadSelection"));
56 }
57
58 @Override
59 protected void updateEnabledState() {
60 updateEnabledStateOnCurrentSelection();
61 }
62
63 @Override
64 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
65 updateEnabledStateOnModifiableSelection(selection);
66 OsmDataLayer editLayer = getLayerManager().getEditLayer();
67 if (editLayer != null && !editLayer.isUploadable()) {
68 setEnabled(false);
69 }
70 }
71
72 protected Set<OsmPrimitive> getDeletedPrimitives(DataSet ds) {
73 return ds.allPrimitives().stream()
74 .filter(p -> p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified())
75 .collect(Collectors.toSet());
76 }
77
78 protected Set<OsmPrimitive> getModifiedPrimitives(Collection<OsmPrimitive> primitives) {
79 return primitives.stream()
80 .filter(p -> p.isNewOrUndeleted() || (p.isModified() && !p.isIncomplete()))
81 .collect(Collectors.toSet());
82 }
83
84 @Override
85 public void actionPerformed(ActionEvent e) {
86 OsmDataLayer editLayer = getLayerManager().getEditLayer();
87 if (!isEnabled() || !editLayer.isUploadable())
88 return;
89 if (editLayer.isUploadDiscouraged() && UploadAction.warnUploadDiscouraged(editLayer)) {
90 return;
91 }
92 Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(editLayer.data.getAllSelected());
93 Collection<OsmPrimitive> deletedCandidates = getDeletedPrimitives(editLayer.getDataSet());
94 if (modifiedCandidates.isEmpty() && deletedCandidates.isEmpty()) {
95 JOptionPane.showMessageDialog(
96 MainApplication.getMainFrame(),
97 tr("No changes to upload."),
98 tr("Warning"),
99 JOptionPane.INFORMATION_MESSAGE
100 );
101 return;
102 }
103 UploadSelectionDialog dialog = new UploadSelectionDialog();
104 dialog.populate(
105 modifiedCandidates,
106 deletedCandidates
107 );
108 dialog.setVisible(true);
109 if (dialog.isCanceled())
110 return;
111 Collection<OsmPrimitive> toUpload = new UploadHullBuilder().build(dialog.getSelectedPrimitives());
112 if (toUpload.isEmpty()) {
113 JOptionPane.showMessageDialog(
114 MainApplication.getMainFrame(),
115 tr("No changes to upload."),
116 tr("Warning"),
117 JOptionPane.INFORMATION_MESSAGE
118 );
119 return;
120 }
121 uploadPrimitives(editLayer, toUpload);
122 }
123
124 /**
125 * Replies true if there is at least one non-new, deleted primitive in
126 * <code>primitives</code>
127 *
128 * @param primitives the primitives to scan
129 * @return true if there is at least one non-new, deleted primitive in
130 * <code>primitives</code>
131 */
132 protected boolean hasPrimitivesToDelete(Collection<OsmPrimitive> primitives) {
133 for (OsmPrimitive p: primitives) {
134 if (p.isDeleted() && p.isModified() && !p.isNew())
135 return true;
136 }
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 MainApplication.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 OsmPrimitiveVisitor {
181 private Set<OsmPrimitive> hull;
182
183 UploadHullBuilder() {
184 hull = new HashSet<>();
185 }
186
187 @Override
188 public void visit(Node n) {
189 if (n.isNewOrUndeleted() || n.isModified() || n.isDeleted()) {
190 // upload new nodes as well as modified and deleted ones
191 hull.add(n);
192 }
193 }
194
195 @Override
196 public void visit(Way w) {
197 if (w.isNewOrUndeleted() || w.isModified() || w.isDeleted()) {
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 selection.
202 n.accept(this);
203 }
204 }
205 }
206
207 @Override
208 public void visit(Relation r) {
209 if (r.isNewOrUndeleted() || r.isModified() || r.isDeleted()) {
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.isNewOrUndeleted()) {
217 p.accept(this);
218 }
219 }
220 }
221 }
222
223 /**
224 * Builds the "hull" of primitives to be uploaded given a base collection
225 * of osm primitives.
226 *
227 * @param base the base collection. Must not be null.
228 * @return the "hull"
229 * @throws IllegalArgumentException if base is null
230 */
231 public Set<OsmPrimitive> build(Collection<OsmPrimitive> base) {
232 CheckParameterUtil.ensureParameterNotNull(base, "base");
233 hull = new HashSet<>();
234 for (OsmPrimitive p: base) {
235 p.accept(this);
236 }
237 return hull;
238 }
239 }
240
241 class DeletedParentsChecker extends PleaseWaitRunnable {
242 private boolean canceled;
243 private Exception lastException;
244 private final Collection<OsmPrimitive> toUpload;
245 private final OsmDataLayer layer;
246 private OsmServerBackreferenceReader reader;
247
248 /**
249 *
250 * @param layer the data layer for which a collection of selected primitives is uploaded
251 * @param toUpload the collection of primitives to upload
252 */
253 DeletedParentsChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
254 super(tr("Checking parents for deleted objects"));
255 this.toUpload = toUpload;
256 this.layer = layer;
257 }
258
259 @Override
260 protected void cancel() {
261 this.canceled = true;
262 synchronized (this) {
263 if (reader != null) {
264 reader.cancel();
265 }
266 }
267 }
268
269 @Override
270 protected void finish() {
271 if (canceled)
272 return;
273 if (lastException != null) {
274 ExceptionUtil.explainException(lastException);
275 return;
276 }
277 SwingUtilities.invokeLater(() -> processPostParentChecker(layer, toUpload));
278 }
279
280 /**
281 * Replies the collection of deleted OSM primitives for which we have to check whether
282 * there are dangling references on the server.
283 *
284 * @return primitives to check
285 */
286 protected Set<OsmPrimitive> getPrimitivesToCheckForParents() {
287 return toUpload.stream().filter(p -> p.isDeleted() && !p.isNewOrUndeleted()).collect(Collectors.toSet());
288 }
289
290 @Override
291 protected void realRun() throws SAXException, IOException, OsmTransferException {
292 try {
293 Stack<OsmPrimitive> toCheck = new Stack<>();
294 toCheck.addAll(getPrimitivesToCheckForParents());
295 Set<OsmPrimitive> checked = new HashSet<>();
296 while (!toCheck.isEmpty()) {
297 if (canceled) return;
298 OsmPrimitive current = toCheck.pop();
299 synchronized (this) {
300 reader = new OsmServerBackreferenceReader(current);
301 }
302 getProgressMonitor().subTask(tr("Reading parents of ''{0}''", current.getDisplayName(DefaultNameFormatter.getInstance())));
303 DataSet ds = reader.parseOsm(getProgressMonitor().createSubTaskMonitor(1, false));
304 synchronized (this) {
305 reader = null;
306 }
307 checked.add(current);
308 getProgressMonitor().subTask(tr("Checking for deleted parents in the local dataset"));
309 for (OsmPrimitive p: ds.allPrimitives()) {
310 if (canceled) return;
311 OsmPrimitive myDeletedParent = layer.data.getPrimitiveById(p);
312 // our local dataset includes a deleted parent of a primitive we want
313 // to delete. Include this parent in the collection of uploaded primitives
314 if (myDeletedParent != null && myDeletedParent.isDeleted()) {
315 if (!toUpload.contains(myDeletedParent)) {
316 toUpload.add(myDeletedParent);
317 }
318 if (!checked.contains(myDeletedParent)) {
319 toCheck.push(myDeletedParent);
320 }
321 }
322 }
323 }
324 } catch (OsmTransferException e) {
325 if (canceled)
326 // ignore exception
327 return;
328 lastException = e;
329 }
330 }
331 }
332}
Note: See TracBrowser for help on using the repository browser.