001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.awt.Font;
008import java.awt.GridBagLayout;
009import java.io.IOException;
010import java.text.MessageFormat;
011import java.util.ArrayList;
012import java.util.HashSet;
013import java.util.List;
014import java.util.Set;
015
016import javax.swing.JLabel;
017import javax.swing.JOptionPane;
018import javax.swing.JPanel;
019import javax.swing.JScrollPane;
020
021import org.openstreetmap.josm.actions.downloadtasks.DownloadReferrersTask;
022import org.openstreetmap.josm.data.osm.DataSet;
023import org.openstreetmap.josm.data.osm.OsmPrimitive;
024import org.openstreetmap.josm.data.osm.PrimitiveId;
025import org.openstreetmap.josm.gui.ExtendedDialog;
026import org.openstreetmap.josm.gui.MainApplication;
027import org.openstreetmap.josm.gui.PleaseWaitRunnable;
028import org.openstreetmap.josm.gui.layer.OsmDataLayer;
029import org.openstreetmap.josm.gui.progress.ProgressMonitor;
030import org.openstreetmap.josm.gui.util.GuiHelper;
031import org.openstreetmap.josm.gui.widgets.HtmlPanel;
032import org.openstreetmap.josm.gui.widgets.JosmTextArea;
033import org.openstreetmap.josm.io.OsmTransferException;
034import org.openstreetmap.josm.tools.GBC;
035import org.openstreetmap.josm.tools.Utils;
036import org.xml.sax.SAXException;
037
038/**
039 * Task for downloading a set of primitives with all referrers.
040 */
041public class DownloadPrimitivesWithReferrersTask extends PleaseWaitRunnable {
042    /** If true download into a new layer */
043    private final boolean newLayer;
044    /** List of primitives id to download */
045    private final List<PrimitiveId> ids;
046    /** If true, download members for relation */
047    private final boolean full;
048    /** If true, download also referrers */
049    private final boolean downloadReferrers;
050
051    /** Temporary layer where downloaded primitives are put */
052    private final OsmDataLayer tmpLayer;
053    /** Reference to the task that download requested primitives */
054    private DownloadPrimitivesTask mainTask;
055    /** Flag indicated that user ask for cancel this task */
056    private boolean canceled;
057    /** Reference to the task currently running */
058    private PleaseWaitRunnable currentTask;
059
060    /**
061     * Constructor
062     *
063     * @param newLayer if the data should be downloaded into a new layer
064     * @param ids List of primitive id to download
065     * @param downloadReferrers if the referrers of the object should be downloaded as well,
066     *     i.e., parent relations, and for nodes, additionally, parent ways
067     * @param full if the members of a relation should be downloaded as well
068     * @param newLayerName the name to use for the new layer, can be {@code null}.
069     * @param monitor ProgressMonitor to use, or null to create a new one
070     */
071    public DownloadPrimitivesWithReferrersTask(boolean newLayer, List<PrimitiveId> ids, boolean downloadReferrers,
072            boolean full, String newLayerName, ProgressMonitor monitor) {
073        super(tr("Download objects"), monitor, false);
074        this.ids = ids;
075        this.downloadReferrers = downloadReferrers;
076        this.full = full;
077        this.newLayer = newLayer;
078        // Check we don't try to download new primitives
079        for (PrimitiveId primitiveId : ids) {
080            if (primitiveId.isNew()) {
081                throw new IllegalArgumentException(MessageFormat.format(
082                        "Cannot download new primitives (ID {0})", primitiveId.getUniqueId()));
083            }
084        }
085        // All downloaded primitives are put in a tmpLayer
086        tmpLayer = new OsmDataLayer(new DataSet(), newLayerName != null ? newLayerName : OsmDataLayer.createNewName(), null);
087    }
088
089    /**
090     * Cancel recursively the task. Do not call directly
091     * @see DownloadPrimitivesWithReferrersTask#operationCanceled()
092     */
093    @Override
094    protected void cancel() {
095        synchronized (this) {
096            canceled = true;
097            if (currentTask != null)
098                currentTask.operationCanceled();
099        }
100    }
101
102    @Override
103    protected void realRun() throws SAXException, IOException, OsmTransferException {
104        getProgressMonitor().setTicksCount(ids.size()+1);
105        // First, download primitives
106        mainTask = new DownloadPrimitivesTask(tmpLayer, ids, full, getProgressMonitor().createSubTaskMonitor(1, false));
107        synchronized (this) {
108            currentTask = mainTask;
109            if (canceled) {
110                currentTask = null;
111                return;
112            }
113        }
114        currentTask.run();
115        // Then, download referrers for each primitive
116        if (downloadReferrers)
117            for (PrimitiveId id : ids) {
118                synchronized (this) {
119                    if (canceled) {
120                        currentTask = null;
121                        return;
122                    }
123                    currentTask = new DownloadReferrersTask(
124                            tmpLayer, id, getProgressMonitor().createSubTaskMonitor(1, false));
125                }
126                currentTask.run();
127            }
128        currentTask = null;
129    }
130
131    @Override
132    protected void finish() {
133        synchronized (this) {
134            if (canceled)
135                return;
136        }
137
138        // Append downloaded data to JOSM
139        OsmDataLayer layer = MainApplication.getLayerManager().getEditLayer();
140        if (layer == null || this.newLayer || !layer.isDownloadable())
141            MainApplication.getLayerManager().addLayer(tmpLayer);
142        else
143            layer.mergeFrom(tmpLayer);
144
145        // Warm about missing primitives
146        final Set<PrimitiveId> errs = mainTask.getMissingPrimitives();
147        if (errs != null && !errs.isEmpty())
148            GuiHelper.runInEDTAndWait(() -> reportProblemDialog(errs,
149                    trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()),
150                    trn("One object could not be downloaded.<br>",
151                            "{0} objects could not be downloaded.<br>",
152                            errs.size(),
153                            errs.size())
154                            + tr("The server replied with response code 404.<br>"
155                                 + "This usually means, the server does not know an object with the requested id."),
156                    tr("missing objects:"),
157                    JOptionPane.ERROR_MESSAGE
158                    ).showDialog());
159
160        // Warm about deleted primitives
161        final Set<PrimitiveId> del = new HashSet<>();
162        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
163        for (PrimitiveId id : ids) {
164            OsmPrimitive osm = ds.getPrimitiveById(id);
165            if (osm != null && osm.isDeleted()) {
166                del.add(id);
167            }
168        }
169        if (!del.isEmpty())
170            GuiHelper.runInEDTAndWait(() -> reportProblemDialog(del,
171                    trn("Object deleted", "Objects deleted", del.size()),
172                    trn(
173                        "One downloaded object is deleted.",
174                        "{0} downloaded objects are deleted.",
175                        del.size(),
176                        del.size()),
177                    null,
178                    JOptionPane.WARNING_MESSAGE
179            ).showDialog());
180    }
181
182    /**
183     * Return id of really downloaded primitives.
184     * @return List of primitives id or null if no primitives was downloaded
185     */
186    public List<PrimitiveId> getDownloadedId() {
187        synchronized (this) {
188            if (canceled)
189                return null;
190        }
191        List<PrimitiveId> downloaded = new ArrayList<>(ids);
192        downloaded.removeAll(mainTask.getMissingPrimitives());
193        return downloaded;
194    }
195
196    /**
197     * Dialog for report a problem during download.
198     * @param errs Primitives involved
199     * @param title Title of dialog
200     * @param text Detail message
201     * @param listLabel List of primitives description
202     * @param msgType Type of message, see {@link JOptionPane}
203     * @return The Dialog object
204     */
205    private static ExtendedDialog reportProblemDialog(Set<PrimitiveId> errs,
206            String title, String text, String listLabel, int msgType) {
207        JPanel p = new JPanel(new GridBagLayout());
208        p.add(new HtmlPanel(text), GBC.eop());
209        JosmTextArea txt = new JosmTextArea();
210        if (listLabel != null) {
211            JLabel missing = new JLabel(listLabel);
212            missing.setFont(missing.getFont().deriveFont(Font.PLAIN));
213            missing.setLabelFor(txt);
214            p.add(missing, GBC.eol());
215        }
216        txt.setFont(GuiHelper.getMonospacedFont(txt));
217        txt.setEditable(false);
218        txt.setBackground(p.getBackground());
219        txt.setColumns(40);
220        txt.setRows(1);
221        txt.setText(Utils.join(", ", errs));
222        JScrollPane scroll = new JScrollPane(txt);
223        p.add(scroll, GBC.eop().weight(1.0, 0.0).fill(GBC.HORIZONTAL));
224
225        return new ExtendedDialog(
226                MainApplication.getMainFrame(),
227                title,
228                tr("Ok"))
229        .setButtonIcons("ok")
230        .setIcon(msgType)
231        .setContent(p, false);
232    }
233}