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}