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