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