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