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