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