001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.io; 003 004 import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 007 import java.io.IOException; 008 import java.util.List; 009 import java.util.Set; 010 011 import org.openstreetmap.josm.actions.AutoScaleAction; 012 import org.openstreetmap.josm.data.osm.DataSet; 013 import org.openstreetmap.josm.data.osm.DataSetMerger; 014 import org.openstreetmap.josm.data.osm.Node; 015 import org.openstreetmap.josm.data.osm.OsmPrimitive; 016 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 017 import org.openstreetmap.josm.data.osm.PrimitiveId; 018 import org.openstreetmap.josm.data.osm.Relation; 019 import org.openstreetmap.josm.data.osm.Way; 020 import org.openstreetmap.josm.gui.ExceptionDialogUtil; 021 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 022 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 023 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024 import org.openstreetmap.josm.gui.util.GuiHelper; 025 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 026 import org.openstreetmap.josm.io.OsmServerObjectReader; 027 import org.openstreetmap.josm.io.OsmTransferException; 028 import org.xml.sax.SAXException; 029 030 public class DownloadPrimitivesTask extends PleaseWaitRunnable { 031 private DataSet ds; 032 private boolean canceled; 033 private Exception lastException; 034 private List<PrimitiveId> ids; 035 036 private Set<PrimitiveId> missingPrimitives; 037 038 private OsmDataLayer layer; 039 private 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 toUpdate 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 public void run() { 084 layer.mergeFrom(ds); 085 AutoScaleAction.zoomTo(ds.allPrimitives()); 086 layer.onPostDownloadFromServer(); 087 } 088 }); 089 } 090 091 protected void initMultiFetchReader(MultiFetchServerObjectReader reader) { 092 getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to download ...")); 093 for (PrimitiveId id : ids) { 094 OsmPrimitive osm = layer.data.getPrimitiveById(id); 095 if (osm == null) { 096 switch (id.getType()) { 097 case NODE: 098 osm = new Node(id.getUniqueId()); 099 break; 100 case WAY: 101 osm = new Way(id.getUniqueId()); 102 break; 103 case RELATION: 104 osm = new Relation(id.getUniqueId()); 105 break; 106 default: throw new AssertionError(); 107 } 108 } 109 reader.append(osm); 110 } 111 } 112 113 @Override 114 protected void realRun() throws SAXException, IOException, OsmTransferException { 115 this.ds = new DataSet(); 116 DataSet theirDataSet; 117 try { 118 synchronized(this) { 119 if (canceled) return; 120 multiObjectReader = new MultiFetchServerObjectReader(); 121 } 122 initMultiFetchReader(multiObjectReader); 123 theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 124 missingPrimitives = multiObjectReader.getMissingPrimitives(); 125 synchronized(this) { 126 multiObjectReader = null; 127 } 128 DataSetMerger merger = new DataSetMerger(ds, theirDataSet); 129 merger.merge(); 130 131 // if incomplete relation members exist, download them too 132 for (Relation r : ds.getRelations()) { 133 if (canceled) return; 134 if (r.hasIncompleteMembers()) { 135 synchronized(this) { 136 if (canceled) return; 137 objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation); 138 } 139 theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 140 synchronized (this) { 141 objectReader = null; 142 } 143 merger = new DataSetMerger(ds, theirDataSet); 144 merger.merge(); 145 } 146 } 147 148 // a way loaded with MultiFetch may have incomplete nodes because at least one of its 149 // nodes isn't present in the local data set. We therefore fully load all 150 // ways with incomplete nodes. 151 // 152 for (Way w : ds.getWays()) { 153 if (canceled) return; 154 if (w.hasIncompleteNodes()) { 155 synchronized(this) { 156 if (canceled) return; 157 objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */); 158 } 159 theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 160 synchronized (this) { 161 objectReader = null; 162 } 163 merger = new DataSetMerger(ds, theirDataSet); 164 merger.merge(); 165 } 166 } 167 168 } catch(Exception e) { 169 if (canceled) return; 170 lastException = e; 171 } 172 } 173 174 /** 175 * replies the set of ids of all primitives for which a fetch request to the 176 * server was submitted but which are not available from the server (the server 177 * replied a return code of 404) 178 * 179 * @return the set of ids of missing primitives 180 */ 181 public Set<PrimitiveId> getMissingPrimitives() { 182 return missingPrimitives; 183 } 184 185 }