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 long id; 040 /** the type of the primitive */ 041 private OsmPrimitiveType primitiveType; 042 /** true if this reader should complete incomplete primitives */ 043 private boolean readFull; 044 045 /** 046 * constructor 047 * 048 * @param primitive the primitive to be read. Must not be null. primitive.id > 0 expected 049 * 050 * @throws IllegalArgumentException if primitive is null 051 * @throws IllegalArgumentException if primitive.id <= 0 052 */ 053 public OsmServerBackreferenceReader(OsmPrimitive primitive) { 054 CheckParameterUtil.ensureValidPrimitiveId(primitive, "primitive"); 055 this.id = primitive.getId(); 056 this.primitiveType = OsmPrimitiveType.from(primitive); 057 this.readFull = false; 058 } 059 060 /** 061 * constructor 062 * 063 * @param id the id of the primitive. > 0 expected 064 * @param type the type of the primitive. Must not be null. 065 * 066 * @throws IllegalArgumentException if id <= 0 067 * @throws IllegalArgumentException if type is null 068 */ 069 public OsmServerBackreferenceReader(long id, OsmPrimitiveType type) { 070 if (id <= 0) 071 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "id", id)); 072 CheckParameterUtil.ensureParameterNotNull(type, "type"); 073 this.id = id; 074 this.primitiveType = type; 075 this.readFull = false; 076 } 077 078 /** 079 * Creates a back reference reader for given primitive 080 * 081 * @param primitive the primitive 082 * @param readFull <code>true</code>, if referers should be read fully (i.e. including their immediate children) 083 * 084 */ 085 public OsmServerBackreferenceReader(OsmPrimitive primitive, boolean readFull) { 086 this(primitive); 087 this.readFull = readFull; 088 } 089 090 /** 091 * Creates a back reference reader for given primitive id 092 * 093 * @param id the id of the primitive whose referers are to be read 094 * @param type the type of the primitive 095 * @param readFull true, if referers should be read fully (i.e. including their immediate children) 096 * 097 * @throws IllegalArgumentException if id <= 0 098 * @throws IllegalArgumentException if type is null 099 */ 100 public OsmServerBackreferenceReader(long id, OsmPrimitiveType type, boolean readFull) { 101 this(id, type); 102 this.readFull = readFull; 103 } 104 105 /** 106 * Replies true if this reader also reads immediate children of referring primitives 107 * 108 * @return true if this reader also reads immediate children of referring primitives 109 */ 110 public boolean isReadFull() { 111 return readFull; 112 } 113 114 /** 115 * Set true if this reader should reads immediate children of referring primitives too. False, otherweise. 116 * 117 * @param readFull true if this reader should reads immediate children of referring primitives too. False, otherweise. 118 */ 119 public void setReadFull(boolean readFull) { 120 this.readFull = readFull; 121 } 122 123 private DataSet getReferringPrimitives(ProgressMonitor progressMonitor, String type, String message) throws OsmTransferException { 124 progressMonitor.beginTask(null, 2); 125 try { 126 progressMonitor.subTask(tr("Contacting OSM Server...")); 127 StringBuilder sb = new StringBuilder(); 128 sb.append(primitiveType.getAPIName()).append('/').append(id).append(type); 129 130 try (InputStream in = getInputStream(sb.toString(), progressMonitor.createSubTaskMonitor(1, true))) { 131 if (in == null) 132 return null; 133 progressMonitor.subTask(message); 134 return OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(1, true)); 135 } 136 } catch (OsmTransferException e) { 137 throw e; 138 } catch (IOException | IllegalDataException e) { 139 if (cancel) 140 return null; 141 throw new OsmTransferException(e); 142 } finally { 143 progressMonitor.finishTask(); 144 activeConnection = null; 145 } 146 } 147 148 /** 149 * Reads referring ways from the API server and replies them in a {@link DataSet} 150 * 151 * @param progressMonitor progress monitor 152 * @return the data set 153 * @throws OsmTransferException if any error occurs during dialog with OSM API 154 */ 155 protected DataSet getReferringWays(ProgressMonitor progressMonitor) throws OsmTransferException { 156 return getReferringPrimitives(progressMonitor, "/ways", tr("Downloading referring ways ...")); 157 } 158 159 /** 160 * Reads referring relations from the API server and replies them in a {@link DataSet} 161 * 162 * @param progressMonitor the progress monitor 163 * @return the data set 164 * @throws OsmTransferException if any error occurs during dialog with OSM API 165 */ 166 protected DataSet getReferringRelations(ProgressMonitor progressMonitor) throws OsmTransferException { 167 return getReferringPrimitives(progressMonitor, "/relations", tr("Downloading referring relations ...")); 168 } 169 170 /** 171 * Scans a dataset for incomplete primitives. Depending on the configuration of this reader 172 * incomplete primitives are read from the server with an individual <tt>/api/0.6/[way,relation]/#id/full</tt> 173 * request. 174 * 175 * <ul> 176 * <li>if this reader reads referers for a {@link org.openstreetmap.josm.data.osm.Node}, referring ways are always 177 * read individually from the server</li> 178 * <li>if this reader reads referers for an {@link Way} or a {@link Relation}, referring relations 179 * are only read fully if {@link #setReadFull(boolean)} is set to true.</li> 180 * </ul> 181 * 182 * The method replies the modified dataset. 183 * 184 * @param ds the original dataset 185 * @param progressMonitor the progress monitor 186 * @return the modified dataset 187 * @throws OsmTransferException if an exception occurs. 188 */ 189 protected DataSet readIncompletePrimitives(DataSet ds, ProgressMonitor progressMonitor) throws OsmTransferException { 190 progressMonitor.beginTask(null, 2); 191 try { 192 Collection<Way> waysToCheck = new ArrayList<>(ds.getWays()); 193 if (isReadFull() || primitiveType.equals(OsmPrimitiveType.NODE)) { 194 for (Way way: waysToCheck) { 195 if (!way.isNew() && way.hasIncompleteNodes()) { 196 OsmServerObjectReader reader = new OsmServerObjectReader(way.getId(), OsmPrimitiveType.from(way), true /* read full */); 197 DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 198 DataSetMerger visitor = new DataSetMerger(ds, wayDs); 199 visitor.merge(); 200 } 201 } 202 } 203 if (isReadFull()) { 204 Collection<Relation> relationsToCheck = new ArrayList<>(ds.getRelations()); 205 for (Relation relation: relationsToCheck) { 206 if (!relation.isNew() && relation.hasIncompleteMembers()) { 207 OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true); 208 DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 209 DataSetMerger visitor = new DataSetMerger(ds, wayDs); 210 visitor.merge(); 211 } 212 } 213 } 214 return ds; 215 } finally { 216 progressMonitor.finishTask(); 217 } 218 } 219 220 /** 221 * Reads the referring primitives from the OSM server, parses them and 222 * replies them as {@link DataSet} 223 * 224 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null. 225 * @return the dataset with the referring primitives 226 * @throws OsmTransferException if an error occurs while communicating with the server 227 */ 228 @Override 229 public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException { 230 if (progressMonitor == null) { 231 progressMonitor = NullProgressMonitor.INSTANCE; 232 } 233 try { 234 progressMonitor.beginTask(null, 3); 235 DataSet ret = new DataSet(); 236 if (primitiveType.equals(OsmPrimitiveType.NODE)) { 237 DataSet ds = getReferringWays(progressMonitor.createSubTaskMonitor(1, false)); 238 DataSetMerger visitor = new DataSetMerger(ret, ds); 239 visitor.merge(); 240 ret = visitor.getTargetDataSet(); 241 } 242 DataSet ds = getReferringRelations(progressMonitor.createSubTaskMonitor(1, false)); 243 DataSetMerger visitor = new DataSetMerger(ret, ds); 244 visitor.merge(); 245 ret = visitor.getTargetDataSet(); 246 if (ret != null) { 247 readIncompletePrimitives(ret, progressMonitor.createSubTaskMonitor(1, false)); 248 ret.deleteInvisible(); 249 } 250 return ret; 251 } finally { 252 progressMonitor.finishTask(); 253 } 254 } 255}