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 * @param progressMonitor progress monitor 151 * @return the data set 152 * @throws OsmTransferException if any error occurs during dialog with OSM API 153 */ 154 protected DataSet getReferringWays(ProgressMonitor progressMonitor) throws OsmTransferException { 155 return getReferringPrimitives(progressMonitor, "/ways", tr("Downloading referring ways ...")); 156 } 157 158 /** 159 * Reads referring relations from the API server and replies them in a {@link DataSet} 160 * 161 * @param progressMonitor the progress monitor 162 * @return the data set 163 * @throws OsmTransferException if any error occurs during dialog with OSM API 164 */ 165 protected DataSet getReferringRelations(ProgressMonitor progressMonitor) throws OsmTransferException { 166 return getReferringPrimitives(progressMonitor, "/relations", tr("Downloading referring relations ...")); 167 } 168 169 /** 170 * Scans a dataset for incomplete primitives. Depending on the configuration of this reader 171 * incomplete primitives are read from the server with an individual <tt>/api/0.6/[way,relation]/#id/full</tt> 172 * request. 173 * 174 * <ul> 175 * <li>if this reader reads referers for a {@link org.openstreetmap.josm.data.osm.Node}, referring ways are always 176 * read individually from the server</li> 177 * <li>if this reader reads referers for an {@link Way} or a {@link Relation}, referring relations 178 * are only read fully if {@link #setReadFull(boolean)} is set to true.</li> 179 * </ul> 180 * 181 * The method replies the modified dataset. 182 * 183 * @param ds the original dataset 184 * @param progressMonitor the progress monitor 185 * @return the modified dataset 186 * @throws OsmTransferException if an exception occurs. 187 */ 188 protected DataSet readIncompletePrimitives(DataSet ds, ProgressMonitor progressMonitor) throws OsmTransferException { 189 progressMonitor.beginTask(null, 2); 190 try { 191 Collection<Way> waysToCheck = new ArrayList<>(ds.getWays()); 192 if (isReadFull() || primitiveType.equals(OsmPrimitiveType.NODE)) { 193 for (Way way: waysToCheck) { 194 if (!way.isNew() && way.hasIncompleteNodes()) { 195 OsmServerObjectReader reader = new OsmServerObjectReader(way.getId(), OsmPrimitiveType.from(way), true /* read full */); 196 DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 197 DataSetMerger visitor = new DataSetMerger(ds, wayDs); 198 visitor.merge(); 199 } 200 } 201 } 202 if (isReadFull()) { 203 Collection<Relation> relationsToCheck = new ArrayList<>(ds.getRelations()); 204 for (Relation relation: relationsToCheck) { 205 if (!relation.isNew() && relation.hasIncompleteMembers()) { 206 OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true); 207 DataSet wayDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 208 DataSetMerger visitor = new DataSetMerger(ds, wayDs); 209 visitor.merge(); 210 } 211 } 212 } 213 return ds; 214 } finally { 215 progressMonitor.finishTask(); 216 } 217 } 218 219 /** 220 * Reads the referring primitives from the OSM server, parses them and 221 * replies them as {@link DataSet} 222 * 223 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null. 224 * @return the dataset with the referring primitives 225 * @throws OsmTransferException if an error occurs while communicating with the server 226 */ 227 @Override 228 public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException { 229 if (progressMonitor == null) { 230 progressMonitor = NullProgressMonitor.INSTANCE; 231 } 232 try { 233 progressMonitor.beginTask(null, 3); 234 DataSet ret = new DataSet(); 235 if (primitiveType.equals(OsmPrimitiveType.NODE)) { 236 DataSet ds = getReferringWays(progressMonitor.createSubTaskMonitor(1, false)); 237 DataSetMerger visitor = new DataSetMerger(ret, ds); 238 visitor.merge(); 239 ret = visitor.getTargetDataSet(); 240 } 241 DataSet ds = getReferringRelations(progressMonitor.createSubTaskMonitor(1, false)); 242 DataSetMerger visitor = new DataSetMerger(ret, ds); 243 visitor.merge(); 244 ret = visitor.getTargetDataSet(); 245 if (ret != null) { 246 readIncompletePrimitives(ret, progressMonitor.createSubTaskMonitor(1, false)); 247 ret.deleteInvisible(); 248 } 249 return ret; 250 } finally { 251 progressMonitor.finishTask(); 252 } 253 } 254}