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#operationCancel 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}