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 final OsmDataLayer targetLayer;
048    /** the collection of child primitives */
049    private final Map<Long, OsmPrimitiveType> children;
050    /** the parents */
051    private final 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. 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 HashMap<>();
093        this.children.put(primitiveId.getUniqueId(), primitiveId.getType());
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.data, parents);
118        visitor.merge();
119        SwingUtilities.invokeLater(
120                new Runnable() {
121                    @Override
122                    public void run() {
123                        targetLayer.onPostDownloadFromServer();
124                        if (Main.map != null)
125                            Main.map.mapView.repaint();
126                    }
127                }
128        );
129        if (visitor.getConflicts().isEmpty())
130            return;
131        targetLayer.getConflicts().add(visitor.getConflicts());
132        JOptionPane.showMessageDialog(
133                Main.parent,
134                trn("There was {0} conflict during import.",
135                        "There were {0} conflicts during import.",
136                        visitor.getConflicts().size(),
137                        visitor.getConflicts().size()
138                ),
139                trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()),
140                JOptionPane.WARNING_MESSAGE
141        );
142        Main.map.conflictDialog.unfurlDialog();
143        Main.map.repaint();
144    }
145
146    protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException {
147        reader = new OsmServerBackreferenceReader(id, type);
148        DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
149        synchronized (this) { // avoid race condition in cancel()
150            reader = null;
151        }
152        Collection<Way> ways = ds.getWays();
153
154        DataSetMerger merger;
155        if (!ways.isEmpty()) {
156            Set<Node> nodes = new HashSet<>();
157            for (Way w: ways) {
158                // Ensure each node is only listed once
159                nodes.addAll(w.getNodes());
160            }
161            // Don't retrieve any nodes we've already grabbed
162            nodes.removeAll(targetLayer.data.getNodes());
163            if (!nodes.isEmpty()) {
164                reader = MultiFetchServerObjectReader.create();
165                ((MultiFetchServerObjectReader) reader).append(nodes);
166                DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
167                synchronized (this) { // avoid race condition in cancel()
168                    reader = null;
169                }
170                merger = new DataSetMerger(ds, wayNodes);
171                merger.merge();
172            }
173        }
174        merger = new DataSetMerger(parents, ds);
175        merger.merge();
176    }
177
178    @Override
179    protected void realRun() throws SAXException, IOException, OsmTransferException {
180        try {
181            progressMonitor.setTicksCount(children.size());
182            int i = 1;
183            for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) {
184                if (canceled)
185                    return;
186                String msg;
187                switch(entry.getValue()) {
188                case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1, children.size(), entry.getKey()); break;
189                case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1, children.size(), entry.getKey()); break;
190                case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1, children.size(), entry.getKey()); break;
191                default: throw new AssertionError();
192                }
193                progressMonitor.subTask(msg);
194                downloadParents(entry.getKey(), entry.getValue(), progressMonitor);
195                i++;
196            }
197        } catch (OsmTransferException e) {
198            if (canceled)
199                return;
200            lastException = e;
201        }
202    }
203}