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