001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.io.InputStream; 008import java.text.MessageFormat; 009import java.util.ArrayList; 010import java.util.Collection; 011 012import org.openstreetmap.josm.data.osm.DataSet; 013import org.openstreetmap.josm.data.osm.DataSetMerger; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 016import org.openstreetmap.josm.data.osm.Relation; 017import org.openstreetmap.josm.data.osm.Way; 018import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 019import org.openstreetmap.josm.gui.progress.ProgressMonitor; 020import org.openstreetmap.josm.tools.CheckParameterUtil; 021 022/** 023 * OsmServerBackreferenceReader fetches the primitives from the OSM server which 024 * refer to a specific primitive. For a {@link org.openstreetmap.josm.data.osm.Node Node}, ways and relations are retrieved 025 * which refer to the node. For a {@link Way} or a {@link Relation}, only relations are read. 026 * 027 * OsmServerBackreferenceReader uses the API calls <code>[node|way|relation]/#id/relations</code> 028 * and <code>node/#id/ways</code> to retrieve the referring primitives. The default behaviour 029 * of these calls is to reply incomplete primitives only. 030 * 031 * If you set {@link #setReadFull(boolean)} to true this reader uses a {@link MultiFetchServerObjectReader} 032 * to complete incomplete primitives. 033 * 034 * @since 1806 035 */ 036public class OsmServerBackreferenceReader extends OsmServerReader { 037 038 /** the id of the primitive whose referrers are to be read */ 039 private final long id; 040 /** the type of the primitive */ 041 private final OsmPrimitiveType primitiveType; 042 /** true if this reader should complete incomplete primitives */ 043 private boolean readFull; 044 /** true if this reader should allow incomplete parent ways */ 045 private boolean allowIncompleteParentWays; 046 047 /** 048 * constructor 049 * 050 * @param primitive the primitive to be read. Must not be null. primitive.id > 0 expected 051 * 052 * @throws IllegalArgumentException if primitive is null 053 * @throws IllegalArgumentException if primitive.id <= 0 054 */ 055 public OsmServerBackreferenceReader(OsmPrimitive primitive) { 056 CheckParameterUtil.ensureThat(primitive.getUniqueId() > 0, "id > 0"); 057 this.id = primitive.getId(); 058 this.primitiveType = OsmPrimitiveType.from(primitive); 059 this.readFull = false; 060 } 061 062 /** 063 * constructor 064 * 065 * @param id the id of the primitive. > 0 expected 066 * @param type the type of the primitive. Must not be null. 067 * 068 * @throws IllegalArgumentException if id <= 0 069 * @throws IllegalArgumentException if type is null 070 */ 071 public OsmServerBackreferenceReader(long id, OsmPrimitiveType type) { 072 if (id <= 0) 073 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "id", id)); 074 CheckParameterUtil.ensureParameterNotNull(type, "type"); 075 this.id = id; 076 this.primitiveType = type; 077 this.readFull = false; 078 } 079 080 /** 081 * Creates a back reference reader for given primitive 082 * 083 * @param primitive the primitive 084 * @param readFull <code>true</code>, if referrers should be read fully (i.e. including their immediate children) 085 * 086 */ 087 public OsmServerBackreferenceReader(OsmPrimitive primitive, boolean readFull) { 088 this(primitive); 089 this.readFull = readFull; 090 } 091 092 /** 093 * Creates a back reference reader for given primitive id 094 * 095 * @param id the id of the primitive whose referrers are to be read 096 * @param type the type of the primitive 097 * @param readFull true, if referrers should be read fully (i.e. including their immediate children) 098 * 099 * @throws IllegalArgumentException if id <= 0 100 * @throws IllegalArgumentException if type is null 101 */ 102 public OsmServerBackreferenceReader(long id, OsmPrimitiveType type, boolean readFull) { 103 this(id, type); 104 this.readFull = readFull; 105 } 106 107 /** 108 * Replies true if this reader also reads immediate children of referring primitives 109 * 110 * @return true if this reader also reads immediate children of referring primitives 111 */ 112 public boolean isReadFull() { 113 return readFull; 114 } 115 116 /** 117 * Set true if this reader should reads immediate children of referring primitives too. False, otherwise. 118 * 119 * @param readFull true if this reader should reads immediate children of referring primitives too. False, otherwise. 120 * @return {@code this}, for easy chaining 121 * @since 15426 122 */ 123 public OsmServerBackreferenceReader setReadFull(boolean readFull) { 124 this.readFull = readFull; 125 return this; 126 } 127 128 /** 129 * Determines if this reader allows to return incomplete parent ways of a node. 130 * @return {@code true} if this reader allows to return incomplete parent ways of a node 131 * @since 15426 132 */ 133 public boolean isAllowIncompleteParentWays() { 134 return allowIncompleteParentWays; 135 } 136 137 /** 138 * Sets whether this reader allows to return incomplete parent ways of a node. 139 * @param allowIncompleteWays {@code true} if this reader allows to return incomplete parent ways of a node 140 * @return {@code this}, for easy chaining 141 * @since 15426 142 */ 143 public OsmServerBackreferenceReader setAllowIncompleteParentWays(boolean allowIncompleteWays) { 144 this.allowIncompleteParentWays = allowIncompleteWays; 145 return this; 146 } 147 148 private DataSet getReferringPrimitives(ProgressMonitor progressMonitor, String type, String message) throws OsmTransferException { 149 progressMonitor.beginTask(null, 2); 150 try { 151 progressMonitor.subTask(tr("Contacting OSM Server...")); 152 StringBuilder sb = new StringBuilder(); 153 sb.append(primitiveType.getAPIName()).append('/').append(id).append(type); 154 155 try (InputStream in = getInputStream(sb.toString(), progressMonitor.createSubTaskMonitor(1, true))) { 156 if (in == null) 157 return null; 158 progressMonitor.subTask(message); 159 return OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(1, true)); 160 } 161 } catch (OsmTransferException e) { 162 throw e; 163 } catch (IOException | IllegalDataException e) { 164 if (cancel) 165 return null; 166 throw new OsmTransferException(e); 167 } finally { 168 progressMonitor.finishTask(); 169 activeConnection = null; 170 } 171 } 172 173 /** 174 * Reads referring ways from the API server and replies them in a {@link DataSet} 175 * 176 * @param progressMonitor progress monitor 177 * @return the data set 178 * @throws OsmTransferException if any error occurs during dialog with OSM API 179 */ 180 protected DataSet getReferringWays(ProgressMonitor progressMonitor) throws OsmTransferException { 181 return getReferringPrimitives(progressMonitor, "/ways", tr("Downloading referring ways ...")); 182 } 183 184 /** 185 * Reads referring relations from the API server and replies them in a {@link DataSet} 186 * 187 * @param progressMonitor the progress monitor 188 * @return the data set 189 * @throws OsmTransferException if any error occurs during dialog with OSM API 190 */ 191 protected DataSet getReferringRelations(ProgressMonitor progressMonitor) throws OsmTransferException { 192 return getReferringPrimitives(progressMonitor, "/relations", tr("Downloading referring relations ...")); 193 } 194 195 /** 196 * Scans a dataset for incomplete primitives. Depending on the configuration of this reader 197 * incomplete primitives are read from the server with an individual <code>/api/0.6/[way,relation]/#id/full</code> 198 * request. 199 * 200 * <ul> 201 * <li>if this reader reads referrers for a {@link org.openstreetmap.josm.data.osm.Node}, referring ways are always 202 * read fully, unless {@link #setAllowIncompleteParentWays(boolean)} is set to true.</li> 203 * <li>if this reader reads referrers for an {@link Way} or a {@link Relation}, referring relations 204 * are only read fully if {@link #setReadFull(boolean)} is set to true.</li> 205 * </ul> 206 * 207 * The method replies the modified dataset. 208 * 209 * @param ds the original dataset 210 * @param progressMonitor the progress monitor 211 * @return the modified dataset 212 * @throws OsmTransferException if an exception occurs. 213 */ 214 protected DataSet readIncompletePrimitives(DataSet ds, ProgressMonitor progressMonitor) throws OsmTransferException { 215 progressMonitor.beginTask(null, 2); 216 try { 217 if (isReadFull() || (primitiveType == OsmPrimitiveType.NODE && !isAllowIncompleteParentWays())) { 218 Collection<Way> waysToCheck = new ArrayList<>(ds.getWays()); 219 for (Way way: waysToCheck) { 220 if (!way.isNew() && way.hasIncompleteNodes()) { 221 OsmServerObjectReader reader = new OsmServerObjectReader(way.getId(), OsmPrimitiveType.from(way), true /* read full */); 222 DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 223 DataSetMerger visitor = new DataSetMerger(ds, wayDs); 224 visitor.merge(); 225 } 226 } 227 } 228 if (isReadFull()) { 229 Collection<Relation> relationsToCheck = new ArrayList<>(ds.getRelations()); 230 for (Relation relation: relationsToCheck) { 231 if (!relation.isNew() && relation.hasIncompleteMembers()) { 232 OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true); 233 DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 234 DataSetMerger visitor = new DataSetMerger(ds, wayDs); 235 visitor.merge(); 236 } 237 } 238 } 239 return ds; 240 } finally { 241 progressMonitor.finishTask(); 242 } 243 } 244 245 /** 246 * Reads the referring primitives from the OSM server, parses them and 247 * replies them as {@link DataSet} 248 * 249 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null. 250 * @return the dataset with the referring primitives 251 * @throws OsmTransferException if an error occurs while communicating with the server 252 */ 253 @Override 254 public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException { 255 if (progressMonitor == null) { 256 progressMonitor = NullProgressMonitor.INSTANCE; 257 } 258 try { 259 progressMonitor.beginTask(null, 3); 260 DataSet ret = new DataSet(); 261 if (primitiveType == OsmPrimitiveType.NODE) { 262 DataSet ds = getReferringWays(progressMonitor.createSubTaskMonitor(1, false)); 263 DataSetMerger visitor = new DataSetMerger(ret, ds); 264 visitor.merge(); 265 ret = visitor.getTargetDataSet(); 266 } 267 DataSet ds = getReferringRelations(progressMonitor.createSubTaskMonitor(1, false)); 268 DataSetMerger visitor = new DataSetMerger(ret, ds); 269 visitor.merge(); 270 ret = visitor.getTargetDataSet(); 271 if (ret != null) { 272 readIncompletePrimitives(ret, progressMonitor.createSubTaskMonitor(1, false)); 273 ret.deleteInvisible(); 274 } 275 return ret; 276 } finally { 277 progressMonitor.finishTask(); 278 } 279 } 280}