// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.io;
import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
import java.io.IOException;
import java.util.Set;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.AutoScaleAction;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.DataSetMerger;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.PrimitiveId;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.ExceptionDialogUtil;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
import org.openstreetmap.josm.io.OsmServerObjectReader;
import org.openstreetmap.josm.io.OsmTransferException;
import org.xml.sax.SAXException;
/**
* Abstract superclass of download/update primitives tasks.
* @since 10129
*/
public abstract class AbstractPrimitiveTask extends PleaseWaitRunnable {
protected final DataSet ds = new DataSet();
protected boolean canceled;
protected Exception lastException;
private Set<PrimitiveId> missingPrimitives;
protected final OsmDataLayer layer;
protected MultiFetchServerObjectReader multiObjectReader;
protected OsmServerObjectReader objectReader;
private boolean zoom;
private boolean downloadRelations;
private boolean fullRelation;
protected AbstractPrimitiveTask(String title, OsmDataLayer layer) {
this(title, new PleaseWaitProgressMonitor(title), layer);
}
protected AbstractPrimitiveTask(String title, ProgressMonitor progressMonitor, OsmDataLayer layer) {
super(title, progressMonitor, false);
ensureParameterNotNull(layer, "layer");
this.layer = layer;
}
protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader);
/**
* Sets whether the map view should zoom to impacted primitives at the end.
* @param zoom {@code true} if the map view should zoom to impacted primitives at the end
* @return {@code this}
*/
public final AbstractPrimitiveTask setZoom(boolean zoom) {
this.zoom = zoom;
return this;
}
/**
* Sets whether .
* @param downloadRelations {@code true} if
* @param fullRelation {@code true} if a full download is required,
* i.e., a download including the immediate children of a relation.
* @return {@code this}
*/
public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) {
this.downloadRelations = downloadRelations;
this.fullRelation = fullRelation;
return this;
}
/**
* Replies the set of ids of all primitives for which a fetch request to the
* server was submitted but which are not available from the server (the server
* replied a return code of 404)
*
* @return the set of ids of missing primitives
*/
public Set<PrimitiveId> getMissingPrimitives() {
return missingPrimitives;
}
@Override
protected void realRun() throws SAXException, IOException, OsmTransferException {
DataSet theirDataSet;
try {
synchronized (this) {
if (canceled)
return;
multiObjectReader = MultiFetchServerObjectReader.create();
}
initMultiFetchReader(multiObjectReader);
theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
missingPrimitives = multiObjectReader.getMissingPrimitives();
synchronized (this) {
multiObjectReader = null;
}
new DataSetMerger(ds, theirDataSet).merge();
if (downloadRelations) {
loadIncompleteRelationMembers();
}
loadIncompleteNodes();
} catch (OsmTransferException e) {
if (canceled)
return;
lastException = e;
}
}
protected void loadIncompleteRelationMembers() throws OsmTransferException {
// if incomplete relation members exist, download them too
for (Relation r : ds.getRelations()) {
if (canceled)
return;
// Relations may be incomplete in case of nested relations if child relations are accessed before their parent
// (it may happen because "relations" has no deterministic sort order, see #10388)
if (r.isIncomplete() || r.hasIncompleteMembers()) {
synchronized (this) {
if (canceled)
return;
objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation);
}
DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
synchronized (this) {
objectReader = null;
}
new DataSetMerger(ds, theirDataSet).merge();
}
}
}
protected void loadIncompleteNodes() throws OsmTransferException {
// a way loaded with MultiFetch may have incomplete nodes because at least one of its
// nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes.
for (Way w : ds.getWays()) {
if (canceled)
return;
if (w.hasIncompleteNodes()) {
synchronized (this) {
if (canceled)
return;
objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
}
DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
synchronized (this) {
objectReader = null;
}
new DataSetMerger(ds, theirDataSet).merge();
}
}
}
@Override
protected void cancel() {
canceled = true;
synchronized (this) {
if (multiObjectReader != null) {
multiObjectReader.cancel();
}
if (objectReader != null) {
objectReader.cancel();
}
}
}
@Override
protected void finish() {
if (canceled)
return;
if (lastException != null) {
ExceptionDialogUtil.explainException(lastException);
return;
}
GuiHelper.runInEDTAndWait(() -> {
layer.mergeFrom(ds);
if (zoom && Main.map != null)
AutoScaleAction.zoomTo(ds.allPrimitives());
layer.onPostDownloadFromServer();
});
}
}