001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.downloadtasks;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.io.IOException;
008import java.text.MessageFormat;
009import java.util.Collection;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.Map;
013import java.util.Map.Entry;
014import java.util.Set;
015
016import javax.swing.JOptionPane;
017import javax.swing.SwingUtilities;
018
019import org.openstreetmap.josm.data.osm.DataSet;
020import org.openstreetmap.josm.data.osm.DataSetMerger;
021import org.openstreetmap.josm.data.osm.Node;
022import org.openstreetmap.josm.data.osm.OsmPrimitive;
023import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
024import org.openstreetmap.josm.data.osm.PrimitiveId;
025import org.openstreetmap.josm.data.osm.Way;
026import org.openstreetmap.josm.gui.MainApplication;
027import org.openstreetmap.josm.gui.MapFrame;
028import org.openstreetmap.josm.gui.PleaseWaitRunnable;
029import org.openstreetmap.josm.gui.layer.OsmDataLayer;
030import org.openstreetmap.josm.gui.progress.ProgressMonitor;
031import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
032import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
033import org.openstreetmap.josm.io.OsmServerReader;
034import org.openstreetmap.josm.io.OsmTransferException;
035import org.openstreetmap.josm.tools.CheckParameterUtil;
036import org.openstreetmap.josm.tools.ExceptionUtil;
037import org.xml.sax.SAXException;
038
039/**
040 * The asynchronous task for downloading referring primitives
041 * @since 2923
042 */
043public class DownloadReferrersTask extends PleaseWaitRunnable {
044    private boolean canceled;
045    private Exception lastException;
046    private OsmServerReader reader;
047    /** the target layer */
048    private final OsmDataLayer targetLayer;
049    /** the collection of child primitives */
050    private final Map<Long, OsmPrimitiveType> children;
051    /** the parents */
052    private final DataSet parents;
053
054    /**
055     * constructor
056     *
057     * @param targetLayer  the target layer for the downloaded primitives. Must not be null.
058     * @param children the collection of child primitives for which parents are to be downloaded
059     */
060    public DownloadReferrersTask(OsmDataLayer targetLayer, Collection<OsmPrimitive> children) {
061        super("Download referrers", false /* don't ignore exception*/);
062        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
063        if (!targetLayer.isDownloadable()) {
064            throw new IllegalArgumentException("Non-downloadable layer: " + targetLayer);
065        }
066        canceled = false;
067        this.children = new HashMap<>();
068        if (children != null) {
069            for (OsmPrimitive p: children) {
070                if (!p.isNew()) {
071                    this.children.put(p.getId(), OsmPrimitiveType.from(p));
072                }
073            }
074        }
075        this.targetLayer = targetLayer;
076        parents = new DataSet();
077    }
078
079    /**
080     * constructor
081     *
082     * @param targetLayer the target layer. Must not be null.
083     * @param primitiveId a PrimitiveId object.
084     * @param progressMonitor ProgressMonitor to use or null to create a new one.
085     * @throws IllegalArgumentException if id &lt;= 0
086     * @throws IllegalArgumentException if targetLayer == null
087     */
088    public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId,
089            ProgressMonitor progressMonitor) {
090        super("Download referrers", progressMonitor, false /* don't ignore exception*/);
091        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
092        if (primitiveId.isNew())
093            throw new IllegalArgumentException(MessageFormat.format(
094                    "Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId()));
095        canceled = false;
096        this.children = new HashMap<>();
097        this.children.put(primitiveId.getUniqueId(), primitiveId.getType());
098        this.targetLayer = targetLayer;
099        parents = new DataSet();
100    }
101
102    @Override
103    protected void cancel() {
104        canceled = true;
105        synchronized (this) {
106            if (reader != null) {
107                reader.cancel();
108            }
109        }
110    }
111
112    @Override
113    protected void finish() {
114        if (canceled)
115            return;
116        if (lastException != null) {
117            ExceptionUtil.explainException(lastException);
118            return;
119        }
120
121        DataSetMerger visitor = new DataSetMerger(targetLayer.getDataSet(), parents);
122        visitor.merge();
123        SwingUtilities.invokeLater(targetLayer::onPostDownloadFromServer);
124        if (visitor.getConflicts().isEmpty())
125            return;
126        targetLayer.getConflicts().add(visitor.getConflicts());
127        JOptionPane.showMessageDialog(
128                MainApplication.getMainFrame(),
129                trn("There was {0} conflict during import.",
130                    "There were {0} conflicts during import.",
131                    visitor.getConflicts().size(),
132                    visitor.getConflicts().size()
133                ),
134                trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()),
135                JOptionPane.WARNING_MESSAGE
136        );
137        MapFrame map = MainApplication.getMap();
138        map.conflictDialog.unfurlDialog();
139        map.repaint();
140    }
141
142    protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException {
143        reader = new OsmServerBackreferenceReader(id, type);
144        DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
145        synchronized (this) { // avoid race condition in cancel()
146            reader = null;
147        }
148        Collection<Way> ways = ds.getWays();
149
150        DataSetMerger merger;
151        if (!ways.isEmpty()) {
152            Set<Node> nodes = new HashSet<>();
153            for (Way w: ways) {
154                // Ensure each node is only listed once
155                nodes.addAll(w.getNodes());
156            }
157            // Don't retrieve any nodes we've already grabbed
158            nodes.removeAll(targetLayer.data.getNodes());
159            if (!nodes.isEmpty()) {
160                reader = MultiFetchServerObjectReader.create();
161                ((MultiFetchServerObjectReader) reader).append(nodes);
162                DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
163                synchronized (this) { // avoid race condition in cancel()
164                    reader = null;
165                }
166                merger = new DataSetMerger(ds, wayNodes);
167                merger.merge();
168            }
169        }
170        merger = new DataSetMerger(parents, ds);
171        merger.merge();
172    }
173
174    @Override
175    protected void realRun() throws SAXException, IOException, OsmTransferException {
176        try {
177            progressMonitor.setTicksCount(children.size());
178            int i = 1;
179            for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) {
180                if (canceled)
181                    return;
182                String msg;
183                switch(entry.getValue()) {
184                case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1, children.size(), entry.getKey()); break;
185                case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1, children.size(), entry.getKey()); break;
186                case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1, children.size(), entry.getKey()); break;
187                default: throw new AssertionError();
188                }
189                progressMonitor.subTask(msg);
190                downloadParents(entry.getKey(), entry.getValue(), progressMonitor);
191                i++;
192            }
193        } catch (OsmTransferException e) {
194            if (canceled)
195                return;
196            lastException = e;
197        }
198    }
199}