001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.io.IOException; 008import java.util.List; 009import java.util.Set; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.actions.AutoScaleAction; 013import org.openstreetmap.josm.data.osm.DataSet; 014import org.openstreetmap.josm.data.osm.DataSetMerger; 015import org.openstreetmap.josm.data.osm.Node; 016import org.openstreetmap.josm.data.osm.OsmPrimitive; 017import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 018import org.openstreetmap.josm.data.osm.PrimitiveId; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.Way; 021import org.openstreetmap.josm.gui.ExceptionDialogUtil; 022import org.openstreetmap.josm.gui.PleaseWaitRunnable; 023import org.openstreetmap.josm.gui.layer.OsmDataLayer; 024import org.openstreetmap.josm.gui.progress.ProgressMonitor; 025import org.openstreetmap.josm.gui.util.GuiHelper; 026import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 027import org.openstreetmap.josm.io.OsmServerObjectReader; 028import org.openstreetmap.josm.io.OsmTransferException; 029import org.xml.sax.SAXException; 030 031public class DownloadPrimitivesTask extends PleaseWaitRunnable { 032 private DataSet ds; 033 private boolean canceled; 034 private Exception lastException; 035 private final List<PrimitiveId> ids; 036 037 private Set<PrimitiveId> missingPrimitives; 038 039 private final OsmDataLayer layer; 040 private final boolean fullRelation; 041 private MultiFetchServerObjectReader multiObjectReader; 042 private OsmServerObjectReader objectReader; 043 044 /** 045 * Creates the task 046 * 047 * @param layer the layer in which primitives are updated. Must not be null. 048 * @param ids a collection of primitives to update from the server. Set to 049 * the empty collection if null. 050 * @param fullRelation true if a full download is required, i.e., 051 * a download including the immediate children of a relation. 052 * @throws IllegalArgumentException thrown if layer is null. 053 */ 054 public DownloadPrimitivesTask(OsmDataLayer layer, List<PrimitiveId> ids, boolean fullRelation) throws IllegalArgumentException { 055 this(layer, ids, fullRelation, null); 056 } 057 058 /** 059 * Creates the task 060 * 061 * @param layer the layer in which primitives are updated. Must not be null. 062 * @param ids a collection of primitives to update from the server. Set to 063 * the empty collection if null. 064 * @param fullRelation true if a full download is required, i.e., 065 * a download including the immediate children of a relation. 066 * @param progressMonitor ProgressMonitor to use or null to create a new one. 067 * @throws IllegalArgumentException thrown if layer is null. 068 */ 069 public DownloadPrimitivesTask(OsmDataLayer layer, List<PrimitiveId> ids, boolean fullRelation, 070 ProgressMonitor progessMonitor) throws IllegalArgumentException { 071 super(tr("Download objects"), progessMonitor, false /* don't ignore exception */); 072 ensureParameterNotNull(layer, "layer"); 073 this.ids = ids; 074 this.layer = layer; 075 this.fullRelation = fullRelation; 076 } 077 078 @Override 079 protected void cancel() { 080 canceled = true; 081 synchronized(this) { 082 if (multiObjectReader != null) { 083 multiObjectReader.cancel(); 084 } 085 if (objectReader != null) { 086 objectReader.cancel(); 087 } 088 } 089 } 090 091 @Override 092 protected void finish() { 093 if (canceled) 094 return; 095 if (lastException != null) { 096 ExceptionDialogUtil.explainException(lastException); 097 return; 098 } 099 GuiHelper.runInEDTAndWait(new Runnable() { 100 @Override 101 public void run() { 102 layer.mergeFrom(ds); 103 if(Main.map != null) 104 AutoScaleAction.zoomTo(ds.allPrimitives()); 105 layer.onPostDownloadFromServer(); 106 } 107 }); 108 } 109 110 protected void initMultiFetchReader(MultiFetchServerObjectReader reader) { 111 getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to download ...")); 112 for (PrimitiveId id : ids) { 113 OsmPrimitive osm = layer.data.getPrimitiveById(id); 114 if (osm == null) { 115 switch (id.getType()) { 116 case NODE: 117 osm = new Node(id.getUniqueId()); 118 break; 119 case WAY: 120 osm = new Way(id.getUniqueId()); 121 break; 122 case RELATION: 123 osm = new Relation(id.getUniqueId()); 124 break; 125 default: throw new AssertionError(); 126 } 127 } 128 reader.append(osm); 129 } 130 } 131 132 @Override 133 protected void realRun() throws SAXException, IOException, OsmTransferException { 134 this.ds = new DataSet(); 135 DataSet theirDataSet; 136 try { 137 synchronized(this) { 138 if (canceled) return; 139 multiObjectReader = new MultiFetchServerObjectReader(); 140 } 141 initMultiFetchReader(multiObjectReader); 142 theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 143 missingPrimitives = multiObjectReader.getMissingPrimitives(); 144 synchronized(this) { 145 multiObjectReader = null; 146 } 147 DataSetMerger merger = new DataSetMerger(ds, theirDataSet); 148 merger.merge(); 149 150 // if incomplete relation members exist, download them too 151 for (Relation r : ds.getRelations()) { 152 if (canceled) return; 153 if (r.hasIncompleteMembers()) { 154 synchronized(this) { 155 if (canceled) return; 156 objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation); 157 } 158 theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 159 synchronized (this) { 160 objectReader = null; 161 } 162 merger = new DataSetMerger(ds, theirDataSet); 163 merger.merge(); 164 } 165 } 166 167 // a way loaded with MultiFetch may have incomplete nodes because at least one of its 168 // nodes isn't present in the local data set. We therefore fully load all 169 // ways with incomplete nodes. 170 // 171 for (Way w : ds.getWays()) { 172 if (canceled) return; 173 if (w.hasIncompleteNodes()) { 174 synchronized(this) { 175 if (canceled) return; 176 objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */); 177 } 178 theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 179 synchronized (this) { 180 objectReader = null; 181 } 182 merger = new DataSetMerger(ds, theirDataSet); 183 merger.merge(); 184 } 185 } 186 187 } catch(Exception e) { 188 if (canceled) return; 189 lastException = e; 190 } 191 } 192 193 /** 194 * replies the set of ids of all primitives for which a fetch request to the 195 * server was submitted but which are not available from the server (the server 196 * replied a return code of 404) 197 * 198 * @return the set of ids of missing primitives 199 */ 200 public Set<PrimitiveId> getMissingPrimitives() { 201 return missingPrimitives; 202 } 203 204}