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