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.data.osm.DataSet; 020import org.openstreetmap.josm.data.osm.DataSetMerger; 021import org.openstreetmap.josm.data.osm.Node; 022import org.openstreetmap.josm.data.osm.OsmPrimitive; 023import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 024import org.openstreetmap.josm.data.osm.PrimitiveId; 025import org.openstreetmap.josm.data.osm.Way; 026import org.openstreetmap.josm.gui.MainApplication; 027import org.openstreetmap.josm.gui.MapFrame; 028import org.openstreetmap.josm.gui.PleaseWaitRunnable; 029import org.openstreetmap.josm.gui.layer.OsmDataLayer; 030import org.openstreetmap.josm.gui.progress.ProgressMonitor; 031import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 032import org.openstreetmap.josm.io.OsmServerBackreferenceReader; 033import org.openstreetmap.josm.io.OsmServerReader; 034import org.openstreetmap.josm.io.OsmTransferException; 035import org.openstreetmap.josm.tools.CheckParameterUtil; 036import org.openstreetmap.josm.tools.ExceptionUtil; 037import org.xml.sax.SAXException; 038 039/** 040 * The asynchronous task for downloading referring primitives 041 * @since 2923 042 */ 043public class DownloadReferrersTask extends PleaseWaitRunnable { 044 private boolean canceled; 045 private Exception lastException; 046 private OsmServerReader reader; 047 /** the target layer */ 048 private final OsmDataLayer targetLayer; 049 /** the collection of child primitives */ 050 private final Map<Long, OsmPrimitiveType> children; 051 /** the parents */ 052 private final DataSet parents; 053 054 /** 055 * constructor 056 * 057 * @param targetLayer the target layer for the downloaded primitives. Must not be null. 058 * @param children the collection of child primitives for which parents are to be downloaded 059 */ 060 public DownloadReferrersTask(OsmDataLayer targetLayer, Collection<OsmPrimitive> children) { 061 super("Download referrers", false /* don't ignore exception*/); 062 CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer"); 063 if (!targetLayer.isDownloadable()) { 064 throw new IllegalArgumentException("Non-downloadable layer: " + targetLayer); 065 } 066 canceled = false; 067 this.children = new HashMap<>(); 068 if (children != null) { 069 for (OsmPrimitive p: children) { 070 if (!p.isNew()) { 071 this.children.put(p.getId(), OsmPrimitiveType.from(p)); 072 } 073 } 074 } 075 this.targetLayer = targetLayer; 076 parents = new DataSet(); 077 } 078 079 /** 080 * constructor 081 * 082 * @param targetLayer the target layer. Must not be null. 083 * @param primitiveId a PrimitiveId object. 084 * @param progressMonitor ProgressMonitor to use or null to create a new one. 085 * @throws IllegalArgumentException if id <= 0 086 * @throws IllegalArgumentException if targetLayer == null 087 */ 088 public DownloadReferrersTask(OsmDataLayer targetLayer, PrimitiveId primitiveId, 089 ProgressMonitor progressMonitor) { 090 super("Download referrers", progressMonitor, false /* don't ignore exception*/); 091 CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer"); 092 if (primitiveId.isNew()) 093 throw new IllegalArgumentException(MessageFormat.format( 094 "Cannot download referrers for new primitives (ID {0})", primitiveId.getUniqueId())); 095 canceled = false; 096 this.children = new HashMap<>(); 097 this.children.put(primitiveId.getUniqueId(), primitiveId.getType()); 098 this.targetLayer = targetLayer; 099 parents = new DataSet(); 100 } 101 102 @Override 103 protected void cancel() { 104 canceled = true; 105 synchronized (this) { 106 if (reader != null) { 107 reader.cancel(); 108 } 109 } 110 } 111 112 @Override 113 protected void finish() { 114 if (canceled) 115 return; 116 if (lastException != null) { 117 ExceptionUtil.explainException(lastException); 118 return; 119 } 120 121 DataSetMerger visitor = new DataSetMerger(targetLayer.getDataSet(), parents); 122 visitor.merge(); 123 SwingUtilities.invokeLater(targetLayer::onPostDownloadFromServer); 124 if (visitor.getConflicts().isEmpty()) 125 return; 126 targetLayer.getConflicts().add(visitor.getConflicts()); 127 JOptionPane.showMessageDialog( 128 MainApplication.getMainFrame(), 129 trn("There was {0} conflict during import.", 130 "There were {0} conflicts during import.", 131 visitor.getConflicts().size(), 132 visitor.getConflicts().size() 133 ), 134 trn("Conflict during download", "Conflicts during download", visitor.getConflicts().size()), 135 JOptionPane.WARNING_MESSAGE 136 ); 137 MapFrame map = MainApplication.getMap(); 138 map.conflictDialog.unfurlDialog(); 139 map.repaint(); 140 } 141 142 protected void downloadParents(long id, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException { 143 reader = new OsmServerBackreferenceReader(id, type); 144 DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 145 synchronized (this) { // avoid race condition in cancel() 146 reader = null; 147 } 148 Collection<Way> ways = ds.getWays(); 149 150 DataSetMerger merger; 151 if (!ways.isEmpty()) { 152 Set<Node> nodes = new HashSet<>(); 153 for (Way w: ways) { 154 // Ensure each node is only listed once 155 nodes.addAll(w.getNodes()); 156 } 157 // Don't retrieve any nodes we've already grabbed 158 nodes.removeAll(targetLayer.data.getNodes()); 159 if (!nodes.isEmpty()) { 160 reader = MultiFetchServerObjectReader.create(); 161 ((MultiFetchServerObjectReader) reader).append(nodes); 162 DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 163 synchronized (this) { // avoid race condition in cancel() 164 reader = null; 165 } 166 merger = new DataSetMerger(ds, wayNodes); 167 merger.merge(); 168 } 169 } 170 merger = new DataSetMerger(parents, ds); 171 merger.merge(); 172 } 173 174 @Override 175 protected void realRun() throws SAXException, IOException, OsmTransferException { 176 try { 177 progressMonitor.setTicksCount(children.size()); 178 int i = 1; 179 for (Entry<Long, OsmPrimitiveType> entry: children.entrySet()) { 180 if (canceled) 181 return; 182 String msg; 183 switch(entry.getValue()) { 184 case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i+1, children.size(), entry.getKey()); break; 185 case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i+1, children.size(), entry.getKey()); break; 186 case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i+1, children.size(), entry.getKey()); break; 187 default: throw new AssertionError(); 188 } 189 progressMonitor.subTask(msg); 190 downloadParents(entry.getKey(), entry.getValue(), progressMonitor); 191 i++; 192 } 193 } catch (OsmTransferException e) { 194 if (canceled) 195 return; 196 lastException = e; 197 } 198 } 199}