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.Set;
011import java.util.HashSet;
012import java.util.HashMap;
013import java.util.Map;
014import java.util.Map.Entry;
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<Long, OsmPrimitiveType>();
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<Long, OsmPrimitiveType>();
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 > 0 required.
104     * @param type the primitive type. type != null required
105     * @exception IllegalArgumentException thrown if id <= 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<Long, OsmPrimitiveType>();
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 <= 0
129     * @exception IllegalArgumentException thrown if targetLayer == null
130     *
131     */
132    public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId) throws IllegalArgumentException {
133        super("Download referrers", false /* don't ignore exception*/);
134        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
135        if (primitiveId.isNew())
136            throw new IllegalArgumentException(MessageFormat.format("Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId()));
137        canceled = false;
138        this.children = new HashMap<Long, OsmPrimitiveType>();
139        this.children.put(primitiveId.getUniqueId(), primitiveId.getType());
140        this.targetLayer = targetLayer;
141        parents = new DataSet();
142    }
143
144    @Override
145    protected void cancel() {
146        canceled = true;
147        synchronized(this) {
148            if (reader != null) {
149                reader.cancel();
150            }
151        }
152    }
153
154    @Override
155    protected void finish() {
156        if (canceled)
157            return;
158        if (lastException != null) {
159            ExceptionUtil.explainException(lastException);
160            return;
161        }
162
163        DataSetMerger visitor = new DataSetMerger(targetLayer.data, parents);
164        visitor.merge();
165        SwingUtilities.invokeLater(
166                new Runnable() {
167                    @Override
168                    public void run() {
169                        targetLayer.onPostDownloadFromServer();
170                        Main.map.mapView.repaint();
171                    }
172                }
173        );
174        if (visitor.getConflicts().isEmpty())
175            return;
176        targetLayer.getConflicts().add(visitor.getConflicts());
177        JOptionPane.showMessageDialog(
178                Main.parent,
179                trn("There was {0} conflict during import.",
180                        "There were {0} conflicts during import.",
181                        visitor.getConflicts().size(),
182                        visitor.getConflicts().size()
183                ),
184                trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()),
185                JOptionPane.WARNING_MESSAGE
186        );
187        Main.map.conflictDialog.unfurlDialog();
188        Main.map.repaint();
189    }
190
191    protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException{
192        reader = new OsmServerBackreferenceReader(id, type);
193        DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
194        synchronized(this) { // avoid race condition in cancel()
195            reader = null;
196        }
197        Collection<Way> ways = ds.getWays();
198
199        DataSetMerger merger;
200        if (!ways.isEmpty()) {
201            Set<Node> nodes = new HashSet<Node>();
202            for (Way w: ways) {
203                // Ensure each node is only listed once
204                nodes.addAll(w.getNodes());
205            }
206            // Don't retrieve any nodes we've already grabbed
207            nodes.removeAll(targetLayer.data.getNodes());
208            if (!nodes.isEmpty()) {
209                reader = new MultiFetchServerObjectReader();
210                ((MultiFetchServerObjectReader)reader).append(nodes);
211                DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
212                synchronized(this) { // avoid race condition in cancel()
213                    reader = null;
214                }
215                merger = new DataSetMerger(ds, wayNodes);
216                merger.merge();
217            }
218        }
219        merger = new DataSetMerger(parents, ds);
220        merger.merge();
221    }
222
223    @Override
224    protected void realRun() throws SAXException, IOException, OsmTransferException {
225        try {
226            progressMonitor.setTicksCount(children.size());
227            int i=1;
228            for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) {
229                if (canceled)
230                    return;
231                String msg = "";
232                switch(entry.getValue()) {
233                case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1,children.size(), entry.getKey()); break;
234                case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1,children.size(), entry.getKey()); break;
235                case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1,children.size(), entry.getKey()); break;
236                }
237                progressMonitor.subTask(msg);
238                downloadParents(entry.getKey(), entry.getValue(), progressMonitor);
239                i++;
240            }
241        } catch(Exception e) {
242            if (canceled)
243                return;
244            lastException = e;
245        }
246    }
247}