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