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.Way; 015import org.openstreetmap.josm.gui.ExceptionDialogUtil; 016import org.openstreetmap.josm.gui.MainApplication; 017import org.openstreetmap.josm.gui.PleaseWaitRunnable; 018import org.openstreetmap.josm.gui.layer.OsmDataLayer; 019import org.openstreetmap.josm.gui.progress.ProgressMonitor; 020import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor; 021import org.openstreetmap.josm.gui.util.GuiHelper; 022import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 023import org.openstreetmap.josm.io.OsmServerObjectReader; 024import org.openstreetmap.josm.io.OsmTransferException; 025import org.xml.sax.SAXException; 026 027/** 028 * Abstract superclass of download/update primitives tasks. 029 * @since 10129 030 */ 031public abstract class AbstractPrimitiveTask extends PleaseWaitRunnable { 032 033 protected final DataSet ds = new DataSet(); 034 protected boolean canceled; 035 protected Exception lastException; 036 private Set<PrimitiveId> missingPrimitives; 037 038 protected final OsmDataLayer layer; 039 protected MultiFetchServerObjectReader multiObjectReader; 040 protected OsmServerObjectReader objectReader; 041 042 private boolean zoom; 043 protected boolean fullRelation; 044 045 protected AbstractPrimitiveTask(String title, OsmDataLayer layer) { 046 this(title, new PleaseWaitProgressMonitor(title), layer); 047 } 048 049 protected AbstractPrimitiveTask(String title, ProgressMonitor progressMonitor, OsmDataLayer layer) { 050 super(title, progressMonitor, false); 051 ensureParameterNotNull(layer, "layer"); 052 this.layer = layer; 053 if (!layer.isDownloadable()) { 054 throw new IllegalArgumentException("Non-downloadable layer: " + layer); 055 } 056 } 057 058 protected abstract void initMultiFetchReader(MultiFetchServerObjectReader reader); 059 060 /** 061 * Sets whether the map view should zoom to impacted primitives at the end. 062 * @param zoom {@code true} if the map view should zoom to impacted primitives at the end 063 * @return {@code this} 064 */ 065 public final AbstractPrimitiveTask setZoom(boolean zoom) { 066 this.zoom = zoom; 067 return this; 068 } 069 070 /** 071 * Sets whether all members of the relation should be downloaded completely. 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 * since 15811 (changed parameter list) 076 */ 077 public final AbstractPrimitiveTask setDownloadRelations(boolean fullRelation) { 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().setRecurseDownRelations(fullRelation); 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 loadIncompleteNodes(); 111 } catch (OsmTransferException e) { 112 if (canceled) 113 return; 114 lastException = e; 115 } 116 } 117 118 protected void loadIncompleteNodes() throws OsmTransferException { 119 // a way loaded with MultiFetch may have incomplete nodes because at least one of its 120 // nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes. 121 for (Way w : ds.getWays()) { 122 if (canceled) 123 return; 124 if (w.hasIncompleteNodes()) { 125 synchronized (this) { 126 if (canceled) 127 return; 128 objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */); 129 } 130 DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 131 synchronized (this) { 132 objectReader = null; 133 } 134 new DataSetMerger(ds, theirDataSet).merge(); 135 } 136 } 137 } 138 139 @Override 140 protected void cancel() { 141 canceled = true; 142 synchronized (this) { 143 if (multiObjectReader != null) { 144 multiObjectReader.cancel(); 145 } 146 if (objectReader != null) { 147 objectReader.cancel(); 148 } 149 } 150 } 151 152 @Override 153 protected void finish() { 154 if (canceled) 155 return; 156 if (lastException != null) { 157 ExceptionDialogUtil.explainException(lastException); 158 return; 159 } 160 GuiHelper.runInEDTAndWait(() -> { 161 layer.mergeFrom(ds); 162 if (zoom && MainApplication.getMap() != null) 163 AutoScaleAction.zoomTo(ds.allPrimitives()); 164 layer.onPostDownloadFromServer(); 165 }); 166 } 167}