001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull; 005 006import java.io.IOException; 007import java.util.Set; 008 009import org.openstreetmap.josm.actions.AutoScaleAction; 010import org.openstreetmap.josm.data.osm.DataSet; 011import org.openstreetmap.josm.data.osm.DataSetMerger; 012import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 013import org.openstreetmap.josm.data.osm.PrimitiveId; 014import org.openstreetmap.josm.data.osm.Relation; 015import org.openstreetmap.josm.data.osm.Way; 016import org.openstreetmap.josm.gui.ExceptionDialogUtil; 017import org.openstreetmap.josm.gui.MainApplication; 018import org.openstreetmap.josm.gui.PleaseWaitRunnable; 019import org.openstreetmap.josm.gui.layer.OsmDataLayer; 020import org.openstreetmap.josm.gui.progress.ProgressMonitor; 021import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor; 022import org.openstreetmap.josm.gui.util.GuiHelper; 023import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 024import org.openstreetmap.josm.io.OsmServerObjectReader; 025import org.openstreetmap.josm.io.OsmTransferException; 026import org.xml.sax.SAXException; 027 028/** 029 * Abstract superclass of download/update primitives tasks. 030 * @since 10129 031 */ 032public abstract class AbstractPrimitiveTask extends PleaseWaitRunnable { 033 034 protected final DataSet ds = new DataSet(); 035 protected boolean canceled; 036 protected Exception lastException; 037 private Set<PrimitiveId> missingPrimitives; 038 039 protected final OsmDataLayer layer; 040 protected MultiFetchServerObjectReader multiObjectReader; 041 protected OsmServerObjectReader objectReader; 042 043 private boolean zoom; 044 private boolean downloadRelations; 045 private boolean fullRelation; 046 047 protected AbstractPrimitiveTask(String title, OsmDataLayer layer) { 048 this(title, new PleaseWaitProgressMonitor(title), layer); 049 } 050 051 protected AbstractPrimitiveTask(String title, ProgressMonitor progressMonitor, OsmDataLayer layer) { 052 super(title, progressMonitor, false); 053 ensureParameterNotNull(layer, "layer"); 054 this.layer = layer; 055 if (!layer.isDownloadable()) { 056 throw new IllegalArgumentException("Non-downloadable layer: " + layer); 057 } 058 } 059 060 protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader); 061 062 /** 063 * Sets whether the map view should zoom to impacted primitives at the end. 064 * @param zoom {@code true} if the map view should zoom to impacted primitives at the end 065 * @return {@code this} 066 */ 067 public final AbstractPrimitiveTask setZoom(boolean zoom) { 068 this.zoom = zoom; 069 return this; 070 } 071 072 /** 073 * Sets whether . 074 * @param downloadRelations {@code true} if 075 * @param fullRelation {@code true} if a full download is required, 076 * i.e., a download including the immediate children of a relation. 077 * @return {@code this} 078 */ 079 public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) { 080 this.downloadRelations = downloadRelations; 081 this.fullRelation = fullRelation; 082 return this; 083 } 084 085 /** 086 * Replies the set of ids of all primitives for which a fetch request to the 087 * server was submitted but which are not available from the server (the server 088 * replied a return code of 404) 089 * 090 * @return the set of ids of missing primitives 091 */ 092 public Set<PrimitiveId> getMissingPrimitives() { 093 return missingPrimitives; 094 } 095 096 @Override 097 protected void realRun() throws SAXException, IOException, OsmTransferException { 098 DataSet theirDataSet; 099 try { 100 synchronized (this) { 101 if (canceled) 102 return; 103 multiObjectReader = MultiFetchServerObjectReader.create(); 104 } 105 initMultiFetchReader(multiObjectReader); 106 theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 107 missingPrimitives = multiObjectReader.getMissingPrimitives(); 108 synchronized (this) { 109 multiObjectReader = null; 110 } 111 new DataSetMerger(ds, theirDataSet).merge(); 112 113 if (downloadRelations) { 114 loadIncompleteRelationMembers(); 115 } 116 117 loadIncompleteNodes(); 118 } catch (OsmTransferException e) { 119 if (canceled) 120 return; 121 lastException = e; 122 } 123 } 124 125 protected void loadIncompleteRelationMembers() throws OsmTransferException { 126 // if incomplete relation members exist, download them too 127 for (Relation r : ds.getRelations()) { 128 if (canceled) 129 return; 130 // Relations may be incomplete in case of nested relations if child relations are accessed before their parent 131 // (it may happen because "relations" has no deterministic sort order, see #10388) 132 if (r.isIncomplete() || r.hasIncompleteMembers()) { 133 synchronized (this) { 134 if (canceled) 135 return; 136 objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation); 137 } 138 DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 139 synchronized (this) { 140 objectReader = null; 141 } 142 new DataSetMerger(ds, theirDataSet).merge(); 143 } 144 } 145 } 146 147 protected void loadIncompleteNodes() throws OsmTransferException { 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 ways with incomplete nodes. 150 for (Way w : ds.getWays()) { 151 if (canceled) 152 return; 153 if (w.hasIncompleteNodes()) { 154 synchronized (this) { 155 if (canceled) 156 return; 157 objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */); 158 } 159 DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 160 synchronized (this) { 161 objectReader = null; 162 } 163 new DataSetMerger(ds, theirDataSet).merge(); 164 } 165 } 166 } 167 168 @Override 169 protected void cancel() { 170 canceled = true; 171 synchronized (this) { 172 if (multiObjectReader != null) { 173 multiObjectReader.cancel(); 174 } 175 if (objectReader != null) { 176 objectReader.cancel(); 177 } 178 } 179 } 180 181 @Override 182 protected void finish() { 183 if (canceled) 184 return; 185 if (lastException != null) { 186 ExceptionDialogUtil.explainException(lastException); 187 return; 188 } 189 GuiHelper.runInEDTAndWait(() -> { 190 layer.mergeFrom(ds); 191 if (zoom && MainApplication.getMap() != null) 192 AutoScaleAction.zoomTo(ds.allPrimitives()); 193 layer.onPostDownloadFromServer(); 194 }); 195 } 196}