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