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.io.InputStreamReader; 009import java.text.MessageFormat; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.List; 013import java.util.Objects; 014import java.util.function.Consumer; 015import java.util.regex.Matcher; 016import java.util.regex.Pattern; 017 018import javax.xml.stream.Location; 019import javax.xml.stream.XMLStreamConstants; 020import javax.xml.stream.XMLStreamException; 021import javax.xml.stream.XMLStreamReader; 022 023import org.openstreetmap.josm.data.Bounds; 024import org.openstreetmap.josm.data.DataSource; 025import org.openstreetmap.josm.data.coor.LatLon; 026import org.openstreetmap.josm.data.osm.AbstractPrimitive; 027import org.openstreetmap.josm.data.osm.Changeset; 028import org.openstreetmap.josm.data.osm.DataSet; 029import org.openstreetmap.josm.data.osm.DownloadPolicy; 030import org.openstreetmap.josm.data.osm.Node; 031import org.openstreetmap.josm.data.osm.NodeData; 032import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 033import org.openstreetmap.josm.data.osm.PrimitiveData; 034import org.openstreetmap.josm.data.osm.Relation; 035import org.openstreetmap.josm.data.osm.RelationData; 036import org.openstreetmap.josm.data.osm.RelationMemberData; 037import org.openstreetmap.josm.data.osm.Tagged; 038import org.openstreetmap.josm.data.osm.UploadPolicy; 039import org.openstreetmap.josm.data.osm.User; 040import org.openstreetmap.josm.data.osm.Way; 041import org.openstreetmap.josm.data.osm.WayData; 042import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 043import org.openstreetmap.josm.gui.progress.ProgressMonitor; 044import org.openstreetmap.josm.tools.CheckParameterUtil; 045import org.openstreetmap.josm.tools.Logging; 046import org.openstreetmap.josm.tools.UncheckedParseException; 047import org.openstreetmap.josm.tools.Utils; 048import org.openstreetmap.josm.tools.XmlUtils; 049import org.openstreetmap.josm.tools.date.DateUtils; 050 051/** 052 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it. 053 * 054 * For each xml element, there is a dedicated method. 055 * The XMLStreamReader cursor points to the start of the element, when the method is 056 * entered, and it must point to the end of the same element, when it is exited. 057 */ 058public class OsmReader extends AbstractReader { 059 060 protected XMLStreamReader parser; 061 062 protected boolean cancel; 063 064 /** Used by plugins to register themselves as data postprocessors. */ 065 private static volatile List<OsmServerReadPostprocessor> postprocessors; 066 067 /** Register a new postprocessor. 068 * @param pp postprocessor 069 * @see #deregisterPostprocessor 070 */ 071 public static void registerPostprocessor(OsmServerReadPostprocessor pp) { 072 if (postprocessors == null) { 073 postprocessors = new ArrayList<>(); 074 } 075 postprocessors.add(pp); 076 } 077 078 /** 079 * Deregister a postprocessor previously registered with {@link #registerPostprocessor}. 080 * @param pp postprocessor 081 * @see #registerPostprocessor 082 */ 083 public static void deregisterPostprocessor(OsmServerReadPostprocessor pp) { 084 if (postprocessors != null) { 085 postprocessors.remove(pp); 086 } 087 } 088 089 /** 090 * constructor (for private and subclasses use only) 091 * 092 * @see #parseDataSet(InputStream, ProgressMonitor) 093 */ 094 protected OsmReader() { 095 // Restricts visibility 096 } 097 098 protected void setParser(XMLStreamReader parser) { 099 this.parser = parser; 100 } 101 102 protected void throwException(String msg, Throwable th) throws XMLStreamException { 103 throw new XmlStreamParsingException(msg, parser.getLocation(), th); 104 } 105 106 protected void throwException(String msg) throws XMLStreamException { 107 throw new XmlStreamParsingException(msg, parser.getLocation()); 108 } 109 110 protected void parse() throws XMLStreamException { 111 int event = parser.getEventType(); 112 while (true) { 113 if (event == XMLStreamConstants.START_ELEMENT) { 114 parseRoot(); 115 } else if (event == XMLStreamConstants.END_ELEMENT) 116 return; 117 if (parser.hasNext()) { 118 event = parser.next(); 119 } else { 120 break; 121 } 122 } 123 parser.close(); 124 } 125 126 protected void parseRoot() throws XMLStreamException { 127 if ("osm".equals(parser.getLocalName())) { 128 parseOsm(); 129 } else { 130 parseUnknown(); 131 } 132 } 133 134 private void parseOsm() throws XMLStreamException { 135 String v = parser.getAttributeValue(null, "version"); 136 if (v == null) { 137 throwException(tr("Missing mandatory attribute ''{0}''.", "version")); 138 } 139 if (!"0.6".equals(v)) { 140 throwException(tr("Unsupported version: {0}", v)); 141 } 142 ds.setVersion(v); 143 parsePolicy("download", policy -> ds.setDownloadPolicy(DownloadPolicy.of(policy))); 144 parsePolicy("upload", policy -> ds.setUploadPolicy(UploadPolicy.of(policy))); 145 if ("true".equalsIgnoreCase(parser.getAttributeValue(null, "locked"))) { 146 ds.lock(); 147 } 148 String generator = parser.getAttributeValue(null, "generator"); 149 Long uploadChangesetId = null; 150 if (parser.getAttributeValue(null, "upload-changeset") != null) { 151 uploadChangesetId = getLong("upload-changeset"); 152 } 153 while (true) { 154 int event = parser.next(); 155 156 if (cancel) { 157 cancel = false; 158 throw new OsmParsingCanceledException(tr("Reading was canceled"), parser.getLocation()); 159 } 160 161 if (event == XMLStreamConstants.START_ELEMENT) { 162 switch (parser.getLocalName()) { 163 case "bounds": 164 parseBounds(generator); 165 break; 166 case "node": 167 parseNode(); 168 break; 169 case "way": 170 parseWay(); 171 break; 172 case "relation": 173 parseRelation(); 174 break; 175 case "changeset": 176 parseChangeset(uploadChangesetId); 177 break; 178 default: 179 parseUnknown(); 180 } 181 } else if (event == XMLStreamConstants.END_ELEMENT) { 182 return; 183 } 184 } 185 } 186 187 private void parsePolicy(String key, Consumer<String> consumer) throws XMLStreamException { 188 String policy = parser.getAttributeValue(null, key); 189 if (policy != null) { 190 try { 191 consumer.accept(policy); 192 } catch (IllegalArgumentException e) { 193 throwException(MessageFormat.format("Illegal value for attribute ''{0}''. Got ''{1}''.", key, policy), e); 194 } 195 } 196 } 197 198 private void parseBounds(String generator) throws XMLStreamException { 199 String minlon = parser.getAttributeValue(null, "minlon"); 200 String minlat = parser.getAttributeValue(null, "minlat"); 201 String maxlon = parser.getAttributeValue(null, "maxlon"); 202 String maxlat = parser.getAttributeValue(null, "maxlat"); 203 String origin = parser.getAttributeValue(null, "origin"); 204 if (minlon != null && maxlon != null && minlat != null && maxlat != null) { 205 if (origin == null) { 206 origin = generator; 207 } 208 Bounds bounds = new Bounds( 209 Double.parseDouble(minlat), Double.parseDouble(minlon), 210 Double.parseDouble(maxlat), Double.parseDouble(maxlon)); 211 if (bounds.isOutOfTheWorld()) { 212 Bounds copy = new Bounds(bounds); 213 bounds.normalize(); 214 Logging.info("Bbox " + copy + " is out of the world, normalized to " + bounds); 215 } 216 DataSource src = new DataSource(bounds, origin); 217 ds.addDataSource(src); 218 } else { 219 throwException(tr("Missing mandatory attributes on element ''bounds''. " + 220 "Got minlon=''{0}'',minlat=''{1}'',maxlon=''{2}'',maxlat=''{3}'', origin=''{4}''.", 221 minlon, minlat, maxlon, maxlat, origin 222 )); 223 } 224 jumpToEnd(); 225 } 226 227 protected Node parseNode() throws XMLStreamException { 228 NodeData nd = new NodeData(); 229 String lat = parser.getAttributeValue(null, "lat"); 230 String lon = parser.getAttributeValue(null, "lon"); 231 LatLon ll = null; 232 if (lat != null && lon != null) { 233 try { 234 ll = new LatLon(Double.parseDouble(lat), Double.parseDouble(lon)); 235 nd.setCoor(ll); 236 } catch (NumberFormatException e) { 237 Logging.trace(e); 238 } 239 } 240 readCommon(nd); 241 if (lat != null && lon != null && (ll == null || !ll.isValid())) { 242 throwException(tr("Illegal value for attributes ''lat'', ''lon'' on node with ID {0}. Got ''{1}'', ''{2}''.", 243 Long.toString(nd.getId()), lat, lon)); 244 } 245 Node n = new Node(nd.getId(), nd.getVersion()); 246 n.setVisible(nd.isVisible()); 247 n.load(nd); 248 externalIdMap.put(nd.getPrimitiveId(), n); 249 while (true) { 250 int event = parser.next(); 251 if (event == XMLStreamConstants.START_ELEMENT) { 252 if ("tag".equals(parser.getLocalName())) { 253 parseTag(n); 254 } else { 255 parseUnknown(); 256 } 257 } else if (event == XMLStreamConstants.END_ELEMENT) 258 return n; 259 } 260 } 261 262 protected Way parseWay() throws XMLStreamException { 263 WayData wd = new WayData(); 264 readCommon(wd); 265 Way w = new Way(wd.getId(), wd.getVersion()); 266 w.setVisible(wd.isVisible()); 267 w.load(wd); 268 externalIdMap.put(wd.getPrimitiveId(), w); 269 270 Collection<Long> nodeIds = new ArrayList<>(); 271 while (true) { 272 int event = parser.next(); 273 if (event == XMLStreamConstants.START_ELEMENT) { 274 switch (parser.getLocalName()) { 275 case "nd": 276 nodeIds.add(parseWayNode(w)); 277 break; 278 case "tag": 279 parseTag(w); 280 break; 281 default: 282 parseUnknown(); 283 } 284 } else if (event == XMLStreamConstants.END_ELEMENT) { 285 break; 286 } 287 } 288 if (w.isDeleted() && !nodeIds.isEmpty()) { 289 Logging.info(tr("Deleted way {0} contains nodes", Long.toString(w.getUniqueId()))); 290 nodeIds = new ArrayList<>(); 291 } 292 ways.put(wd.getUniqueId(), nodeIds); 293 return w; 294 } 295 296 private long parseWayNode(Way w) throws XMLStreamException { 297 if (parser.getAttributeValue(null, "ref") == null) { 298 throwException( 299 tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", Long.toString(w.getUniqueId())) 300 ); 301 } 302 long id = getLong("ref"); 303 if (id == 0) { 304 throwException( 305 tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", Long.toString(id)) 306 ); 307 } 308 jumpToEnd(); 309 return id; 310 } 311 312 protected Relation parseRelation() throws XMLStreamException { 313 RelationData rd = new RelationData(); 314 readCommon(rd); 315 Relation r = new Relation(rd.getId(), rd.getVersion()); 316 r.setVisible(rd.isVisible()); 317 r.load(rd); 318 externalIdMap.put(rd.getPrimitiveId(), r); 319 320 Collection<RelationMemberData> members = new ArrayList<>(); 321 while (true) { 322 int event = parser.next(); 323 if (event == XMLStreamConstants.START_ELEMENT) { 324 switch (parser.getLocalName()) { 325 case "member": 326 members.add(parseRelationMember(r)); 327 break; 328 case "tag": 329 parseTag(r); 330 break; 331 default: 332 parseUnknown(); 333 } 334 } else if (event == XMLStreamConstants.END_ELEMENT) { 335 break; 336 } 337 } 338 if (r.isDeleted() && !members.isEmpty()) { 339 Logging.info(tr("Deleted relation {0} contains members", Long.toString(r.getUniqueId()))); 340 members = new ArrayList<>(); 341 } 342 relations.put(rd.getUniqueId(), members); 343 return r; 344 } 345 346 private RelationMemberData parseRelationMember(Relation r) throws XMLStreamException { 347 OsmPrimitiveType type = null; 348 long id = 0; 349 String value = parser.getAttributeValue(null, "ref"); 350 if (value == null) { 351 throwException(tr("Missing attribute ''ref'' on member in relation {0}.", Long.toString(r.getUniqueId()))); 352 } 353 try { 354 id = Long.parseLong(value); 355 } catch (NumberFormatException e) { 356 throwException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", Long.toString(r.getUniqueId()), 357 value), e); 358 } 359 value = parser.getAttributeValue(null, "type"); 360 if (value == null) { 361 throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(id), Long.toString(r.getUniqueId()))); 362 } 363 try { 364 type = OsmPrimitiveType.fromApiTypeName(value); 365 } catch (IllegalArgumentException e) { 366 throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", 367 Long.toString(id), Long.toString(r.getUniqueId()), value), e); 368 } 369 String role = parser.getAttributeValue(null, "role"); 370 371 if (id == 0) { 372 throwException(tr("Incomplete <member> specification with ref=0")); 373 } 374 jumpToEnd(); 375 return new RelationMemberData(role, type, id); 376 } 377 378 private void parseChangeset(Long uploadChangesetId) throws XMLStreamException { 379 380 Long id = null; 381 if (parser.getAttributeValue(null, "id") != null) { 382 id = getLong("id"); 383 } 384 // Read changeset info if neither upload-changeset nor id are set, or if they are both set to the same value 385 if (Objects.equals(id, uploadChangesetId)) { 386 uploadChangeset = new Changeset(id != null ? id.intValue() : 0); 387 while (true) { 388 int event = parser.next(); 389 if (event == XMLStreamConstants.START_ELEMENT) { 390 if ("tag".equals(parser.getLocalName())) { 391 parseTag(uploadChangeset); 392 } else { 393 parseUnknown(); 394 } 395 } else if (event == XMLStreamConstants.END_ELEMENT) 396 return; 397 } 398 } else { 399 jumpToEnd(false); 400 } 401 } 402 403 private void parseTag(Tagged t) throws XMLStreamException { 404 String key = parser.getAttributeValue(null, "k"); 405 String value = parser.getAttributeValue(null, "v"); 406 if (key == null || value == null) { 407 throwException(tr("Missing key or value attribute in tag.")); 408 } else if (Utils.isStripEmpty(key) && t instanceof AbstractPrimitive) { 409 // #14199: Empty keys as ignored by AbstractPrimitive#put, but it causes problems to fix existing data 410 // Drop the tag on import, but flag the primitive as modified 411 ((AbstractPrimitive) t).setModified(true); 412 } else { 413 t.put(key.intern(), value.intern()); 414 } 415 jumpToEnd(); 416 } 417 418 protected void parseUnknown(boolean printWarning) throws XMLStreamException { 419 final String element = parser.getLocalName(); 420 if (printWarning && ("note".equals(element) || "meta".equals(element))) { 421 // we know that Overpass API returns those elements 422 Logging.debug(tr("Undefined element ''{0}'' found in input stream. Skipping.", element)); 423 } else if (printWarning) { 424 Logging.info(tr("Undefined element ''{0}'' found in input stream. Skipping.", element)); 425 } 426 while (true) { 427 int event = parser.next(); 428 if (event == XMLStreamConstants.START_ELEMENT) { 429 parseUnknown(false); /* no more warning for inner elements */ 430 } else if (event == XMLStreamConstants.END_ELEMENT) 431 return; 432 } 433 } 434 435 protected void parseUnknown() throws XMLStreamException { 436 parseUnknown(true); 437 } 438 439 /** 440 * When cursor is at the start of an element, moves it to the end tag of that element. 441 * Nested content is skipped. 442 * 443 * This is basically the same code as parseUnknown(), except for the warnings, which 444 * are displayed for inner elements and not at top level. 445 * @param printWarning if {@code true}, a warning message will be printed if an unknown element is met 446 * @throws XMLStreamException if there is an error processing the underlying XML source 447 */ 448 private void jumpToEnd(boolean printWarning) throws XMLStreamException { 449 while (true) { 450 int event = parser.next(); 451 if (event == XMLStreamConstants.START_ELEMENT) { 452 parseUnknown(printWarning); 453 } else if (event == XMLStreamConstants.END_ELEMENT) 454 return; 455 } 456 } 457 458 private void jumpToEnd() throws XMLStreamException { 459 jumpToEnd(true); 460 } 461 462 private User createUser(String uid, String name) throws XMLStreamException { 463 if (uid == null) { 464 if (name == null) 465 return null; 466 return User.createLocalUser(name); 467 } 468 try { 469 long id = Long.parseLong(uid); 470 return User.createOsmUser(id, name); 471 } catch (NumberFormatException e) { 472 throwException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid), e); 473 } 474 return null; 475 } 476 477 /** 478 * Read out the common attributes and put them into current OsmPrimitive. 479 * @param current primitive to update 480 * @throws XMLStreamException if there is an error processing the underlying XML source 481 */ 482 private void readCommon(PrimitiveData current) throws XMLStreamException { 483 current.setId(getLong("id")); 484 if (current.getUniqueId() == 0) { 485 throwException(tr("Illegal object with ID=0.")); 486 } 487 488 String time = parser.getAttributeValue(null, "timestamp"); 489 if (time != null && !time.isEmpty()) { 490 current.setRawTimestamp((int) (DateUtils.tsFromString(time)/1000)); 491 } 492 493 String user = parser.getAttributeValue(null, "user"); 494 String uid = parser.getAttributeValue(null, "uid"); 495 current.setUser(createUser(uid, user)); 496 497 String visible = parser.getAttributeValue(null, "visible"); 498 if (visible != null) { 499 current.setVisible(Boolean.parseBoolean(visible)); 500 } 501 502 String versionString = parser.getAttributeValue(null, "version"); 503 int version = 0; 504 if (versionString != null) { 505 try { 506 version = Integer.parseInt(versionString); 507 } catch (NumberFormatException e) { 508 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", 509 Long.toString(current.getUniqueId()), versionString), e); 510 } 511 switch (ds.getVersion()) { 512 case "0.6": 513 if (version <= 0 && !current.isNew()) { 514 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", 515 Long.toString(current.getUniqueId()), versionString)); 516 } else if (version < 0 && current.isNew()) { 517 Logging.warn(tr("Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", 518 current.getUniqueId(), version, 0, "0.6")); 519 version = 0; 520 } 521 break; 522 default: 523 // should not happen. API version has been checked before 524 throwException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion())); 525 } 526 } else { 527 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6 528 if (!current.isNew() && ds.getVersion() != null && "0.6".equals(ds.getVersion())) { 529 throwException(tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId()))); 530 } 531 } 532 current.setVersion(version); 533 534 String action = parser.getAttributeValue(null, "action"); 535 if (action == null) { 536 // do nothing 537 } else if ("delete".equals(action)) { 538 current.setDeleted(true); 539 current.setModified(current.isVisible()); 540 } else if ("modify".equals(action)) { 541 current.setModified(true); 542 } 543 544 String v = parser.getAttributeValue(null, "changeset"); 545 if (v == null) { 546 current.setChangesetId(0); 547 } else { 548 try { 549 current.setChangesetId(Integer.parseInt(v)); 550 } catch (IllegalArgumentException e) { 551 Logging.debug(e.getMessage()); 552 if (current.isNew()) { 553 // for a new primitive we just log a warning 554 Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", 555 v, current.getUniqueId())); 556 current.setChangesetId(0); 557 } else { 558 // for an existing primitive this is a problem 559 throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v), e); 560 } 561 } catch (IllegalStateException e) { 562 // thrown for positive changeset id on new primitives 563 Logging.debug(e); 564 Logging.info(e.getMessage()); 565 current.setChangesetId(0); 566 } 567 if (current.getChangesetId() <= 0) { 568 if (current.isNew()) { 569 // for a new primitive we just log a warning 570 Logging.info(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", 571 v, current.getUniqueId())); 572 current.setChangesetId(0); 573 } else if (current.getChangesetId() < 0) { 574 // for an existing primitive this is a problem only for negative ids (GPDR extracts are set to 0) 575 throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v)); 576 } 577 } 578 } 579 } 580 581 private long getLong(String name) throws XMLStreamException { 582 String value = parser.getAttributeValue(null, name); 583 if (value == null) { 584 throwException(tr("Missing required attribute ''{0}''.", name)); 585 } 586 try { 587 return Long.parseLong(value); 588 } catch (NumberFormatException e) { 589 throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.", name, value), e); 590 } 591 return 0; // should not happen 592 } 593 594 /** 595 * Exception thrown after user cancelation. 596 */ 597 private static final class OsmParsingCanceledException extends XmlStreamParsingException implements ImportCancelException { 598 /** 599 * Constructs a new {@code OsmParsingCanceledException}. 600 * @param msg The error message 601 * @param location The parser location 602 */ 603 OsmParsingCanceledException(String msg, Location location) { 604 super(msg, location); 605 } 606 } 607 608 @Override 609 protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 610 if (progressMonitor == null) { 611 progressMonitor = NullProgressMonitor.INSTANCE; 612 } 613 ProgressMonitor.CancelListener cancelListener = () -> cancel = true; 614 progressMonitor.addCancelListener(cancelListener); 615 CheckParameterUtil.ensureParameterNotNull(source, "source"); 616 try { 617 progressMonitor.beginTask(tr("Prepare OSM data...", 2)); 618 progressMonitor.indeterminateSubTask(tr("Parsing OSM data...")); 619 620 try (InputStreamReader ir = UTFInputStreamReader.create(source)) { 621 setParser(XmlUtils.newSafeXMLInputFactory().createXMLStreamReader(ir)); 622 parse(); 623 } 624 progressMonitor.worked(1); 625 626 boolean readOnly = getDataSet().isLocked(); 627 628 progressMonitor.indeterminateSubTask(tr("Preparing data set...")); 629 if (readOnly) { 630 getDataSet().unlock(); 631 } 632 prepareDataSet(); 633 if (readOnly) { 634 getDataSet().lock(); 635 } 636 progressMonitor.worked(1); 637 638 // iterate over registered postprocessors and give them each a chance 639 // to modify the dataset we have just loaded. 640 if (postprocessors != null) { 641 for (OsmServerReadPostprocessor pp : postprocessors) { 642 pp.postprocessDataSet(getDataSet(), progressMonitor); 643 } 644 } 645 // Make sure postprocessors did not change the read-only state 646 if (readOnly && !getDataSet().isLocked()) { 647 getDataSet().lock(); 648 } 649 return getDataSet(); 650 } catch (IllegalDataException e) { 651 throw e; 652 } catch (XmlStreamParsingException | UncheckedParseException e) { 653 throw new IllegalDataException(e.getMessage(), e); 654 } catch (XMLStreamException e) { 655 String msg = e.getMessage(); 656 Pattern p = Pattern.compile("Message: (.+)"); 657 Matcher m = p.matcher(msg); 658 if (m.find()) { 659 msg = m.group(1); 660 } 661 if (e.getLocation() != null) 662 throw new IllegalDataException(tr("Line {0} column {1}: ", 663 e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()) + msg, e); 664 else 665 throw new IllegalDataException(msg, e); 666 } catch (IOException e) { 667 throw new IllegalDataException(e); 668 } finally { 669 progressMonitor.finishTask(); 670 progressMonitor.removeCancelListener(cancelListener); 671 } 672 } 673 674 /** 675 * Parse the given input source and return the dataset. 676 * 677 * @param source the source input stream. Must not be null. 678 * @param progressMonitor the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed 679 * 680 * @return the dataset with the parsed data 681 * @throws IllegalDataException if an error was found while parsing the data from the source 682 * @throws IllegalArgumentException if source is null 683 */ 684 public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 685 return new OsmReader().doParseDataSet(source, progressMonitor); 686 } 687}