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.Collection;
009import java.util.Collections;
010
011import org.openstreetmap.josm.data.osm.DataSet;
012import org.openstreetmap.josm.data.osm.DataSetMerger;
013import org.openstreetmap.josm.data.osm.Node;
014import org.openstreetmap.josm.data.osm.OsmPrimitive;
015import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
016import org.openstreetmap.josm.data.osm.Relation;
017import org.openstreetmap.josm.data.osm.Way;
018import org.openstreetmap.josm.gui.ExceptionDialogUtil;
019import org.openstreetmap.josm.gui.PleaseWaitRunnable;
020import org.openstreetmap.josm.gui.layer.OsmDataLayer;
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 * The asynchronous task for updating a collection of objects using multi fetch.
030 *
031 */
032public class UpdatePrimitivesTask extends PleaseWaitRunnable {
033    private DataSet ds;
034    private boolean canceled;
035    private Exception lastException;
036    private final Collection<? extends OsmPrimitive> toUpdate;
037    private final OsmDataLayer layer;
038    private MultiFetchServerObjectReader multiObjectReader;
039    private OsmServerObjectReader objectReader;
040
041    /**
042     * Creates the  task
043     *
044     * @param layer the layer in which primitives are updated. Must not be null.
045     * @param toUpdate a collection of primitives to update from the server. Set to
046     * the empty collection if null.
047     * @throws IllegalArgumentException if layer is null.
048     */
049    public UpdatePrimitivesTask(OsmDataLayer layer, Collection<? extends OsmPrimitive> toUpdate) {
050        super(tr("Update objects"), false /* don't ignore exception */);
051        ensureParameterNotNull(layer, "layer");
052        if (toUpdate == null) {
053            toUpdate = Collections.emptyList();
054        }
055        this.layer = layer;
056        this.toUpdate = toUpdate;
057    }
058
059    @Override
060    protected void cancel() {
061        canceled = true;
062        synchronized (this) {
063            if (multiObjectReader != null) {
064                multiObjectReader.cancel();
065            }
066            if (objectReader != null) {
067                objectReader.cancel();
068            }
069        }
070    }
071
072    @Override
073    protected void finish() {
074        if (canceled)
075            return;
076        if (lastException != null) {
077            ExceptionDialogUtil.explainException(lastException);
078            return;
079        }
080        GuiHelper.runInEDTAndWait(new Runnable() {
081            @Override
082            public void run() {
083                layer.mergeFrom(ds);
084                layer.onPostDownloadFromServer();
085            }
086        });
087    }
088
089    protected void initMultiFetchReaderWithNodes(MultiFetchServerObjectReader reader) {
090        getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to update ..."));
091        for (OsmPrimitive primitive : toUpdate) {
092            if (primitive instanceof Node && !primitive.isNew()) {
093                reader.append(primitive);
094            }
095        }
096    }
097
098    protected void initMultiFetchReaderWithWays(MultiFetchServerObjectReader reader) {
099        getProgressMonitor().indeterminateSubTask(tr("Initializing ways to update ..."));
100        for (OsmPrimitive primitive : toUpdate) {
101            if (primitive instanceof Way && !primitive.isNew()) {
102                // this also adds way nodes
103                reader.append(primitive);
104            }
105        }
106    }
107
108    protected void initMultiFetchReaderWithRelations(MultiFetchServerObjectReader reader) {
109        getProgressMonitor().indeterminateSubTask(tr("Initializing relations to update ..."));
110        for (OsmPrimitive primitive : toUpdate) {
111            if (primitive instanceof Relation && !primitive.isNew()) {
112                // this also adds relation members
113                reader.append(primitive);
114            }
115        }
116    }
117
118    @Override
119    protected void realRun() throws SAXException, IOException, OsmTransferException {
120        this.ds = new DataSet();
121        DataSet theirDataSet;
122        try {
123            synchronized (this) {
124                if (canceled) return;
125                multiObjectReader = MultiFetchServerObjectReader.create();
126            }
127            initMultiFetchReaderWithNodes(multiObjectReader);
128            initMultiFetchReaderWithWays(multiObjectReader);
129            initMultiFetchReaderWithRelations(multiObjectReader);
130            theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
131            synchronized (this) {
132                multiObjectReader = null;
133            }
134            DataSetMerger merger = new DataSetMerger(ds, theirDataSet);
135            merger.merge();
136            // a way loaded with MultiFetch may have incomplete nodes because at least one of its
137            // nodes isn't present in the local data set. We therefore fully load all
138            // ways with incomplete nodes.
139            //
140            for (Way w : ds.getWays()) {
141                if (canceled) return;
142                if (w.hasIncompleteNodes()) {
143                    synchronized (this) {
144                        if (canceled) return;
145                        objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */);
146                    }
147                    theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
148                    synchronized (this) {
149                        objectReader = null;
150                    }
151                    merger = new DataSetMerger(ds, theirDataSet);
152                    merger.merge();
153                }
154            }
155        } catch (Exception e) {
156            if (canceled)
157                return;
158            lastException = e;
159        }
160    }
161}