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; 015import java.util.stream.Collectors; 016 017import javax.swing.JLabel; 018import javax.swing.JOptionPane; 019import javax.swing.JPanel; 020import javax.swing.JScrollPane; 021 022import org.openstreetmap.josm.actions.downloadtasks.DownloadReferrersTask; 023import org.openstreetmap.josm.data.osm.DataSet; 024import org.openstreetmap.josm.data.osm.OsmPrimitive; 025import org.openstreetmap.josm.data.osm.PrimitiveId; 026import org.openstreetmap.josm.gui.ExtendedDialog; 027import org.openstreetmap.josm.gui.MainApplication; 028import org.openstreetmap.josm.gui.PleaseWaitRunnable; 029import org.openstreetmap.josm.gui.layer.OsmDataLayer; 030import org.openstreetmap.josm.gui.progress.ProgressMonitor; 031import org.openstreetmap.josm.gui.util.GuiHelper; 032import org.openstreetmap.josm.gui.widgets.HtmlPanel; 033import org.openstreetmap.josm.gui.widgets.JosmTextArea; 034import org.openstreetmap.josm.io.OsmTransferException; 035import org.openstreetmap.josm.tools.GBC; 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 currentTask = new DownloadReferrersTask(tmpLayer, ids); 118 currentTask.run(); 119 synchronized (this) { 120 if (currentTask.getProgressMonitor().isCanceled()) 121 cancel(); 122 } 123 } 124 currentTask = null; 125 } 126 127 @Override 128 protected void finish() { 129 synchronized (this) { 130 if (canceled) 131 return; 132 } 133 134 // Append downloaded data to JOSM 135 OsmDataLayer layer = MainApplication.getLayerManager().getEditLayer(); 136 if (layer == null || this.newLayer || !layer.isDownloadable()) 137 MainApplication.getLayerManager().addLayer(tmpLayer); 138 else 139 layer.mergeFrom(tmpLayer); 140 141 // Warm about missing primitives 142 final Set<PrimitiveId> errs = mainTask.getMissingPrimitives(); 143 if (errs != null && !errs.isEmpty()) 144 GuiHelper.runInEDTAndWait(() -> reportProblemDialog(errs, 145 trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()), 146 trn("One object could not be downloaded.<br>", 147 "{0} objects could not be downloaded.<br>", 148 errs.size(), 149 errs.size()) 150 + tr("The server replied with response code 404.<br>" 151 + "This usually means, the server does not know an object with the requested id."), 152 tr("missing objects:"), 153 JOptionPane.ERROR_MESSAGE 154 ).showDialog()); 155 156 // Warm about deleted primitives 157 final Set<PrimitiveId> del = new HashSet<>(); 158 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 159 for (PrimitiveId id : ids) { 160 OsmPrimitive osm = ds.getPrimitiveById(id); 161 if (osm != null && osm.isDeleted()) { 162 del.add(id); 163 } 164 } 165 if (!del.isEmpty()) 166 GuiHelper.runInEDTAndWait(() -> reportProblemDialog(del, 167 trn("Object deleted", "Objects deleted", del.size()), 168 trn( 169 "One downloaded object is deleted.", 170 "{0} downloaded objects are deleted.", 171 del.size(), 172 del.size()), 173 null, 174 JOptionPane.WARNING_MESSAGE 175 ).showDialog()); 176 } 177 178 /** 179 * Return ids of really downloaded primitives. 180 * @return List of primitives id or null if no primitives were downloaded 181 */ 182 public List<PrimitiveId> getDownloadedId() { 183 synchronized (this) { 184 if (canceled) 185 return null; 186 } 187 List<PrimitiveId> downloaded = new ArrayList<>(ids); 188 downloaded.removeAll(mainTask.getMissingPrimitives()); 189 return downloaded; 190 } 191 192 /** 193 * Dialog for report a problem during download. 194 * @param errs Primitives involved 195 * @param title Title of dialog 196 * @param text Detail message 197 * @param listLabel List of primitives description 198 * @param msgType Type of message, see {@link JOptionPane} 199 * @return The Dialog object 200 */ 201 private static ExtendedDialog reportProblemDialog(Set<PrimitiveId> errs, 202 String title, String text, String listLabel, int msgType) { 203 JPanel p = new JPanel(new GridBagLayout()); 204 p.add(new HtmlPanel(text), GBC.eop()); 205 JosmTextArea txt = new JosmTextArea(); 206 if (listLabel != null) { 207 JLabel missing = new JLabel(listLabel); 208 missing.setFont(missing.getFont().deriveFont(Font.PLAIN)); 209 missing.setLabelFor(txt); 210 p.add(missing, GBC.eol()); 211 } 212 txt.setFont(GuiHelper.getMonospacedFont(txt)); 213 txt.setEditable(false); 214 txt.setBackground(p.getBackground()); 215 txt.setColumns(40); 216 txt.setRows(1); 217 txt.setText(errs.stream().map(String::valueOf).collect(Collectors.joining(", "))); 218 JScrollPane scroll = new JScrollPane(txt); 219 p.add(scroll, GBC.eop().weight(1.0, 0.0).fill(GBC.HORIZONTAL)); 220 221 return new ExtendedDialog( 222 MainApplication.getMainFrame(), 223 title, 224 tr("Ok")) 225 .setButtonIcons("ok") 226 .setIcon(msgType) 227 .setContent(p, false); 228 } 229}