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