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.LinkedHashSet;
011import java.util.Set;
012import java.util.stream.Collectors;
013
014import javax.swing.JOptionPane;
015import javax.swing.SwingUtilities;
016
017import org.openstreetmap.josm.data.Bounds;
018import org.openstreetmap.josm.data.osm.DataSet;
019import org.openstreetmap.josm.data.osm.DataSetMerger;
020import org.openstreetmap.josm.data.osm.Node;
021import org.openstreetmap.josm.data.osm.OsmPrimitive;
022import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
023import org.openstreetmap.josm.data.osm.PrimitiveId;
024import org.openstreetmap.josm.data.osm.Way;
025import org.openstreetmap.josm.gui.MainApplication;
026import org.openstreetmap.josm.gui.MapFrame;
027import org.openstreetmap.josm.gui.PleaseWaitRunnable;
028import org.openstreetmap.josm.gui.layer.OsmDataLayer;
029import org.openstreetmap.josm.gui.progress.ProgressMonitor;
030import org.openstreetmap.josm.io.MultiFetchOverpassObjectReader;
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.io.OverpassDownloadReader;
036import org.openstreetmap.josm.tools.CheckParameterUtil;
037import org.openstreetmap.josm.tools.ExceptionUtil;
038import org.xml.sax.SAXException;
039
040/**
041 * The asynchronous task for downloading referring primitives
042 * @since 2923
043 */
044public class DownloadReferrersTask extends PleaseWaitRunnable {
045    private boolean canceled;
046    private Exception lastException;
047    private OsmServerReader reader;
048    /** the target layer */
049    private final OsmDataLayer targetLayer;
050    /** the collection of child primitives */
051    private final Set<PrimitiveId> children;
052    /** the parents */
053    private final DataSet parents;
054
055    /**
056     * constructor
057     *
058     * @param targetLayer  the target layer for the downloaded primitives. Must not be null.
059     * @param children the collection of child primitives for which parents are to be downloaded
060     * @since 15787 (modified interface)
061     */
062    public DownloadReferrersTask(OsmDataLayer targetLayer, Collection<? extends PrimitiveId> children) {
063        super("Download referrers", false /* don't ignore exception*/);
064        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
065        if (!targetLayer.isDownloadable()) {
066            throw new IllegalArgumentException("Non-downloadable layer: " + targetLayer);
067        }
068        canceled = false;
069        this.children = new LinkedHashSet<>();
070        if (children != null) {
071            children.stream().filter(p -> !p.isNew()).forEach(this.children::add);
072        }
073
074        this.targetLayer = targetLayer;
075        parents = new DataSet();
076    }
077
078    /**
079     * constructor
080     *
081     * @param targetLayer the target layer. Must not be null.
082     * @param primitiveId a PrimitiveId object.
083     * @param progressMonitor ProgressMonitor to use or null to create a new one.
084     * @throws IllegalArgumentException if id &lt;= 0
085     * @throws IllegalArgumentException if targetLayer == null
086     */
087    public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId,
088            ProgressMonitor progressMonitor) {
089        super("Download referrers", progressMonitor, false /* don't ignore exception*/);
090        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
091        if (primitiveId.isNew())
092            throw new IllegalArgumentException(MessageFormat.format(
093                    "Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId()));
094        canceled = false;
095        this.children = new LinkedHashSet<>();
096        this.children.add(primitiveId);
097        this.targetLayer = targetLayer;
098        parents = new DataSet();
099    }
100
101    @Override
102    protected void cancel() {
103        canceled = true;
104        synchronized (this) {
105            if (reader != null) {
106                reader.cancel();
107            }
108        }
109    }
110
111    @Override
112    protected void finish() {
113        if (canceled)
114            return;
115        if (lastException != null) {
116            JOptionPane.showMessageDialog(
117                    MainApplication.getMainFrame(),
118                    ExceptionUtil.explainException(lastException),
119                    tr("Error"),
120                    JOptionPane.ERROR_MESSAGE);
121            return;
122        }
123
124        DataSetMerger visitor = new DataSetMerger(targetLayer.getDataSet(), parents);
125        visitor.merge();
126        SwingUtilities.invokeLater(targetLayer::onPostDownloadFromServer);
127        if (visitor.getConflicts().isEmpty())
128            return;
129        targetLayer.getConflicts().add(visitor.getConflicts());
130        JOptionPane.showMessageDialog(
131                MainApplication.getMainFrame(),
132                trn("There was {0} conflict during import.",
133                    "There were {0} conflicts during import.",
134                    visitor.getConflicts().size(),
135                    visitor.getConflicts().size()
136                ),
137                trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()),
138                JOptionPane.WARNING_MESSAGE
139        );
140        MapFrame map = MainApplication.getMap();
141        map.conflictDialog.unfurlDialog();
142        map.repaint();
143    }
144
145    protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException {
146        reader = new OsmServerBackreferenceReader(id, type, false).setAllowIncompleteParentWays(true);
147
148        DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
149        synchronized (this) { // avoid race condition in cancel()
150            reader = null;
151            if (canceled)
152                return;
153        }
154        new DataSetMerger(parents, ds).merge();
155    }
156
157    @Override
158    protected void realRun() throws SAXException, IOException, OsmTransferException {
159        try {
160            if (Boolean.TRUE.equals(OverpassDownloadReader.FOR_MULTI_FETCH.get())) {
161                String request = MultiFetchOverpassObjectReader.genOverpassQuery(children, false, true, false);
162                reader = new OverpassDownloadReader(new Bounds(0, 0, 0, 0),
163                        OverpassDownloadReader.OVERPASS_SERVER.get(), request);
164                DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
165                new DataSetMerger(parents, ds).merge();
166            } else {
167                progressMonitor.setTicksCount(children.size());
168                int i = 1;
169                for (PrimitiveId p : children) {
170                    if (canceled)
171                        return;
172                    String msg;
173                    String id = Long.toString(p.getUniqueId());
174                    switch(p.getType()) {
175                    case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i, children.size(), id); break;
176                    case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i, children.size(), id); break;
177                    case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i, children.size(), id); break;
178                    default: throw new AssertionError();
179                    }
180                    progressMonitor.subTask(msg);
181                    downloadParents(p.getUniqueId(), p.getType(), progressMonitor);
182                    i++;
183                }
184                Collection<Way> ways = parents.getWays();
185
186                if (!ways.isEmpty()) {
187                    // Collect incomplete nodes of parent ways
188                    Set<Node> nodes = ways.stream().flatMap(w -> w.getNodes().stream().filter(OsmPrimitive::isIncomplete))
189                            .collect(Collectors.toSet());
190                    if (!nodes.isEmpty()) {
191                        reader = MultiFetchServerObjectReader.create();
192                        ((MultiFetchServerObjectReader) reader).append(nodes);
193                        DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
194                        synchronized (this) { // avoid race condition in cancel()
195                            reader = null;
196                        }
197                        new DataSetMerger(parents, wayNodes).merge();
198                    }
199                }
200            }
201        } catch (OsmTransferException e) {
202            if (canceled)
203                return;
204            lastException = e;
205        }
206    }
207
208}