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