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.Way;
015import org.openstreetmap.josm.gui.ExceptionDialogUtil;
016import org.openstreetmap.josm.gui.MainApplication;
017import org.openstreetmap.josm.gui.PleaseWaitRunnable;
018import org.openstreetmap.josm.gui.layer.OsmDataLayer;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
021import org.openstreetmap.josm.gui.util.GuiHelper;
022import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
023import org.openstreetmap.josm.io.OsmServerObjectReader;
024import org.openstreetmap.josm.io.OsmTransferException;
025import org.xml.sax.SAXException;
026
027/**
028 * Abstract superclass of download/update primitives tasks.
029 * @since 10129
030 */
031public abstract class AbstractPrimitiveTask extends PleaseWaitRunnable {
032
033    protected final DataSet ds = new DataSet();
034    protected boolean canceled;
035    protected Exception lastException;
036    private Set<PrimitiveId> missingPrimitives;
037
038    protected final OsmDataLayer layer;
039    protected MultiFetchServerObjectReader multiObjectReader;
040    protected OsmServerObjectReader objectReader;
041
042    private boolean zoom;
043    protected boolean fullRelation;
044
045    protected AbstractPrimitiveTask(String title, OsmDataLayer layer) {
046        this(title, new PleaseWaitProgressMonitor(title), layer);
047    }
048
049    protected AbstractPrimitiveTask(String title, ProgressMonitor progressMonitor, OsmDataLayer layer) {
050        super(title, progressMonitor, false);
051        ensureParameterNotNull(layer, "layer");
052        this.layer = layer;
053        if (!layer.isDownloadable()) {
054            throw new IllegalArgumentException("Non-downloadable layer: " + layer);
055        }
056    }
057
058    protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader);
059
060    /**
061     * Sets whether the map view should zoom to impacted primitives at the end.
062     * @param zoom {@code true} if the map view should zoom to impacted primitives at the end
063     * @return {@code this}
064     */
065    public final AbstractPrimitiveTask setZoom(boolean zoom) {
066        this.zoom = zoom;
067        return this;
068    }
069
070    /**
071     * Sets whether all members of the relation should be downloaded completely.
072     * @param fullRelation {@code true} if a full download is required,
073     *                     i.e., a download including the immediate children of a relation.
074     * @return {@code this}
075     * since 15811 (changed parameter list)
076     */
077    public final AbstractPrimitiveTask setDownloadRelations(boolean fullRelation) {
078        this.fullRelation = fullRelation;
079        return this;
080    }
081
082    /**
083     * Replies the set of ids of all primitives for which a fetch request to the
084     * server was submitted but which are not available from the server (the server
085     * replied a return code of 404)
086     *
087     * @return the set of ids of missing primitives
088     */
089    public Set<PrimitiveId> getMissingPrimitives() {
090        return missingPrimitives;
091    }
092
093    @Override
094    protected void realRun() throws SAXException, IOException, OsmTransferException {
095        DataSet theirDataSet;
096        try {
097            synchronized (this) {
098                if (canceled)
099                    return;
100                multiObjectReader = MultiFetchServerObjectReader.create().setRecurseDownRelations(fullRelation);
101            }
102            initMultiFetchReader(multiObjectReader);
103            theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
104            missingPrimitives = multiObjectReader.getMissingPrimitives();
105            synchronized (this) {
106                multiObjectReader = null;
107            }
108            new DataSetMerger(ds, theirDataSet).merge();
109
110            loadIncompleteNodes();
111        } catch (OsmTransferException e) {
112            if (canceled)
113                return;
114            lastException = e;
115        }
116    }
117
118    protected void loadIncompleteNodes() throws OsmTransferException {
119        // a way loaded with MultiFetch may have incomplete nodes because at least one of its
120        // nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes.
121        for (Way w : ds.getWays()) {
122            if (canceled)
123                return;
124            if (w.hasIncompleteNodes()) {
125                synchronized (this) {
126                    if (canceled)
127                        return;
128                    objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
129                }
130                DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
131                synchronized (this) {
132                    objectReader = null;
133                }
134                new DataSetMerger(ds, theirDataSet).merge();
135            }
136        }
137    }
138
139    @Override
140    protected void cancel() {
141        canceled = true;
142        synchronized (this) {
143            if (multiObjectReader != null) {
144                multiObjectReader.cancel();
145            }
146            if (objectReader != null) {
147                objectReader.cancel();
148            }
149        }
150    }
151
152    @Override
153    protected void finish() {
154        if (canceled)
155            return;
156        if (lastException != null) {
157            ExceptionDialogUtil.explainException(lastException);
158            return;
159        }
160        GuiHelper.runInEDTAndWait(() -> {
161            layer.mergeFrom(ds);
162            if (zoom && MainApplication.getMap() != null)
163                AutoScaleAction.zoomTo(ds.allPrimitives());
164            layer.onPostDownloadFromServer();
165        });
166    }
167}