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(() -> reportProblemDialog(errs, 141 trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()), 142 trn("One object could not be downloaded.<br>", 143 "{0} objects could not be downloaded.<br>", 144 errs.size(), 145 errs.size()) 146 + tr("The server replied with response code 404.<br>" 147 + "This usually means, the server does not know an object with the requested id."), 148 tr("missing objects:"), 149 JOptionPane.ERROR_MESSAGE 150 ).showDialog()); 151 152 // Warm about deleted primitives 153 final Set<PrimitiveId> del = new HashSet<>(); 154 DataSet ds = Main.getLayerManager().getEditDataSet(); 155 for (PrimitiveId id : ids) { 156 OsmPrimitive osm = ds.getPrimitiveById(id); 157 if (osm != null && osm.isDeleted()) { 158 del.add(id); 159 } 160 } 161 if (!del.isEmpty()) 162 GuiHelper.runInEDTAndWait(() -> reportProblemDialog(del, 163 trn("Object deleted", "Objects deleted", del.size()), 164 trn( 165 "One downloaded object is deleted.", 166 "{0} downloaded objects are deleted.", 167 del.size(), 168 del.size()), 169 null, 170 JOptionPane.WARNING_MESSAGE 171 ).showDialog()); 172 } 173 174 /** 175 * Return id of really downloaded primitives. 176 * @return List of primitives id or null if no primitives was downloaded 177 */ 178 public List<PrimitiveId> getDownloadedId() { 179 synchronized (this) { 180 if (canceled) 181 return null; 182 } 183 List<PrimitiveId> downloaded = new ArrayList<>(ids); 184 downloaded.removeAll(mainTask.getMissingPrimitives()); 185 return downloaded; 186 } 187 188 /** 189 * Dialog for report a problem during download. 190 * @param errs Primitives involved 191 * @param title Title of dialog 192 * @param text Detail message 193 * @param listLabel List of primitives description 194 * @param msgType Type of message, see {@link JOptionPane} 195 * @return The Dialog object 196 */ 197 private static ExtendedDialog reportProblemDialog(Set<PrimitiveId> errs, 198 String title, String text, String listLabel, int msgType) { 199 JPanel p = new JPanel(new GridBagLayout()); 200 p.add(new HtmlPanel(text), GBC.eop()); 201 JosmTextArea txt = new JosmTextArea(); 202 if (listLabel != null) { 203 JLabel missing = new JLabel(listLabel); 204 missing.setFont(missing.getFont().deriveFont(Font.PLAIN)); 205 missing.setLabelFor(txt); 206 p.add(missing, GBC.eol()); 207 } 208 txt.setFont(GuiHelper.getMonospacedFont(txt)); 209 txt.setEditable(false); 210 txt.setBackground(p.getBackground()); 211 txt.setColumns(40); 212 txt.setRows(1); 213 txt.setText(Utils.join(", ", errs)); 214 JScrollPane scroll = new JScrollPane(txt); 215 p.add(scroll, GBC.eop().weight(1.0, 0.0).fill(GBC.HORIZONTAL)); 216 217 return new ExtendedDialog( 218 Main.parent, 219 title, 220 new String[] {tr("Ok")}) 221 .setButtonIcons(new String[] {"ok"}) 222 .setIcon(msgType) 223 .setContent(p, false); 224 } 225}