001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.io;
003
004import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
005
006import java.io.IOException;
007import java.util.Set;
008
009import org.openstreetmap.josm.actions.AutoScaleAction;
010import org.openstreetmap.josm.data.osm.DataSet;
011import org.openstreetmap.josm.data.osm.DataSetMerger;
012import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
013import org.openstreetmap.josm.data.osm.PrimitiveId;
014import org.openstreetmap.josm.data.osm.Relation;
015import org.openstreetmap.josm.data.osm.Way;
016import org.openstreetmap.josm.gui.ExceptionDialogUtil;
017import org.openstreetmap.josm.gui.MainApplication;
018import org.openstreetmap.josm.gui.PleaseWaitRunnable;
019import org.openstreetmap.josm.gui.layer.OsmDataLayer;
020import org.openstreetmap.josm.gui.progress.ProgressMonitor;
021import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
022import org.openstreetmap.josm.gui.util.GuiHelper;
023import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
024import org.openstreetmap.josm.io.OsmServerObjectReader;
025import org.openstreetmap.josm.io.OsmTransferException;
026import org.xml.sax.SAXException;
027
028/**
029 * Abstract superclass of download/update primitives tasks.
030 * @since 10129
031 */
032public abstract class AbstractPrimitiveTask extends PleaseWaitRunnable {
033
034    protected final DataSet ds = new DataSet();
035    protected boolean canceled;
036    protected Exception lastException;
037    private Set<PrimitiveId> missingPrimitives;
038
039    protected final OsmDataLayer layer;
040    protected MultiFetchServerObjectReader multiObjectReader;
041    protected OsmServerObjectReader objectReader;
042
043    private boolean zoom;
044    private boolean downloadRelations;
045    private boolean fullRelation;
046
047    protected AbstractPrimitiveTask(String title, OsmDataLayer layer) {
048        this(title, new PleaseWaitProgressMonitor(title), layer);
049    }
050
051    protected AbstractPrimitiveTask(String title, ProgressMonitor progressMonitor, OsmDataLayer layer) {
052        super(title, progressMonitor, false);
053        ensureParameterNotNull(layer, "layer");
054        this.layer = layer;
055        if (!layer.isDownloadable()) {
056            throw new IllegalArgumentException("Non-downloadable layer: " + layer);
057        }
058    }
059
060    protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader);
061
062    /**
063     * Sets whether the map view should zoom to impacted primitives at the end.
064     * @param zoom {@code true} if the map view should zoom to impacted primitives at the end
065     * @return {@code this}
066     */
067    public final AbstractPrimitiveTask setZoom(boolean zoom) {
068        this.zoom = zoom;
069        return this;
070    }
071
072    /**
073     * Sets whether .
074     * @param downloadRelations {@code true} if
075     * @param fullRelation {@code true} if a full download is required,
076     *                     i.e., a download including the immediate children of a relation.
077     * @return {@code this}
078     */
079    public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) {
080        this.downloadRelations = downloadRelations;
081        this.fullRelation = fullRelation;
082        return this;
083    }
084
085    /**
086     * Replies the set of ids of all primitives for which a fetch request to the
087     * server was submitted but which are not available from the server (the server
088     * replied a return code of 404)
089     *
090     * @return the set of ids of missing primitives
091     */
092    public Set<PrimitiveId> getMissingPrimitives() {
093        return missingPrimitives;
094    }
095
096    @Override
097    protected void realRun() throws SAXException, IOException, OsmTransferException {
098        DataSet theirDataSet;
099        try {
100            synchronized (this) {
101                if (canceled)
102                    return;
103                multiObjectReader = MultiFetchServerObjectReader.create();
104            }
105            initMultiFetchReader(multiObjectReader);
106            theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
107            missingPrimitives = multiObjectReader.getMissingPrimitives();
108            synchronized (this) {
109                multiObjectReader = null;
110            }
111            new DataSetMerger(ds, theirDataSet).merge();
112
113            if (downloadRelations) {
114                loadIncompleteRelationMembers();
115            }
116
117            loadIncompleteNodes();
118        } catch (OsmTransferException e) {
119            if (canceled)
120                return;
121            lastException = e;
122        }
123    }
124
125    protected void loadIncompleteRelationMembers() throws OsmTransferException {
126        // if incomplete relation members exist, download them too
127        for (Relation r : ds.getRelations()) {
128            if (canceled)
129                return;
130            // Relations may be incomplete in case of nested relations if child relations are accessed before their parent
131            // (it may happen because "relations" has no deterministic sort order, see #10388)
132            if (r.isIncomplete() || r.hasIncompleteMembers()) {
133                synchronized (this) {
134                    if (canceled)
135                        return;
136                    objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation);
137                }
138                DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
139                synchronized (this) {
140                    objectReader = null;
141                }
142                new DataSetMerger(ds, theirDataSet).merge();
143            }
144        }
145    }
146
147    protected void loadIncompleteNodes() throws OsmTransferException {
148        // a way loaded with MultiFetch may have incomplete nodes because at least one of its
149        // nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes.
150        for (Way w : ds.getWays()) {
151            if (canceled)
152                return;
153            if (w.hasIncompleteNodes()) {
154                synchronized (this) {
155                    if (canceled)
156                        return;
157                    objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
158                }
159                DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
160                synchronized (this) {
161                    objectReader = null;
162                }
163                new DataSetMerger(ds, theirDataSet).merge();
164            }
165        }
166    }
167
168    @Override
169    protected void cancel() {
170        canceled = true;
171        synchronized (this) {
172            if (multiObjectReader != null) {
173                multiObjectReader.cancel();
174            }
175            if (objectReader != null) {
176                objectReader.cancel();
177            }
178        }
179    }
180
181    @Override
182    protected void finish() {
183        if (canceled)
184            return;
185        if (lastException != null) {
186            ExceptionDialogUtil.explainException(lastException);
187            return;
188        }
189        GuiHelper.runInEDTAndWait(() -> {
190            layer.mergeFrom(ds);
191            if (zoom && MainApplication.getMap() != null)
192                AutoScaleAction.zoomTo(ds.allPrimitives());
193            layer.onPostDownloadFromServer();
194        });
195    }
196}