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

Revision 5025, 13.0 KB checked in by Don-vip, 3 months ago (diff)

see #4043 - Have an 'upload prohibited' flag in .osm files

  • Property svn:eol-style set to native
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;
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.Relation;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.data.osm.visitor.Visitor;
26import org.openstreetmap.josm.gui.DefaultNameFormatter;
27import org.openstreetmap.josm.gui.PleaseWaitRunnable;
28import org.openstreetmap.josm.gui.io.UploadSelectionDialog;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
31import org.openstreetmap.josm.io.OsmTransferException;
32import org.openstreetmap.josm.tools.CheckParameterUtil;
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    public UploadSelectionAction() {
42        super(
43                tr("Upload selection"),
44                "uploadselection",
45                tr("Upload all changes in the current selection to the OSM server."),
46                null, /* no shortcut */
47                true);
48        putValue("help", ht("/Action/UploadSelection"));
49    }
50
51    @Override
52    protected void updateEnabledState() {
53        if (getCurrentDataSet() == null) {
54            setEnabled(false);
55        } else {
56            updateEnabledState(getCurrentDataSet().getSelected());
57        }
58    }
59
60    @Override
61    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
62        setEnabled(selection != null && !selection.isEmpty());
63    }
64
65    protected Set<OsmPrimitive> getDeletedPrimitives(DataSet ds) {
66        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
67        for (OsmPrimitive p: ds.allPrimitives()) {
68            if (p.isDeleted() && !p.isNew() && p.isVisible() && p.isModified()) {
69                ret.add(p);
70            }
71        }
72        return ret;
73    }
74
75    protected Set<OsmPrimitive> getModifiedPrimitives(Collection<OsmPrimitive> primitives) {
76        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
77        for (OsmPrimitive p: primitives) {
78            if (p.isNewOrUndeleted()) {
79                ret.add(p);
80            } else if (p.isModified() && !p.isIncomplete()) {
81                ret.add(p);
82            }
83        }
84        return ret;
85    }
86
87    public void actionPerformed(ActionEvent e) {
88        if (!isEnabled())
89            return;
90        if (getEditLayer().isUploadDiscouraged()) {
91            if (UploadAction.warnUploadDiscouraged(getEditLayer())) {
92                return;
93            }
94        }
95        UploadHullBuilder builder = new UploadHullBuilder();
96        UploadSelectionDialog dialog = new UploadSelectionDialog();
97        Collection<OsmPrimitive> modifiedCandidates = getModifiedPrimitives(getEditLayer().data.getSelected());
98        Collection<OsmPrimitive> deletedCandidates = getDeletedPrimitives(getEditLayer().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        dialog.populate(
109                modifiedCandidates,
110                deletedCandidates
111        );
112        dialog.setVisible(true);
113        if (dialog.isCanceled())
114            return;
115        Collection<OsmPrimitive> toUpload = builder.build(dialog.getSelectedPrimitives());
116        if (toUpload.isEmpty()) {
117            JOptionPane.showMessageDialog(
118                    Main.parent,
119                    tr("No changes to upload."),
120                    tr("Warning"),
121                    JOptionPane.INFORMATION_MESSAGE
122            );
123            return;
124        }
125        uploadPrimitives(getEditLayer(), toUpload);
126    }
127
128    /**
129     * Replies true if there is at least one non-new, deleted primitive in
130     * <code>primitives</code>
131     *
132     * @param primitives the primitives to scan
133     * @return true if there is at least one non-new, deleted primitive in
134     * <code>primitives</code>
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    /**
144     * Uploads the primitives in <code>toUpload</code> to the server. Only
145     * uploads primitives which are either new, modified or deleted.
146     *
147     * Also checks whether <code>toUpload</code> has to be extended with
148     * deleted parents in order to avoid precondition violations on the server.
149     *
150     * @param layer the data layer from which we upload a subset of primitives
151     * @param toUpload the primitives to upload. If null or empty returns immediatelly
152     */
153    public void uploadPrimitives(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
154        if (toUpload == null || toUpload.isEmpty()) return;
155        UploadHullBuilder builder = new UploadHullBuilder();
156        toUpload = builder.build(toUpload);
157        if (hasPrimitivesToDelete(toUpload)) {
158            // runs the check for deleted parents and then invokes
159            // processPostParentChecker()
160            //
161            Main.worker.submit(new DeletedParentsChecker(layer, toUpload));
162        } else {
163            processPostParentChecker(layer, toUpload);
164        }
165    }
166
167    protected void processPostParentChecker(OsmDataLayer layer, Collection<OsmPrimitive> toUpload) {
168        APIDataSet ds = new APIDataSet(toUpload);
169        UploadAction action = new UploadAction();
170        action.uploadData(layer, ds);
171    }
172
173    /**
174     * Computes the collection of primitives to upload, given a collection of candidate
175     * primitives.
176     * Some of the candidates are excluded, i.e. if they aren't modified.
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.isNewOrUndeleted() || n.isModified() || n.isDeleted()) {
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.isNewOrUndeleted() || w.isModified() || w.isDeleted()) {
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.isNewOrUndeleted() || r.isModified() || r.isDeleted()) {
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.isNewOrUndeleted()) {
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            CheckParameterUtil.ensureParameterNotNull(base, "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.isNewOrUndeleted()) {
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.