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