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.Main; 010import org.openstreetmap.josm.actions.AutoScaleAction; 011import org.openstreetmap.josm.data.osm.DataSet; 012import org.openstreetmap.josm.data.osm.DataSetMerger; 013import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 014import org.openstreetmap.josm.data.osm.PrimitiveId; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.Way; 017import org.openstreetmap.josm.gui.ExceptionDialogUtil; 018import org.openstreetmap.josm.gui.PleaseWaitRunnable; 019import org.openstreetmap.josm.gui.layer.OsmDataLayer; 020import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor; 021import org.openstreetmap.josm.gui.progress.ProgressMonitor; 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 } 056 057 protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader); 058 059 /** 060 * Sets whether the map view should zoom to impacted primitives at the end. 061 * @param zoom {@code true} if the map view should zoom to impacted primitives at the end 062 * @return {@code this} 063 */ 064 public final AbstractPrimitiveTask setZoom(boolean zoom) { 065 this.zoom = zoom; 066 return this; 067 } 068 069 /** 070 * Sets whether . 071 * @param downloadRelations {@code true} if 072 * @param fullRelation {@code true} if a full download is required, 073 * i.e., a download including the immediate children of a relation. 074 * @return {@code this} 075 */ 076 public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) { 077 this.downloadRelations = downloadRelations; 078 this.fullRelation = fullRelation; 079 return this; 080 } 081 082 /** 083 * Replies the set of ids of all primitives for which a fetch request to the 084 * server was submitted but which are not available from the server (the server 085 * replied a return code of 404) 086 * 087 * @return the set of ids of missing primitives 088 */ 089 public Set<PrimitiveId> getMissingPrimitives() { 090 return missingPrimitives; 091 } 092 093 @Override 094 protected void realRun() throws SAXException, IOException, OsmTransferException { 095 DataSet theirDataSet; 096 try { 097 synchronized (this) { 098 if (canceled) 099 return; 100 multiObjectReader = MultiFetchServerObjectReader.create(); 101 } 102 initMultiFetchReader(multiObjectReader); 103 theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 104 missingPrimitives = multiObjectReader.getMissingPrimitives(); 105 synchronized (this) { 106 multiObjectReader = null; 107 } 108 new DataSetMerger(ds, theirDataSet).merge(); 109 110 if (downloadRelations) { 111 loadIncompleteRelationMembers(); 112 } 113 114 loadIncompleteNodes(); 115 } catch (OsmTransferException e) { 116 if (canceled) 117 return; 118 lastException = e; 119 } 120 } 121 122 protected void loadIncompleteRelationMembers() throws OsmTransferException { 123 // if incomplete relation members exist, download them too 124 for (Relation r : ds.getRelations()) { 125 if (canceled) 126 return; 127 // Relations may be incomplete in case of nested relations if child relations are accessed before their parent 128 // (it may happen because "relations" has no deterministic sort order, see #10388) 129 if (r.isIncomplete() || r.hasIncompleteMembers()) { 130 synchronized (this) { 131 if (canceled) 132 return; 133 objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation); 134 } 135 DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 136 synchronized (this) { 137 objectReader = null; 138 } 139 new DataSetMerger(ds, theirDataSet).merge(); 140 } 141 } 142 } 143 144 protected void loadIncompleteNodes() throws OsmTransferException { 145 // a way loaded with MultiFetch may have incomplete nodes because at least one of its 146 // nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes. 147 for (Way w : ds.getWays()) { 148 if (canceled) 149 return; 150 if (w.hasIncompleteNodes()) { 151 synchronized (this) { 152 if (canceled) 153 return; 154 objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */); 155 } 156 DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 157 synchronized (this) { 158 objectReader = null; 159 } 160 new DataSetMerger(ds, theirDataSet).merge(); 161 } 162 } 163 } 164 165 @Override 166 protected void cancel() { 167 canceled = true; 168 synchronized (this) { 169 if (multiObjectReader != null) { 170 multiObjectReader.cancel(); 171 } 172 if (objectReader != null) { 173 objectReader.cancel(); 174 } 175 } 176 } 177 178 @Override 179 protected void finish() { 180 if (canceled) 181 return; 182 if (lastException != null) { 183 ExceptionDialogUtil.explainException(lastException); 184 return; 185 } 186 GuiHelper.runInEDTAndWait(() -> { 187 layer.mergeFrom(ds); 188 if (zoom && Main.map != null) 189 AutoScaleAction.zoomTo(ds.allPrimitives()); 190 layer.onPostDownloadFromServer(); 191 }); 192 } 193}