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