001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.downloadtasks; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.io.IOException; 008import java.text.MessageFormat; 009import java.util.Collection; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.Map; 013import java.util.Map.Entry; 014import java.util.Set; 015 016import javax.swing.JOptionPane; 017import javax.swing.SwingUtilities; 018 019import org.openstreetmap.josm.Main; 020import org.openstreetmap.josm.data.osm.DataSet; 021import org.openstreetmap.josm.data.osm.DataSetMerger; 022import org.openstreetmap.josm.data.osm.Node; 023import org.openstreetmap.josm.data.osm.OsmPrimitive; 024import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 025import org.openstreetmap.josm.data.osm.PrimitiveId; 026import org.openstreetmap.josm.data.osm.Way; 027import org.openstreetmap.josm.gui.PleaseWaitRunnable; 028import org.openstreetmap.josm.gui.layer.OsmDataLayer; 029import org.openstreetmap.josm.gui.progress.ProgressMonitor; 030import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 031import org.openstreetmap.josm.io.OsmServerBackreferenceReader; 032import org.openstreetmap.josm.io.OsmServerReader; 033import org.openstreetmap.josm.io.OsmTransferException; 034import org.openstreetmap.josm.tools.CheckParameterUtil; 035import org.openstreetmap.josm.tools.ExceptionUtil; 036import org.xml.sax.SAXException; 037 038/** 039 * The asynchronous task for downloading referring primitives 040 * @since 2923 041 */ 042public class DownloadReferrersTask extends PleaseWaitRunnable { 043 private boolean canceled; 044 private Exception lastException; 045 private OsmServerReader reader; 046 /** the target layer */ 047 private OsmDataLayer targetLayer; 048 /** the collection of child primitives */ 049 private Map<Long, OsmPrimitiveType> children; 050 /** the parents */ 051 private DataSet parents; 052 053 /** 054 * constructor 055 * 056 * @param targetLayer the target layer for the downloaded primitives. Must not be null. 057 * @param children the collection of child primitives for which parents are to be downloaded 058 */ 059 public DownloadReferrersTask(OsmDataLayer targetLayer, Collection<OsmPrimitive> children) { 060 super("Download referrers", false /* don't ignore exception*/); 061 CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer"); 062 canceled = false; 063 this.children = new HashMap<>(); 064 if (children != null) { 065 for (OsmPrimitive p: children) { 066 if (!p.isNew()) { 067 this.children.put(p.getId(), OsmPrimitiveType.from(p)); 068 } 069 } 070 } 071 this.targetLayer = targetLayer; 072 parents = new DataSet(); 073 } 074 075 /** 076 * constructor 077 * 078 * @param targetLayer the target layer for the downloaded primitives. Must not be null. 079 * @param children the collection of children for which parents are to be downloaded. Children 080 * are specified by their id and their type. 081 */ 082 public DownloadReferrersTask(OsmDataLayer targetLayer, Map<Long, OsmPrimitiveType> children) { 083 super("Download referrers", false /* don't ignore exception*/); 084 CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer"); 085 canceled = false; 086 this.children = new HashMap<>(); 087 if (children != null) { 088 for (Entry<Long, OsmPrimitiveType> entry : children.entrySet()) { 089 if (entry.getKey() > 0 && entry.getValue() != null) { 090 children.put(entry.getKey(), entry.getValue()); 091 } 092 } 093 } 094 this.targetLayer = targetLayer; 095 parents = new DataSet(); 096 } 097 098 /** 099 * constructor 100 * 101 * @param targetLayer the target layer. Must not be null. 102 * @param id the primitive id. id > 0 required. 103 * @param type the primitive type. type != null required 104 * @throws IllegalArgumentException if id <= 0 105 * @throws IllegalArgumentException if type == null 106 * @throws IllegalArgumentException if targetLayer == null 107 */ 108 public DownloadReferrersTask(OsmDataLayer targetLayer, long id, OsmPrimitiveType type) { 109 super("Download referrers", false /* don't ignore exception*/); 110 CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer"); 111 if (id <= 0) 112 throw new IllegalArgumentException(MessageFormat.format("Id > 0 required, got {0}", id)); 113 CheckParameterUtil.ensureParameterNotNull(type, "type"); 114 canceled = false; 115 this.children = new HashMap<>(); 116 this.children.put(id, type); 117 this.targetLayer = targetLayer; 118 parents = new DataSet(); 119 } 120 121 /** 122 * constructor 123 * 124 * @param targetLayer the target layer. Must not be null. 125 * @param primitiveId a PrimitiveId object. 126 * @throws IllegalArgumentException if id <= 0 127 * @throws IllegalArgumentException if targetLayer == null 128 */ 129 public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId) { 130 this(targetLayer, primitiveId, null); 131 } 132 133 /** 134 * constructor 135 * 136 * @param targetLayer the target layer. Must not be null. 137 * @param primitiveId a PrimitiveId object. 138 * @param progressMonitor ProgressMonitor to use or null to create a new one. 139 * @throws IllegalArgumentException if id <= 0 140 * @throws IllegalArgumentException if targetLayer == null 141 */ 142 public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId, 143 ProgressMonitor progressMonitor) { 144 super("Download referrers", progressMonitor, false /* don't ignore exception*/); 145 CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer"); 146 if (primitiveId.isNew()) 147 throw new IllegalArgumentException(MessageFormat.format( 148 "Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId())); 149 canceled = false; 150 this.children = new HashMap<>(); 151 this.children.put(primitiveId.getUniqueId(), primitiveId.getType()); 152 this.targetLayer = targetLayer; 153 parents = new DataSet(); 154 } 155 156 @Override 157 protected void cancel() { 158 canceled = true; 159 synchronized (this) { 160 if (reader != null) { 161 reader.cancel(); 162 } 163 } 164 } 165 166 @Override 167 protected void finish() { 168 if (canceled) 169 return; 170 if (lastException != null) { 171 ExceptionUtil.explainException(lastException); 172 return; 173 } 174 175 DataSetMerger visitor = new DataSetMerger(targetLayer.data, parents); 176 visitor.merge(); 177 SwingUtilities.invokeLater( 178 new Runnable() { 179 @Override 180 public void run() { 181 targetLayer.onPostDownloadFromServer(); 182 if (Main.map != null) 183 Main.map.mapView.repaint(); 184 } 185 } 186 ); 187 if (visitor.getConflicts().isEmpty()) 188 return; 189 targetLayer.getConflicts().add(visitor.getConflicts()); 190 JOptionPane.showMessageDialog( 191 Main.parent, 192 trn("There was {0} conflict during import.", 193 "There were {0} conflicts during import.", 194 visitor.getConflicts().size(), 195 visitor.getConflicts().size() 196 ), 197 trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()), 198 JOptionPane.WARNING_MESSAGE 199 ); 200 Main.map.conflictDialog.unfurlDialog(); 201 Main.map.repaint(); 202 } 203 204 protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException { 205 reader = new OsmServerBackreferenceReader(id, type); 206 DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 207 synchronized (this) { // avoid race condition in cancel() 208 reader = null; 209 } 210 Collection<Way> ways = ds.getWays(); 211 212 DataSetMerger merger; 213 if (!ways.isEmpty()) { 214 Set<Node> nodes = new HashSet<>(); 215 for (Way w: ways) { 216 // Ensure each node is only listed once 217 nodes.addAll(w.getNodes()); 218 } 219 // Don't retrieve any nodes we've already grabbed 220 nodes.removeAll(targetLayer.data.getNodes()); 221 if (!nodes.isEmpty()) { 222 reader = MultiFetchServerObjectReader.create(); 223 ((MultiFetchServerObjectReader) reader).append(nodes); 224 DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 225 synchronized (this) { // avoid race condition in cancel() 226 reader = null; 227 } 228 merger = new DataSetMerger(ds, wayNodes); 229 merger.merge(); 230 } 231 } 232 merger = new DataSetMerger(parents, ds); 233 merger.merge(); 234 } 235 236 @Override 237 protected void realRun() throws SAXException, IOException, OsmTransferException { 238 try { 239 progressMonitor.setTicksCount(children.size()); 240 int i = 1; 241 for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) { 242 if (canceled) 243 return; 244 String msg = ""; 245 switch(entry.getValue()) { 246 case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1, children.size(), entry.getKey()); break; 247 case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1, children.size(), entry.getKey()); break; 248 case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1, children.size(), entry.getKey()); break; 249 } 250 progressMonitor.subTask(msg); 251 downloadParents(entry.getKey(), entry.getValue(), progressMonitor); 252 i++; 253 } 254 } catch (Exception e) { 255 if (canceled) 256 return; 257 lastException = e; 258 } 259 } 260}