source: josm/trunk/src/org/openstreetmap/josm/io/OsmReader.java@ 14216

Last change on this file since 14216 was 14094, checked in by Don-vip, 6 years ago

fix spotbugs warning

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.InputStream;
7import java.util.Collection;
8import java.util.Objects;
9import java.util.regex.Matcher;
10import java.util.regex.Pattern;
11
12import javax.xml.stream.Location;
13import javax.xml.stream.XMLStreamConstants;
14import javax.xml.stream.XMLStreamException;
15import javax.xml.stream.XMLStreamReader;
16
17import org.openstreetmap.josm.data.osm.Changeset;
18import org.openstreetmap.josm.data.osm.DataSet;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.PrimitiveData;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.RelationMemberData;
23import org.openstreetmap.josm.data.osm.Tagged;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
26import org.openstreetmap.josm.gui.progress.ProgressMonitor;
27import org.openstreetmap.josm.tools.Logging;
28import org.openstreetmap.josm.tools.UncheckedParseException;
29import org.openstreetmap.josm.tools.XmlUtils;
30
31/**
32 * Parser for the Osm API (XML output). Read from an input stream and construct a dataset out of it.
33 *
34 * For each xml element, there is a dedicated method.
35 * The XMLStreamReader cursor points to the start of the element, when the method is
36 * entered, and it must point to the end of the same element, when it is exited.
37 */
38public class OsmReader extends AbstractReader {
39
40 protected XMLStreamReader parser;
41
42 /**
43 * constructor (for private and subclasses use only)
44 *
45 * @see #parseDataSet(InputStream, ProgressMonitor)
46 */
47 protected OsmReader() {
48 // Restricts visibility
49 }
50
51 protected void setParser(XMLStreamReader parser) {
52 this.parser = parser;
53 }
54
55 protected void throwException(Throwable th) throws XMLStreamException {
56 throw new XmlStreamParsingException(th.getMessage(), parser.getLocation(), th);
57 }
58
59 protected void throwException(String msg, Throwable th) throws XMLStreamException {
60 throw new XmlStreamParsingException(msg, parser.getLocation(), th);
61 }
62
63 protected void throwException(String msg) throws XMLStreamException {
64 throw new XmlStreamParsingException(msg, parser.getLocation());
65 }
66
67 protected void parse() throws XMLStreamException {
68 int event = parser.getEventType();
69 while (true) {
70 if (event == XMLStreamConstants.START_ELEMENT) {
71 parseRoot();
72 } else if (event == XMLStreamConstants.END_ELEMENT)
73 return;
74 if (parser.hasNext()) {
75 event = parser.next();
76 } else {
77 break;
78 }
79 }
80 parser.close();
81 }
82
83 protected void parseRoot() throws XMLStreamException {
84 if ("osm".equals(parser.getLocalName())) {
85 parseOsm();
86 } else {
87 parseUnknown();
88 }
89 }
90
91 private void parseOsm() throws XMLStreamException {
92 try {
93 parseVersion(parser.getAttributeValue(null, "version"));
94 parseDownloadPolicy("download", parser.getAttributeValue(null, "download"));
95 parseUploadPolicy("upload", parser.getAttributeValue(null, "upload"));
96 parseLocked(parser.getAttributeValue(null, "locked"));
97 } catch (IllegalDataException e) {
98 throwException(e);
99 }
100 String generator = parser.getAttributeValue(null, "generator");
101 Long uploadChangesetId = null;
102 if (parser.getAttributeValue(null, "upload-changeset") != null) {
103 uploadChangesetId = getLong("upload-changeset");
104 }
105 while (true) {
106 int event = parser.next();
107
108 if (cancel) {
109 cancel = false;
110 throw new OsmParsingCanceledException(tr("Reading was canceled"), parser.getLocation());
111 }
112
113 if (event == XMLStreamConstants.START_ELEMENT) {
114 switch (parser.getLocalName()) {
115 case "bounds":
116 parseBounds(generator);
117 break;
118 case "node":
119 parseNode();
120 break;
121 case "way":
122 parseWay();
123 break;
124 case "relation":
125 parseRelation();
126 break;
127 case "changeset":
128 parseChangeset(uploadChangesetId);
129 break;
130 default:
131 parseUnknown();
132 }
133 } else if (event == XMLStreamConstants.END_ELEMENT) {
134 return;
135 }
136 }
137 }
138
139 private void handleIllegalDataException(IllegalDataException e) throws XMLStreamException {
140 Throwable cause = e.getCause();
141 if (cause instanceof XMLStreamException) {
142 throw (XMLStreamException) cause;
143 } else {
144 throwException(e);
145 }
146 }
147
148 private void parseBounds(String generator) throws XMLStreamException {
149 String minlon = parser.getAttributeValue(null, "minlon");
150 String minlat = parser.getAttributeValue(null, "minlat");
151 String maxlon = parser.getAttributeValue(null, "maxlon");
152 String maxlat = parser.getAttributeValue(null, "maxlat");
153 String origin = parser.getAttributeValue(null, "origin");
154 try {
155 parseBounds(generator, minlon, minlat, maxlon, maxlat, origin);
156 } catch (IllegalDataException e) {
157 handleIllegalDataException(e);
158 }
159 jumpToEnd();
160 }
161
162 protected Node parseNode() throws XMLStreamException {
163 String lat = parser.getAttributeValue(null, "lat");
164 String lon = parser.getAttributeValue(null, "lon");
165 try {
166 return parseNode(lat, lon, this::readCommon, this::parseNodeTags);
167 } catch (IllegalDataException e) {
168 handleIllegalDataException(e);
169 }
170 return null;
171 }
172
173 private void parseNodeTags(Node n) throws IllegalDataException {
174 try {
175 while (parser.hasNext()) {
176 int event = parser.next();
177 if (event == XMLStreamConstants.START_ELEMENT) {
178 if ("tag".equals(parser.getLocalName())) {
179 parseTag(n);
180 } else {
181 parseUnknown();
182 }
183 } else if (event == XMLStreamConstants.END_ELEMENT) {
184 return;
185 }
186 }
187 } catch (XMLStreamException e) {
188 throw new IllegalDataException(e);
189 }
190 }
191
192 protected Way parseWay() throws XMLStreamException {
193 try {
194 return parseWay(this::readCommon, this::parseWayNodesAndTags);
195 } catch (IllegalDataException e) {
196 handleIllegalDataException(e);
197 }
198 return null;
199 }
200
201 private void parseWayNodesAndTags(Way w, Collection<Long> nodeIds) throws IllegalDataException {
202 try {
203 while (parser.hasNext()) {
204 int event = parser.next();
205 if (event == XMLStreamConstants.START_ELEMENT) {
206 switch (parser.getLocalName()) {
207 case "nd":
208 nodeIds.add(parseWayNode(w));
209 break;
210 case "tag":
211 parseTag(w);
212 break;
213 default:
214 parseUnknown();
215 }
216 } else if (event == XMLStreamConstants.END_ELEMENT) {
217 break;
218 }
219 }
220 } catch (XMLStreamException e) {
221 throw new IllegalDataException(e);
222 }
223 }
224
225 private long parseWayNode(Way w) throws XMLStreamException {
226 if (parser.getAttributeValue(null, "ref") == null) {
227 throwException(
228 tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", Long.toString(w.getUniqueId()))
229 );
230 }
231 long id = getLong("ref");
232 if (id == 0) {
233 throwException(
234 tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", Long.toString(id))
235 );
236 }
237 jumpToEnd();
238 return id;
239 }
240
241 protected Relation parseRelation() throws XMLStreamException {
242 try {
243 return parseRelation(this::readCommon, this::parseRelationMembersAndTags);
244 } catch (IllegalDataException e) {
245 handleIllegalDataException(e);
246 }
247 return null;
248 }
249
250 private void parseRelationMembersAndTags(Relation r, Collection<RelationMemberData> members) throws IllegalDataException {
251 try {
252 while (parser.hasNext()) {
253 int event = parser.next();
254 if (event == XMLStreamConstants.START_ELEMENT) {
255 switch (parser.getLocalName()) {
256 case "member":
257 members.add(parseRelationMember(r));
258 break;
259 case "tag":
260 parseTag(r);
261 break;
262 default:
263 parseUnknown();
264 }
265 } else if (event == XMLStreamConstants.END_ELEMENT) {
266 break;
267 }
268 }
269 } catch (XMLStreamException e) {
270 throw new IllegalDataException(e);
271 }
272 }
273
274 private RelationMemberData parseRelationMember(Relation r) throws XMLStreamException {
275 RelationMemberData result = null;
276 try {
277 String ref = parser.getAttributeValue(null, "ref");
278 String type = parser.getAttributeValue(null, "type");
279 String role = parser.getAttributeValue(null, "role");
280 result = parseRelationMember(r, ref, type, role);
281 jumpToEnd();
282 } catch (IllegalDataException e) {
283 handleIllegalDataException(e);
284 }
285 return result;
286 }
287
288 private void parseChangeset(Long uploadChangesetId) throws XMLStreamException {
289
290 Long id = null;
291 if (parser.getAttributeValue(null, "id") != null) {
292 id = getLong("id");
293 }
294 // Read changeset info if neither upload-changeset nor id are set, or if they are both set to the same value
295 if (Objects.equals(id, uploadChangesetId)) {
296 uploadChangeset = new Changeset(id != null ? id.intValue() : 0);
297 while (true) {
298 int event = parser.next();
299 if (event == XMLStreamConstants.START_ELEMENT) {
300 if ("tag".equals(parser.getLocalName())) {
301 parseTag(uploadChangeset);
302 } else {
303 parseUnknown();
304 }
305 } else if (event == XMLStreamConstants.END_ELEMENT)
306 return;
307 }
308 } else {
309 jumpToEnd(false);
310 }
311 }
312
313 private void parseTag(Tagged t) throws XMLStreamException {
314 String key = parser.getAttributeValue(null, "k");
315 String value = parser.getAttributeValue(null, "v");
316 try {
317 parseTag(t, key, value);
318 } catch (IllegalDataException e) {
319 throwException(e);
320 }
321 jumpToEnd();
322 }
323
324 protected void parseUnknown(boolean printWarning) throws XMLStreamException {
325 final String element = parser.getLocalName();
326 if (printWarning && ("note".equals(element) || "meta".equals(element))) {
327 // we know that Overpass API returns those elements
328 Logging.debug(tr("Undefined element ''{0}'' found in input stream. Skipping.", element));
329 } else if (printWarning) {
330 Logging.info(tr("Undefined element ''{0}'' found in input stream. Skipping.", element));
331 }
332 while (true) {
333 int event = parser.next();
334 if (event == XMLStreamConstants.START_ELEMENT) {
335 parseUnknown(false); /* no more warning for inner elements */
336 } else if (event == XMLStreamConstants.END_ELEMENT)
337 return;
338 }
339 }
340
341 protected void parseUnknown() throws XMLStreamException {
342 parseUnknown(true);
343 }
344
345 /**
346 * When cursor is at the start of an element, moves it to the end tag of that element.
347 * Nested content is skipped.
348 *
349 * This is basically the same code as parseUnknown(), except for the warnings, which
350 * are displayed for inner elements and not at top level.
351 * @param printWarning if {@code true}, a warning message will be printed if an unknown element is met
352 * @throws XMLStreamException if there is an error processing the underlying XML source
353 */
354 private void jumpToEnd(boolean printWarning) throws XMLStreamException {
355 while (true) {
356 int event = parser.next();
357 if (event == XMLStreamConstants.START_ELEMENT) {
358 parseUnknown(printWarning);
359 } else if (event == XMLStreamConstants.END_ELEMENT)
360 return;
361 }
362 }
363
364 private void jumpToEnd() throws XMLStreamException {
365 jumpToEnd(true);
366 }
367
368 /**
369 * Read out the common attributes and put them into current OsmPrimitive.
370 * @param current primitive to update
371 * @throws IllegalDataException if there is an error processing the underlying XML source
372 */
373 private void readCommon(PrimitiveData current) throws IllegalDataException {
374 try {
375 parseId(current, getLong("id"));
376 parseTimestamp(current, parser.getAttributeValue(null, "timestamp"));
377 parseUser(current, parser.getAttributeValue(null, "user"), parser.getAttributeValue(null, "uid"));
378 parseVisible(current, parser.getAttributeValue(null, "visible"));
379 parseVersion(current, parser.getAttributeValue(null, "version"));
380 parseAction(current, parser.getAttributeValue(null, "action"));
381 parseChangeset(current, parser.getAttributeValue(null, "changeset"));
382 } catch (UncheckedParseException | XMLStreamException e) {
383 throw new IllegalDataException(e);
384 }
385 }
386
387 private long getLong(String name) throws XMLStreamException {
388 String value = parser.getAttributeValue(null, name);
389 try {
390 return getLong(name, value);
391 } catch (IllegalDataException e) {
392 throwException(e);
393 }
394 return 0; // should not happen
395 }
396
397 /**
398 * Exception thrown after user cancelation.
399 */
400 private static final class OsmParsingCanceledException extends XmlStreamParsingException implements ImportCancelException {
401 /**
402 * Constructs a new {@code OsmParsingCanceledException}.
403 * @param msg The error message
404 * @param location The parser location
405 */
406 OsmParsingCanceledException(String msg, Location location) {
407 super(msg, location);
408 }
409 }
410
411 @Override
412 protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
413 return doParseDataSet(source, progressMonitor, ir -> {
414 try {
415 setParser(XmlUtils.newSafeXMLInputFactory().createXMLStreamReader(ir));
416 parse();
417 } catch (XmlStreamParsingException | UncheckedParseException e) {
418 throw new IllegalDataException(e.getMessage(), e);
419 } catch (XMLStreamException e) {
420 String msg = e.getMessage();
421 Pattern p = Pattern.compile("Message: (.+)");
422 Matcher m = p.matcher(msg);
423 if (m.find()) {
424 msg = m.group(1);
425 }
426 if (e.getLocation() != null)
427 throw new IllegalDataException(tr("Line {0} column {1}: ",
428 e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()) + msg, e);
429 else
430 throw new IllegalDataException(msg, e);
431 }
432 });
433 }
434
435 /**
436 * Parse the given input source and return the dataset.
437 *
438 * @param source the source input stream. Must not be null.
439 * @param progressMonitor the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed
440 *
441 * @return the dataset with the parsed data
442 * @throws IllegalDataException if an error was found while parsing the data from the source
443 * @throws IllegalArgumentException if source is null
444 */
445 public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
446 return new OsmReader().doParseDataSet(source, progressMonitor);
447 }
448}
Note: See TracBrowser for help on using the repository browser.