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.Main;
010import org.openstreetmap.josm.actions.AutoScaleAction;
011import org.openstreetmap.josm.data.osm.DataSet;
012import org.openstreetmap.josm.data.osm.DataSetMerger;
013import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
014import org.openstreetmap.josm.data.osm.PrimitiveId;
015import org.openstreetmap.josm.data.osm.Relation;
016import org.openstreetmap.josm.data.osm.Way;
017import org.openstreetmap.josm.gui.ExceptionDialogUtil;
018import org.openstreetmap.josm.gui.PleaseWaitRunnable;
019import org.openstreetmap.josm.gui.layer.OsmDataLayer;
020import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
021import org.openstreetmap.josm.gui.progress.ProgressMonitor;
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    }
056
057    protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader);
058
059    /**
060     * Sets whether the map view should zoom to impacted primitives at the end.
061     * @param zoom {@code true} if the map view should zoom to impacted primitives at the end
062     * @return {@code this}
063     */
064    public final AbstractPrimitiveTask setZoom(boolean zoom) {
065        this.zoom = zoom;
066        return this;
067    }
068
069    /**
070     * Sets whether .
071     * @param downloadRelations {@code true} if
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     */
076    public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) {
077        this.downloadRelations = downloadRelations;
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();
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            if (downloadRelations) {
111                loadIncompleteRelationMembers();
112            }
113
114            loadIncompleteNodes();
115        } catch (OsmTransferException e) {
116            if (canceled)
117                return;
118            lastException = e;
119        }
120    }
121
122    protected void loadIncompleteRelationMembers() throws OsmTransferException {
123        // if incomplete relation members exist, download them too
124        for (Relation r : ds.getRelations()) {
125            if (canceled)
126                return;
127            // Relations may be incomplete in case of nested relations if child relations are accessed before their parent
128            // (it may happen because "relations" has no deterministic sort order, see #10388)
129            if (r.isIncomplete() || r.hasIncompleteMembers()) {
130                synchronized (this) {
131                    if (canceled)
132                        return;
133                    objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation);
134                }
135                DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
136                synchronized (this) {
137                    objectReader = null;
138                }
139                new DataSetMerger(ds, theirDataSet).merge();
140            }
141        }
142    }
143
144    protected void loadIncompleteNodes() throws OsmTransferException {
145        // a way loaded with MultiFetch may have incomplete nodes because at least one of its
146        // nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes.
147        for (Way w : ds.getWays()) {
148            if (canceled)
149                return;
150            if (w.hasIncompleteNodes()) {
151                synchronized (this) {
152                    if (canceled)
153                        return;
154                    objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
155                }
156                DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
157                synchronized (this) {
158                    objectReader = null;
159                }
160                new DataSetMerger(ds, theirDataSet).merge();
161            }
162        }
163    }
164
165    @Override
166    protected void cancel() {
167        canceled = true;
168        synchronized (this) {
169            if (multiObjectReader != null) {
170                multiObjectReader.cancel();
171            }
172            if (objectReader != null) {
173                objectReader.cancel();
174            }
175        }
176    }
177
178    @Override
179    protected void finish() {
180        if (canceled)
181            return;
182        if (lastException != null) {
183            ExceptionDialogUtil.explainException(lastException);
184            return;
185        }
186        GuiHelper.runInEDTAndWait(new Runnable() {
187            @Override
188            public void run() {
189                layer.mergeFrom(ds);
190                if (zoom && Main.map != null)
191                    AutoScaleAction.zoomTo(ds.allPrimitives());
192                layer.onPostDownloadFromServer();
193            }
194        });
195    }
196}