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 * @exception IllegalArgumentException thrown if primitive is null 050 * @exception IllegalArgumentException thrown if primitive.id <= 0 051 */ 052 public OsmServerBackreferenceReader(OsmPrimitive primitive) throws IllegalArgumentException { 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 * @exception IllegalArgumentException thrown if id <= 0 066 * @exception IllegalArgumentException thrown if type is null 067 * 068 */ 069 public OsmServerBackreferenceReader(long id, OsmPrimitiveType type) throws IllegalArgumentException { 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 * @exception IllegalArgumentException thrown if id <= 0 098 * @exception IllegalArgumentException thrown if type is null 099 * 100 */ 101 public OsmServerBackreferenceReader(long id, OsmPrimitiveType type, boolean readFull) throws IllegalArgumentException { 102 this(id, type); 103 this.readFull = readFull; 104 } 105 106 /** 107 * Replies true if this reader also reads immediate children of referring primitives 108 * 109 * @return true if this reader also reads immediate children of referring primitives 110 */ 111 public boolean isReadFull() { 112 return readFull; 113 } 114 115 /** 116 * Set true if this reader should reads immediate children of referring primitives too. False, otherweise. 117 * 118 * @param readFull true if this reader should reads immediate children of referring primitives too. False, otherweise. 119 */ 120 public void setReadFull(boolean readFull) { 121 this.readFull = readFull; 122 } 123 124 private DataSet getReferringPrimitives(ProgressMonitor progressMonitor, String type, String message) throws OsmTransferException { 125 progressMonitor.beginTask(null, 2); 126 try { 127 progressMonitor.subTask(tr("Contacting OSM Server...")); 128 StringBuilder sb = new StringBuilder(); 129 sb.append(primitiveType.getAPIName()).append("/").append(id).append(type); 130 131 try (InputStream in = getInputStream(sb.toString(), progressMonitor.createSubTaskMonitor(1, true))) { 132 if (in == null) 133 return null; 134 progressMonitor.subTask(message); 135 return OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(1, true)); 136 } 137 } catch(OsmTransferException e) { 138 throw e; 139 } catch (Exception e) { 140 if (cancel) 141 return null; 142 throw new OsmTransferException(e); 143 } finally { 144 progressMonitor.finishTask(); 145 activeConnection = null; 146 } 147 } 148 149 /** 150 * Reads referring ways from the API server and replies them in a {@link DataSet} 151 * 152 * @return the data set 153 * @throws OsmTransferException 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 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 thrown 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 /* read full */); 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 * @exception OsmTransferException thrown 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}