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.PleaseWaitRunnable;
028import org.openstreetmap.josm.gui.layer.OsmDataLayer;
029import org.openstreetmap.josm.gui.progress.ProgressMonitor;
030import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
031import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
032import org.openstreetmap.josm.io.OsmServerReader;
033import org.openstreetmap.josm.io.OsmTransferException;
034import org.openstreetmap.josm.tools.CheckParameterUtil;
035import org.openstreetmap.josm.tools.ExceptionUtil;
036import org.xml.sax.SAXException;
037
038/**
039 * The asynchronous task for downloading referring primitives
040 * @since 2923
041 */
042public class DownloadReferrersTask extends PleaseWaitRunnable {
043    private boolean canceled;
044    private Exception lastException;
045    private OsmServerReader reader;
046    /** the target layer */
047    private OsmDataLayer targetLayer;
048    /** the collection of child primitives */
049    private Map<Long, OsmPrimitiveType> children;
050    /** the parents */
051    private DataSet parents;
052
053    /**
054     * constructor
055     *
056     * @param targetLayer  the target layer for the downloaded primitives. Must not be null.
057     * @param children the collection of child primitives for which parents are to be downloaded
058     */
059    public DownloadReferrersTask(OsmDataLayer targetLayer, Collection<OsmPrimitive> children) {
060        super("Download referrers", false /* don't ignore exception*/);
061        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
062        canceled = false;
063        this.children = new HashMap<>();
064        if (children != null) {
065            for (OsmPrimitive p: children) {
066                if (!p.isNew()) {
067                    this.children.put(p.getId(), OsmPrimitiveType.from(p));
068                }
069            }
070        }
071        this.targetLayer = targetLayer;
072        parents = new DataSet();
073    }
074
075    /**
076     * constructor
077     *
078     * @param targetLayer  the target layer for the downloaded primitives. Must not be null.
079     * @param children  the collection of children for which parents are to be downloaded. Children
080     * are specified by their id and  their type.
081     */
082    public DownloadReferrersTask(OsmDataLayer targetLayer, Map<Long, OsmPrimitiveType> children) {
083        super("Download referrers", false /* don't ignore exception*/);
084        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
085        canceled = false;
086        this.children = new HashMap<>();
087        if (children != null) {
088            for (Entry<Long, OsmPrimitiveType> entry : children.entrySet()) {
089                if (entry.getKey() > 0 && entry.getValue() != null) {
090                    children.put(entry.getKey(), entry.getValue());
091                }
092            }
093        }
094        this.targetLayer = targetLayer;
095        parents = new DataSet();
096    }
097
098    /**
099     * constructor
100     *
101     * @param targetLayer  the target layer. Must not be null.
102     * @param id the primitive id. id &gt; 0 required.
103     * @param type the primitive type. type != null required
104     * @throws IllegalArgumentException if id &lt;= 0
105     * @throws IllegalArgumentException if type == null
106     * @throws IllegalArgumentException if targetLayer == null
107     */
108    public DownloadReferrersTask(OsmDataLayer targetLayer, long id, OsmPrimitiveType type) {
109        super("Download referrers", false /* don't ignore exception*/);
110        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
111        if (id <= 0)
112            throw new IllegalArgumentException(MessageFormat.format("Id > 0 required, got {0}", id));
113        CheckParameterUtil.ensureParameterNotNull(type, "type");
114        canceled = false;
115        this.children = new HashMap<>();
116        this.children.put(id, type);
117        this.targetLayer = targetLayer;
118        parents = new DataSet();
119    }
120
121    /**
122     * constructor
123     *
124     * @param targetLayer the target layer. Must not be null.
125     * @param primitiveId a PrimitiveId object.
126     * @throws IllegalArgumentException if id &lt;= 0
127     * @throws IllegalArgumentException if targetLayer == null
128     */
129    public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId) {
130        this(targetLayer,  primitiveId, null);
131    }
132
133    /**
134     * constructor
135     *
136     * @param targetLayer the target layer. Must not be null.
137     * @param primitiveId a PrimitiveId object.
138     * @param progressMonitor ProgressMonitor to use or null to create a new one.
139     * @throws IllegalArgumentException if id &lt;= 0
140     * @throws IllegalArgumentException if targetLayer == null
141     */
142    public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId,
143            ProgressMonitor progressMonitor) {
144        super("Download referrers", progressMonitor, false /* don't ignore exception*/);
145        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
146        if (primitiveId.isNew())
147            throw new IllegalArgumentException(MessageFormat.format(
148                    "Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId()));
149        canceled = false;
150        this.children = new HashMap<>();
151        this.children.put(primitiveId.getUniqueId(), primitiveId.getType());
152        this.targetLayer = targetLayer;
153        parents = new DataSet();
154    }
155
156    @Override
157    protected void cancel() {
158        canceled = true;
159        synchronized (this) {
160            if (reader != null) {
161                reader.cancel();
162            }
163        }
164    }
165
166    @Override
167    protected void finish() {
168        if (canceled)
169            return;
170        if (lastException != null) {
171            ExceptionUtil.explainException(lastException);
172            return;
173        }
174
175        DataSetMerger visitor = new DataSetMerger(targetLayer.data, parents);
176        visitor.merge();
177        SwingUtilities.invokeLater(
178                new Runnable() {
179                    @Override
180                    public void run() {
181                        targetLayer.onPostDownloadFromServer();
182                        if (Main.map != null)
183                            Main.map.mapView.repaint();
184                    }
185                }
186        );
187        if (visitor.getConflicts().isEmpty())
188            return;
189        targetLayer.getConflicts().add(visitor.getConflicts());
190        JOptionPane.showMessageDialog(
191                Main.parent,
192                trn("There was {0} conflict during import.",
193                        "There were {0} conflicts during import.",
194                        visitor.getConflicts().size(),
195                        visitor.getConflicts().size()
196                ),
197                trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()),
198                JOptionPane.WARNING_MESSAGE
199        );
200        Main.map.conflictDialog.unfurlDialog();
201        Main.map.repaint();
202    }
203
204    protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException {
205        reader = new OsmServerBackreferenceReader(id, type);
206        DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
207        synchronized (this) { // avoid race condition in cancel()
208            reader = null;
209        }
210        Collection<Way> ways = ds.getWays();
211
212        DataSetMerger merger;
213        if (!ways.isEmpty()) {
214            Set<Node> nodes = new HashSet<>();
215            for (Way w: ways) {
216                // Ensure each node is only listed once
217                nodes.addAll(w.getNodes());
218            }
219            // Don't retrieve any nodes we've already grabbed
220            nodes.removeAll(targetLayer.data.getNodes());
221            if (!nodes.isEmpty()) {
222                reader = new MultiFetchServerObjectReader();
223                ((MultiFetchServerObjectReader) reader).append(nodes);
224                DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
225                synchronized (this) { // avoid race condition in cancel()
226                    reader = null;
227                }
228                merger = new DataSetMerger(ds, wayNodes);
229                merger.merge();
230            }
231        }
232        merger = new DataSetMerger(parents, ds);
233        merger.merge();
234    }
235
236    @Override
237    protected void realRun() throws SAXException, IOException, OsmTransferException {
238        try {
239            progressMonitor.setTicksCount(children.size());
240            int i = 1;
241            for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) {
242                if (canceled)
243                    return;
244                String msg = "";
245                switch(entry.getValue()) {
246                case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1, children.size(), entry.getKey()); break;
247                case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1, children.size(), entry.getKey()); break;
248                case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1, children.size(), entry.getKey()); break;
249                }
250                progressMonitor.subTask(msg);
251                downloadParents(entry.getKey(), entry.getValue(), progressMonitor);
252                i++;
253            }
254        } catch (Exception e) {
255            if (canceled)
256                return;
257            lastException = e;
258        }
259    }
260}